diff options
author | Devaev Maxim <[email protected]> | 2019-02-07 05:45:36 +0300 |
---|---|---|
committer | Devaev Maxim <[email protected]> | 2019-02-07 05:45:36 +0300 |
commit | de1bed956cc714e8b750e3df22a563eac0800876 (patch) | |
tree | 8d5bea19bd8380313eab5ee1cdfb75784fdb3dc7 | |
parent | 5bec2ff1449832be374d69ece14e7a63d5d7379a (diff) |
new hid protocol with crc
-rw-r--r-- | configs/kvmd/platforms/kvmd.v1-hdmi.yaml | 9 | ||||
-rw-r--r-- | configs/kvmd/platforms/kvmd.v1-vga.yaml | 9 | ||||
-rw-r--r-- | hid/platformio.ini | 1 | ||||
-rw-r--r-- | hid/src/main.cpp | 203 | ||||
-rw-r--r-- | kvmd/apps/kvmd/__init__.py | 11 | ||||
-rw-r--r-- | kvmd/apps/kvmd/hid.py | 262 | ||||
-rw-r--r-- | kvmd/apps/kvmd/server.py | 14 | ||||
-rw-r--r-- | testenv/kvmd.yaml | 12 | ||||
-rw-r--r-- | web/share/js/kvm/hid.js | 5 | ||||
-rw-r--r-- | web/share/js/kvm/keyboard.js | 14 | ||||
-rw-r--r-- | web/share/js/kvm/mouse.js | 14 | ||||
-rw-r--r-- | web/share/js/kvm/session.js | 2 |
12 files changed, 394 insertions, 162 deletions
diff --git a/configs/kvmd/platforms/kvmd.v1-hdmi.yaml b/configs/kvmd/platforms/kvmd.v1-hdmi.yaml index 63f146a3..c181ceb5 100644 --- a/configs/kvmd/platforms/kvmd.v1-hdmi.yaml +++ b/configs/kvmd/platforms/kvmd.v1-hdmi.yaml @@ -19,9 +19,16 @@ kvmd: pinout: reset: 4 + reset_delay: 0.1 + device: "/dev/kvmd-hid" speed: 115200 - reset_delay: 0.1 + read_timeout: 2.0 + read_retries: 10 + common_retries: 100 + retries_delay: 0.1 + + state_poll: 1.0 atx: pinout: diff --git a/configs/kvmd/platforms/kvmd.v1-vga.yaml b/configs/kvmd/platforms/kvmd.v1-vga.yaml index 9e401733..5fc4a44c 100644 --- a/configs/kvmd/platforms/kvmd.v1-vga.yaml +++ b/configs/kvmd/platforms/kvmd.v1-vga.yaml @@ -19,9 +19,16 @@ kvmd: pinout: reset: 4 + reset_delay: 0.1 + device: "/dev/kvmd-hid" speed: 115200 - reset_delay: 0.1 + read_timeout: 2.0 + read_retries: 10 + common_retries: 100 + retries_delay: 0.1 + + state_poll: 1.0 atx: pinout: diff --git a/hid/platformio.ini b/hid/platformio.ini index 13036b9d..f70cd66c 100644 --- a/hid/platformio.ini +++ b/hid/platformio.ini @@ -17,3 +17,4 @@ monitor_speed = 115200 lib_deps = diff --git a/hid/src/main.cpp b/hid/src/main.cpp index 93a90b8b..a7867981 100644 --- a/hid/src/main.cpp +++ b/hid/src/main.cpp @@ -1,5 +1,6 @@ #include <Arduino.h> #include <HID-Project.h> +#include <TimerOne.h> #include "inline.h" #include "keymap.h" @@ -7,35 +8,42 @@ #define CMD_SERIAL Serial1 #define CMD_SERIAL_SPEED 115200 - -#define CMD_MOUSE_LEFT 0b10000000 -#define CMD_MOUSE_LEFT_STATE 0b00001000 -#define CMD_MOUSE_RIGHT 0b01000000 -#define CMD_MOUSE_RIGHT_STATE 0b00000100 - -#define REPORT_INTERVAL 100 +#define CMD_RECV_TIMEOUT 100000 + +#define PROTO_MAGIC 0x33 +#define PROTO_CRC_POLINOM 0xA001 +// ----------------------------------------- +#define PROTO_RESP_OK 0x20 +#define PROTO_RESP_NONE 0x24 +#define PROTO_RESP_CRC_ERROR 0x40 +#define PROTO_RESP_INVALID_ERROR 0x45 +#define PROTO_RESP_TIMEOUT_ERROR 0x48 +// ----------------------------------------- +#define PROTO_CMD_PING 0x01 +#define PROTO_CMD_REPEAT 0x02 +#define PROTO_CMD_RESET_HID 0x10 +#define PROTO_CMD_KEY_EVENT 0x11 +#define PROTO_CMD_MOUSE_MOVE_EVENT 0x12 +#define PROTO_CMD_MOUSE_BUTTON_EVENT 0x13 +#define PROTO_CMD_MOUSE_WHEEL_EVENT 0x14 +// ----------------------------------------- +#define PROTO_CMD_MOUSE_BUTTON_LEFT_SELECT 0b10000000 +#define PROTO_CMD_MOUSE_BUTTON_LEFT_STATE 0b00001000 +#define PROTO_CMD_MOUSE_BUTTON_RIGHT_SELECT 0b01000000 +#define PROTO_CMD_MOUSE_BUTTON_RIGHT_STATE 0b00000100 // ----------------------------------------------------------------------------- -INLINE void readNoop() { - for (int count = 0; count < 4; ++count) { - CMD_SERIAL.read(); - } -} - -INLINE void cmdResetHid() { // 0 bytes - readNoop(); +INLINE void cmdResetHid(const uint8_t *buffer) { // 0 bytes BootKeyboard.releaseAll(); SingleAbsoluteMouse.releaseAll(); } -INLINE void cmdKeyEvent() { // 2 bytes - KeyboardKeycode code = keymap((uint8_t)CMD_SERIAL.read()); - uint8_t state = CMD_SERIAL.read(); - CMD_SERIAL.read(); // unused - CMD_SERIAL.read(); // unused +INLINE void cmdKeyEvent(const uint8_t *buffer) { // 2 bytes + KeyboardKeycode code = keymap(buffer[0]); + if (code != KEY_ERROR_UNDEFINED) { - if (state) { + if (buffer[1]) { BootKeyboard.press(code); } else { BootKeyboard.release(code); @@ -43,28 +51,29 @@ INLINE void cmdKeyEvent() { // 2 bytes } } -INLINE void cmdMouseMoveEvent() { // 4 bytes - int x = (int)CMD_SERIAL.read() << 8; - x |= (int)CMD_SERIAL.read(); - int y = (int)CMD_SERIAL.read() << 8; - y |= (int)CMD_SERIAL.read(); +INLINE void cmdMouseMoveEvent(const uint8_t *buffer) { // 4 bytes + int x = (int)buffer[0] << 8; + x |= (int)buffer[1]; + + int y = (int)buffer[2] << 8; + y |= (int)buffer[3]; + SingleAbsoluteMouse.moveTo(x, y); } -INLINE void cmdMouseButtonEvent() { // 1 byte - uint8_t state = CMD_SERIAL.read(); - CMD_SERIAL.read(); // unused - CMD_SERIAL.read(); // unused - CMD_SERIAL.read(); // unused - if (state & CMD_MOUSE_LEFT) { - if (state & CMD_MOUSE_LEFT_STATE) { +INLINE void cmdMouseButtonEvent(const uint8_t *buffer) { // 1 byte + uint8_t state = buffer[0]; + + if (state & PROTO_CMD_MOUSE_BUTTON_LEFT_SELECT) { + if (state & PROTO_CMD_MOUSE_BUTTON_LEFT_STATE) { SingleAbsoluteMouse.press(MOUSE_LEFT); } else { SingleAbsoluteMouse.release(MOUSE_LEFT); } } - if (state & CMD_MOUSE_RIGHT) { - if (state & CMD_MOUSE_RIGHT_STATE) { + + if (state & PROTO_CMD_MOUSE_BUTTON_RIGHT_SELECT) { + if (state & PROTO_CMD_MOUSE_BUTTON_RIGHT_STATE) { SingleAbsoluteMouse.press(MOUSE_RIGHT); } else { SingleAbsoluteMouse.release(MOUSE_RIGHT); @@ -72,45 +81,113 @@ INLINE void cmdMouseButtonEvent() { // 1 byte } } -INLINE void cmdMouseWheelEvent() { // 2 bytes - CMD_SERIAL.read(); // delta_x is not supported by hid-project now - signed char delta_y = CMD_SERIAL.read(); - CMD_SERIAL.read(); // unused - CMD_SERIAL.read(); // unused +INLINE void cmdMouseWheelEvent(const uint8_t *buffer) { // 2 bytes + // delta_x is not supported by hid-project now + signed char delta_y = buffer[1]; + SingleAbsoluteMouse.move(0, 0, delta_y); } // ----------------------------------------------------------------------------- +INLINE uint16_t makeCrc16(const uint8_t *buffer, const unsigned length) { + uint16_t crc = 0xFFFF; + + for (unsigned byte_count = 0; byte_count < length; ++byte_count) { + crc = crc ^ buffer[byte_count]; + for (unsigned bit_count = 0; bit_count < 8; ++bit_count) { + if ((crc & 0x0001) == 0) { + crc = crc >> 1; + } else { + crc = crc >> 1; + crc = crc ^ PROTO_CRC_POLINOM; + } + } + } + return crc; +} + + +// ----------------------------------------------------------------------------- +volatile bool cmd_recv_timed_out = false; + +INLINE void recvTimerStop(bool flag) { + Timer1.stop(); + cmd_recv_timed_out = flag; +} + +INLINE void resetCmdRecvTimeout() { + recvTimerStop(false); + Timer1.initialize(CMD_RECV_TIMEOUT); +} + +INLINE void sendCmdResponse(uint8_t code=0) { + static uint8_t prev_code = PROTO_RESP_NONE; + if (code == 0) { + code = prev_code; // Repeat the last code + } else { + prev_code = code; + } + + uint8_t buffer[4]; + buffer[0] = PROTO_MAGIC; + buffer[1] = code; + uint16_t crc = makeCrc16(buffer, 2); + buffer[2] = (uint8_t)(crc >> 8); + buffer[3] = (uint8_t)(crc & 0xFF); + + recvTimerStop(false); + CMD_SERIAL.write(buffer, 4); +} + +void intRecvTimedOut() { + recvTimerStop(true); +} + void setup() { - CMD_SERIAL.begin(CMD_SERIAL_SPEED); BootKeyboard.begin(); SingleAbsoluteMouse.begin(); + + Timer1.attachInterrupt(intRecvTimedOut); + CMD_SERIAL.begin(CMD_SERIAL_SPEED); } void loop() { - static unsigned long last_report = 0; - bool cmd_processed = false; - - if (CMD_SERIAL.available() >= 5) { - switch ((uint8_t)CMD_SERIAL.read()) { - case 0: cmdResetHid(); break; - case 1: cmdKeyEvent(); break; - case 2: cmdMouseMoveEvent(); break; - case 3: cmdMouseButtonEvent(); break; - case 4: cmdMouseWheelEvent(); break; - default: readNoop(); break; + uint8_t buffer[8]; + unsigned index = 0; + + while (true) { + if (CMD_SERIAL.available() > 0) { + buffer[index] = (uint8_t)CMD_SERIAL.read(); + if (index == 7) { + uint16_t crc = (uint16_t)buffer[6] << 8; + crc |= (uint16_t)buffer[7]; + + if (makeCrc16(buffer, 6) == crc) { +# define HANDLE(_handler) { _handler(buffer + 2); sendCmdResponse(PROTO_RESP_OK); break; } + switch (buffer[1]) { + case PROTO_CMD_RESET_HID: HANDLE(cmdResetHid); + case PROTO_CMD_KEY_EVENT: HANDLE(cmdKeyEvent); + case PROTO_CMD_MOUSE_MOVE_EVENT: HANDLE(cmdMouseMoveEvent); + case PROTO_CMD_MOUSE_BUTTON_EVENT: HANDLE(cmdMouseButtonEvent); + case PROTO_CMD_MOUSE_WHEEL_EVENT: HANDLE(cmdMouseWheelEvent); + + case PROTO_CMD_PING: sendCmdResponse(PROTO_RESP_OK); break; + case PROTO_CMD_REPEAT: sendCmdResponse(); break; + default: sendCmdResponse(PROTO_RESP_INVALID_ERROR); break; + } +# undef HANDLE + } else { + sendCmdResponse(PROTO_RESP_CRC_ERROR); + } + index = 0; + } else { + resetCmdRecvTimeout(); + index += 1; + } + } else if (index > 0 && cmd_recv_timed_out) { + sendCmdResponse(PROTO_RESP_TIMEOUT_ERROR); + index = 0; } - cmd_processed = true; - } - - unsigned long now = millis(); - if ( - cmd_processed - || (now >= last_report && now - last_report >= REPORT_INTERVAL) - || (now < last_report && ((unsigned long) -1) - last_report + now >= REPORT_INTERVAL) - ) { - CMD_SERIAL.write(0); - last_report = now; } } diff --git a/kvmd/apps/kvmd/__init__.py b/kvmd/apps/kvmd/__init__.py index e8b74abc..e9daea32 100644 --- a/kvmd/apps/kvmd/__init__.py +++ b/kvmd/apps/kvmd/__init__.py @@ -35,9 +35,18 @@ def main() -> None: hid = Hid( reset=int(config["hid"]["pinout"]["reset"]), + + reset_delay=float(config["hid"]["reset_delay"]), + device_path=str(config["hid"]["device"]), speed=int(config["hid"]["speed"]), - reset_delay=float(config["hid"]["reset_delay"]), + read_timeout=float(config["hid"]["read_timeout"]), + read_retries=int(config["hid"]["read_retries"]), + common_retries=int(config["hid"]["common_retries"]), + retries_delay=float(config["hid"]["retries_delay"]), + noop=bool(config["hid"].get("noop", False)), + + state_poll=float(config["hid"]["state_poll"]), ) atx = Atx( diff --git a/kvmd/apps/kvmd/hid.py b/kvmd/apps/kvmd/hid.py index 26cfa898..3f329c64 100644 --- a/kvmd/apps/kvmd/hid.py +++ b/kvmd/apps/kvmd/hid.py @@ -11,6 +11,7 @@ import time from typing import Dict from typing import Set from typing import NamedTuple +from typing import AsyncGenerator import yaml import serial @@ -33,41 +34,92 @@ class _KeyEvent(NamedTuple): key: str state: bool + @staticmethod + def is_valid(key: str) -> bool: + return (key in _KEYMAP) + + def make_command(self) -> bytes: + code = _KEYMAP[self.key] + key_bytes = bytes([code]) + assert len(key_bytes) == 1, (self, key_bytes, code) + state_bytes = (b"\x01" if self.state else b"\x00") + return b"\x11" + key_bytes + state_bytes + b"\x00\x00" + class _MouseMoveEvent(NamedTuple): to_x: int to_y: int + def make_command(self) -> bytes: + to_x = min(max(-32768, self.to_x), 32767) + to_y = min(max(-32768, self.to_y), 32767) + return b"\x12" + struct.pack(">hh", to_x, to_y) + class _MouseButtonEvent(NamedTuple): button: str state: bool + @staticmethod + def is_valid(button: str) -> bool: + return (button in ["left", "right"]) + + def make_command(self) -> bytes: + code = 0 + if self.button == "left": + code = (0b10000000 | (0b00001000 if self.state else 0)) + elif self.button == "right": + code = (0b01000000 | (0b00000100 if self.state else 0)) + assert code, self + return b"\x13" + bytes([code]) + b"\x00\x00\x00" + class _MouseWheelEvent(NamedTuple): delta_y: int + def make_command(self) -> bytes: + delta_y = min(max(-128, self.delta_y), 127) + return b"\x14\x00" + struct.pack(">b", delta_y) + b"\x00\x00" + class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attributes - def __init__( + def __init__( # pylint: disable=too-many-arguments self, reset: int, + reset_delay: float, + device_path: str, speed: int, - reset_delay: float, + read_timeout: float, + read_retries: int, + common_retries: int, + retries_delay: float, + noop: bool, + + state_poll: float, ) -> None: super().__init__(daemon=True) self.__reset = gpio.set_output(reset) + self.__reset_delay = reset_delay + self.__device_path = device_path self.__speed = speed - self.__reset_delay = reset_delay + self.__read_timeout = read_timeout + self.__read_retries = read_retries + self.__common_retries = common_retries + self.__retries_delay = retries_delay + self.__noop = noop + + self.__state_poll = state_poll self.__pressed_keys: Set[str] = set() self.__pressed_mouse_buttons: Set[str] = set() self.__lock = asyncio.Lock() - self.__queue: multiprocessing.queues.Queue = multiprocessing.Queue() + self.__events_queue: multiprocessing.queues.Queue = multiprocessing.Queue() + + self.__ok_shared = multiprocessing.Value("i", 1) self.__stop_event = multiprocessing.Event() @@ -75,6 +127,14 @@ class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attribu get_logger().info("Starting HID daemon ...") super().start() + def get_state(self) -> Dict: + return {"ok": bool(self.__ok_shared.value)} + + async def poll_state(self) -> AsyncGenerator[Dict, None]: + while self.is_alive(): + yield self.get_state() + await asyncio.sleep(self.__state_poll) + async def reset(self) -> None: async with self.__lock: gpio.write(self.__reset, True) @@ -84,32 +144,34 @@ class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attribu async def send_key_event(self, key: str, state: bool) -> None: if not self.__stop_event.is_set(): async with self.__lock: - if state and key not in self.__pressed_keys: - self.__pressed_keys.add(key) - self.__queue.put(_KeyEvent(key, state)) - elif not state and key in self.__pressed_keys: - self.__pressed_keys.remove(key) - self.__queue.put(_KeyEvent(key, state)) + if _KeyEvent.is_valid(key): + if state and key not in self.__pressed_keys: + self.__pressed_keys.add(key) + self.__events_queue.put(_KeyEvent(key, state)) + elif not state and key in self.__pressed_keys: + self.__pressed_keys.remove(key) + self.__events_queue.put(_KeyEvent(key, state)) async def send_mouse_move_event(self, to_x: int, to_y: int) -> None: if not self.__stop_event.is_set(): async with self.__lock: - self.__queue.put(_MouseMoveEvent(to_x, to_y)) + self.__events_queue.put(_MouseMoveEvent(to_x, to_y)) async def send_mouse_button_event(self, button: str, state: bool) -> None: if not self.__stop_event.is_set(): async with self.__lock: - if state and button not in self.__pressed_mouse_buttons: - self.__pressed_mouse_buttons.add(button) - self.__queue.put(_MouseButtonEvent(button, state)) - elif not state and button in self.__pressed_mouse_buttons: - self.__pressed_mouse_buttons.remove(button) - self.__queue.put(_MouseButtonEvent(button, state)) + if _MouseButtonEvent.is_valid(button): + if state and button not in self.__pressed_mouse_buttons: + self.__pressed_mouse_buttons.add(button) + self.__events_queue.put(_MouseButtonEvent(button, state)) + elif not state and button in self.__pressed_mouse_buttons: + self.__pressed_mouse_buttons.remove(button) + self.__events_queue.put(_MouseButtonEvent(button, state)) async def send_mouse_wheel_event(self, delta_y: int) -> None: if not self.__stop_event.is_set(): async with self.__lock: - self.__queue.put(_MouseWheelEvent(delta_y)) + self.__events_queue.put(_MouseWheelEvent(delta_y)) async def clear_events(self) -> None: if not self.__stop_event.is_set(): @@ -120,7 +182,7 @@ class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attribu async with self.__lock: if self.is_alive(): self.__unsafe_clear_events() - get_logger().info("Stopping keyboard daemon ...") + get_logger().info("Stopping HID daemon ...") self.__stop_event.set() self.join() else: @@ -130,17 +192,17 @@ class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attribu def __unsafe_clear_events(self) -> None: for button in self.__pressed_mouse_buttons: - self.__queue.put(_MouseButtonEvent(button, False)) + self.__events_queue.put(_MouseButtonEvent(button, False)) self.__pressed_mouse_buttons.clear() for key in self.__pressed_keys: - self.__queue.put(_KeyEvent(key, False)) + self.__events_queue.put(_KeyEvent(key, False)) self.__pressed_keys.clear() def __emergency_clear_events(self) -> None: if os.path.exists(self.__device_path): try: - with serial.Serial(self.__device_path, self.__speed) as tty: - self.__send_clear_hid(tty) + with self.__get_serial() as tty: + self.__process_request(tty, b"\x10\x00\x00\x00\x00") except Exception: get_logger().exception("Can't execute emergency clear HID events") @@ -148,70 +210,102 @@ class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attribu signal.signal(signal.SIGINT, signal.SIG_IGN) setproctitle.setproctitle("[hid] " + setproctitle.getproctitle()) try: - with serial.Serial(self.__device_path, self.__speed) as tty: - hid_ready = False - while True: - if hid_ready: - try: - event = self.__queue.get(timeout=0.05) - except queue.Empty: - pass + with self.__get_serial() as tty: + passed = 0 + while not (self.__stop_event.is_set() and self.__events_queue.qsize() == 0): + try: + event = self.__events_queue.get(timeout=0.05) + except queue.Empty: + if passed >= 20: # 20 * 0.05 = 1 sec + self.__process_request(tty, b"\x01\x00\x00\x00\x00") # Ping + passed = 0 else: - if isinstance(event, _KeyEvent): - self.__send_key_event(tty, event) - elif isinstance(event, _MouseMoveEvent): - self.__send_mouse_move_event(tty, event) - elif isinstance(event, _MouseButtonEvent): - self.__send_mouse_button_event(tty, event) - elif isinstance(event, _MouseWheelEvent): - self.__send_mouse_wheel_event(tty, event) - else: - raise RuntimeError("Unknown HID event") - hid_ready = False - - if tty.in_waiting: - while tty.in_waiting: - tty.read(tty.in_waiting) - hid_ready = True + passed += 1 else: - time.sleep(0.05) - - if self.__stop_event.is_set() and self.__queue.qsize() == 0: - break + self.__process_request(tty, event.make_command()) + passed = 0 except Exception: get_logger().exception("Unhandled exception") raise - def __send_key_event(self, tty: serial.Serial, event: _KeyEvent) -> None: - code = _KEYMAP.get(event.key) - if code: - key_bytes = bytes([code]) - assert len(key_bytes) == 1, (event, key_bytes) - tty.write( - b"\01" - + key_bytes - + (b"\01" if event.state else b"\00") - + b"\00\00" - ) - - def __send_mouse_move_event(self, tty: serial.Serial, event: _MouseMoveEvent) -> None: - to_x = min(max(-32768, event.to_x), 32767) - to_y = min(max(-32768, event.to_y), 32767) - tty.write(b"\02" + struct.pack(">hh", to_x, to_y)) - - def __send_mouse_button_event(self, tty: serial.Serial, event: _MouseButtonEvent) -> None: - if event.button == "left": - code = (0b10000000 | (0b00001000 if event.state else 0)) - elif event.button == "right": - code = (0b01000000 | (0b00000100 if event.state else 0)) - else: - code = 0 - if code: - tty.write(b"\03" + bytes([code]) + b"\00\00\00") - - def __send_mouse_wheel_event(self, tty: serial.Serial, event: _MouseWheelEvent) -> None: - delta_y = min(max(-128, event.delta_y), 127) - tty.write(b"\04\00" + struct.pack(">b", delta_y) + b"\00\00") - - def __send_clear_hid(self, tty: serial.Serial) -> None: - tty.write(b"\00\00\00\00\00") + def __get_serial(self) -> serial.Serial: + return serial.Serial(self.__device_path, self.__speed, timeout=self.__read_timeout) + + def __process(self, tty: serial.Serial, command: bytes) -> None: + self.__process_request(tty, self.__make_request(command)) + + def __process_request(self, tty: serial.Serial, request: bytes) -> None: # pylint: disable=too-many-branches + logger = get_logger() + + common_retries = self.__common_retries + read_retries = self.__read_retries + error_occured = False + + while common_retries and read_retries: + if not self.__noop: + if tty.in_waiting: + tty.read(tty.in_waiting) + + assert tty.write(request) == len(request) + response = tty.read(4) + else: + response = b"\x33\x20" # Magic + OK + response += struct.pack(">H", self.__make_crc16(response)) + + if len(response) < 4: + logger.error("No response from HID: request=%r", request) + read_retries -= 1 + else: + assert len(response) == 4, response + if self.__make_crc16(response[-4:-2]) != struct.unpack(">H", response[-2:])[0]: + get_logger().error("Invalid response CRC; requesting response again ...") + request = self.__make_request(b"\x02\x00\x00\x00\x00") # Repeat an answer + else: + code = response[1] + if code == 0x48: # Request timeout + logger.error("Got request timeout from HID: request=%r", request) + elif code == 0x40: # CRC Error + logger.error("Got CRC error of request from HID: request=%r", request) + elif code == 0x45: # Unknown command + logger.error("HID did not recognize the request=%r", request) + self.__ok_shared.value = 1 + return + elif code == 0x24: # Rebooted? + logger.error("No previous command state inside HID, seems it was rebooted") + self.__ok_shared.value = 1 + return + elif code == 0x20: # Done + if error_occured: + logger.info("Success!") + self.__ok_shared.value = 1 + return + else: + logger.error("Invalid response from HID: request=%r; code=0x%x", request, code) + + common_retries -= 1 + error_occured = True + self.__ok_shared.value = 0 + + if common_retries and read_retries: + logger.error("Retries left: common_retries=%d; read_retries=%d", common_retries, read_retries) + time.sleep(self.__retries_delay) + + logger.error("Can't process HID request due many errors: %r", request) + + def __make_request(self, command: bytes) -> bytes: + request = b"\x33" + command + request += struct.pack(">H", self.__make_crc16(request)) + assert len(request) == 8, (request, command) + return request + + def __make_crc16(self, data: bytes) -> int: + crc = 0xFFFF + for byte in data: + crc = crc ^ byte + for _ in range(8): + if crc & 0x0001 == 0: + crc = crc >> 1 + else: + crc = crc >> 1 + crc = crc ^ 0xA001 + return crc diff --git a/kvmd/apps/kvmd/server.py b/kvmd/apps/kvmd/server.py index f3e7ddc8..37e6ca5b 100644 --- a/kvmd/apps/kvmd/server.py +++ b/kvmd/apps/kvmd/server.py @@ -144,6 +144,7 @@ def _system_task(method: Callable) -> Callable: async def wrap(self: "Server") -> None: try: await method(self) + raise RuntimeError("Dead system task: %s" % (method)) except asyncio.CancelledError: pass except Exception: @@ -201,6 +202,7 @@ def _valid_int(name: str, value: Optional[str], min_value: Optional[int]=None, m class _Events(Enum): INFO_STATE = "info_state" + HID_STATE = "hid_state" ATX_STATE = "atx_state" MSD_STATE = "msd_state" STREAMER_STATE = "streamer_state" @@ -357,6 +359,7 @@ class Server: # pylint: disable=too-many-instance-attributes await self.__register_socket(ws) await asyncio.gather(*[ self.__broadcast_event(_Events.INFO_STATE, (await self.__make_info())), + self.__broadcast_event(_Events.HID_STATE, self.__hid.get_state()), self.__broadcast_event(_Events.ATX_STATE, self.__atx.get_state()), self.__broadcast_event(_Events.MSD_STATE, self.__msd.get_state()), self.__broadcast_event(_Events.STREAMER_STATE, (await self.__streamer.get_state())), @@ -568,12 +571,6 @@ class Server: # pylint: disable=too-many-instance-attributes # ===== SYSTEM TASKS @_system_task - async def __hid_watchdog(self) -> None: - while self.__hid.is_alive(): - await asyncio.sleep(0.1) - raise RuntimeError("HID is dead") - - @_system_task async def __stream_controller(self) -> None: prev = 0 shutdown_at = 0.0 @@ -607,6 +604,11 @@ class Server: # pylint: disable=too-many-instance-attributes await asyncio.sleep(0.1) @_system_task + async def __poll_hid_state(self) -> None: + async for state in self.__hid.poll_state(): + await self.__broadcast_event(_Events.HID_STATE, state) + + @_system_task async def __poll_atx_state(self) -> None: async for state in self.__atx.poll_state(): await self.__broadcast_event(_Events.ATX_STATE, state) diff --git a/testenv/kvmd.yaml b/testenv/kvmd.yaml index b2c91c3e..1ca4e490 100644 --- a/testenv/kvmd.yaml +++ b/testenv/kvmd.yaml @@ -16,9 +16,17 @@ kvmd: pinout: reset: 4 + reset_delay: 0.1 + device: /dev/ttyS10 speed: 115200 - reset_delay: 0.1 + read_timeout: 2.0 + read_retries: 10 + common_retries: 100 + retries_delay: 0.1 + noop: true + + state_poll: 1.0 atx: pinout: @@ -36,7 +44,7 @@ kvmd: target: 12 reset: 13 - device: "/dev/kvmd-msd" + device: /dev/kvmd-msd init_delay: 2.0 reset_delay: 1.0 write_meta: true diff --git a/web/share/js/kvm/hid.js b/web/share/js/kvm/hid.js index 1c60b099..b6896955 100644 --- a/web/share/js/kvm/hid.js +++ b/web/share/js/kvm/hid.js @@ -62,6 +62,11 @@ function Hid() { __mouse.setSocket(ws); }; + self.setState = function(state) { + __keyboard.setState(state); + __mouse.setState(state); + }; + var __releaseAll = function() { __keyboard.releaseAll(); }; diff --git a/web/share/js/kvm/keyboard.js b/web/share/js/kvm/keyboard.js index c424c659..d5ccd80f 100644 --- a/web/share/js/kvm/keyboard.js +++ b/web/share/js/kvm/keyboard.js @@ -4,6 +4,7 @@ function Keyboard() { /********************************************************************************/ var __ws = null; + var __ok = true; var __keys = [].slice.call($$$("div#keyboard-desktop div.keyboard-block div.keyboard-row div.key")); var __modifiers = [].slice.call($$$("div#keyboard-desktop div.keyboard-block div.keyboard-row div.modifier")); @@ -53,6 +54,10 @@ function Keyboard() { __updateLeds(); }; + self.setState = function(state) { + __ok = state.ok; + }; + self.releaseAll = function() { __keys.concat(__modifiers).forEach(function(el_key) { if (__isActive(el_key)) { @@ -73,8 +78,13 @@ function Keyboard() { || $("keyboard-window").classList.contains("window-active") ) ) { - $("hid-keyboard-led").className = "led-green"; - $("hid-keyboard-led").title = "Keyboard captured"; + if (__ok) { + $("hid-keyboard-led").className = "led-green"; + $("hid-keyboard-led").title = "Keyboard captured"; + } else { + $("hid-keyboard-led").className = "led-yellow"; + $("hid-keyboard-led").title = "Keyboard captured, HID offline"; + } } else { $("hid-keyboard-led").className = "led-gray"; $("hid-keyboard-led").title = "Keyboard free"; diff --git a/web/share/js/kvm/mouse.js b/web/share/js/kvm/mouse.js index 59b3fc73..75836b1d 100644 --- a/web/share/js/kvm/mouse.js +++ b/web/share/js/kvm/mouse.js @@ -4,6 +4,7 @@ function Mouse() { /********************************************************************************/ var __ws = null; + var __ok = true; var __current_pos = {x: 0, y:0}; var __sent_pos = {x: 0, y:0}; @@ -44,6 +45,10 @@ function Mouse() { __updateLeds(); }; + self.setState = function(state) { + __ok = state.ok; + }; + var __hoverStream = function() { __stream_hovered = true; __updateLeds(); @@ -57,8 +62,13 @@ function Mouse() { var __updateLeds = function() { if (__ws && (__stream_hovered || tools.browser.is_ios)) { // Mouse is always available on iOS via touchscreen - $("hid-mouse-led").className = "led-green"; - $("hid-mouse-led").title = "Mouse tracked"; + if (__ok) { + $("hid-mouse-led").className = "led-green"; + $("hid-mouse-led").title = "Mouse tracked"; + } else { + $("hid-mouse-led").className = "led-yellow"; + $("hid-mouse-led").title = "Mouse tracked, HID offline"; + } } else { $("hid-mouse-led").className = "led-gray"; $("hid-mouse-led").title = "Mouse free"; diff --git a/web/share/js/kvm/session.js b/web/share/js/kvm/session.js index 95f8fd88..7f2100bd 100644 --- a/web/share/js/kvm/session.js +++ b/web/share/js/kvm/session.js @@ -84,6 +84,8 @@ function Session() { } else if (event.msg_type === "event") { if (event.msg.event === "info_state") { __setKvmdInfo(event.msg.event_attrs); + } else if (event.msg.event === "hid_state") { + __hid.setState(event.msg.event_attrs); } else if (event.msg.event === "atx_state") { __atx.setState(event.msg.event_attrs); } else if (event.msg.event === "msd_state") { |