new hid protocol with crc

This commit is contained in:
Devaev Maxim 2019-02-07 05:45:36 +03:00
parent 5bec2ff144
commit de1bed956c
12 changed files with 388 additions and 156 deletions

View File

@ -19,9 +19,16 @@ kvmd:
pinout: pinout:
reset: 4 reset: 4
reset_delay: 0.1
device: "/dev/kvmd-hid" device: "/dev/kvmd-hid"
speed: 115200 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: atx:
pinout: pinout:

View File

@ -19,9 +19,16 @@ kvmd:
pinout: pinout:
reset: 4 reset: 4
reset_delay: 0.1
device: "/dev/kvmd-hid" device: "/dev/kvmd-hid"
speed: 115200 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: atx:
pinout: pinout:

View File

@ -17,3 +17,4 @@ monitor_speed = 115200
lib_deps = lib_deps =
HID-Project@2.4.4 HID-Project@2.4.4
TimerOne@1.1

View File

@ -1,5 +1,6 @@
#include <Arduino.h> #include <Arduino.h>
#include <HID-Project.h> #include <HID-Project.h>
#include <TimerOne.h>
#include "inline.h" #include "inline.h"
#include "keymap.h" #include "keymap.h"
@ -7,35 +8,42 @@
#define CMD_SERIAL Serial1 #define CMD_SERIAL Serial1
#define CMD_SERIAL_SPEED 115200 #define CMD_SERIAL_SPEED 115200
#define CMD_RECV_TIMEOUT 100000
#define CMD_MOUSE_LEFT 0b10000000 #define PROTO_MAGIC 0x33
#define CMD_MOUSE_LEFT_STATE 0b00001000 #define PROTO_CRC_POLINOM 0xA001
#define CMD_MOUSE_RIGHT 0b01000000 // -----------------------------------------
#define CMD_MOUSE_RIGHT_STATE 0b00000100 #define PROTO_RESP_OK 0x20
#define PROTO_RESP_NONE 0x24
#define REPORT_INTERVAL 100 #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() { INLINE void cmdResetHid(const uint8_t *buffer) { // 0 bytes
for (int count = 0; count < 4; ++count) {
CMD_SERIAL.read();
}
}
INLINE void cmdResetHid() { // 0 bytes
readNoop();
BootKeyboard.releaseAll(); BootKeyboard.releaseAll();
SingleAbsoluteMouse.releaseAll(); SingleAbsoluteMouse.releaseAll();
} }
INLINE void cmdKeyEvent() { // 2 bytes INLINE void cmdKeyEvent(const uint8_t *buffer) { // 2 bytes
KeyboardKeycode code = keymap((uint8_t)CMD_SERIAL.read()); KeyboardKeycode code = keymap(buffer[0]);
uint8_t state = CMD_SERIAL.read();
CMD_SERIAL.read(); // unused
CMD_SERIAL.read(); // unused
if (code != KEY_ERROR_UNDEFINED) { if (code != KEY_ERROR_UNDEFINED) {
if (state) { if (buffer[1]) {
BootKeyboard.press(code); BootKeyboard.press(code);
} else { } else {
BootKeyboard.release(code); BootKeyboard.release(code);
@ -43,28 +51,29 @@ INLINE void cmdKeyEvent() { // 2 bytes
} }
} }
INLINE void cmdMouseMoveEvent() { // 4 bytes INLINE void cmdMouseMoveEvent(const uint8_t *buffer) { // 4 bytes
int x = (int)CMD_SERIAL.read() << 8; int x = (int)buffer[0] << 8;
x |= (int)CMD_SERIAL.read(); x |= (int)buffer[1];
int y = (int)CMD_SERIAL.read() << 8;
y |= (int)CMD_SERIAL.read(); int y = (int)buffer[2] << 8;
y |= (int)buffer[3];
SingleAbsoluteMouse.moveTo(x, y); SingleAbsoluteMouse.moveTo(x, y);
} }
INLINE void cmdMouseButtonEvent() { // 1 byte INLINE void cmdMouseButtonEvent(const uint8_t *buffer) { // 1 byte
uint8_t state = CMD_SERIAL.read(); uint8_t state = buffer[0];
CMD_SERIAL.read(); // unused
CMD_SERIAL.read(); // unused if (state & PROTO_CMD_MOUSE_BUTTON_LEFT_SELECT) {
CMD_SERIAL.read(); // unused if (state & PROTO_CMD_MOUSE_BUTTON_LEFT_STATE) {
if (state & CMD_MOUSE_LEFT) {
if (state & CMD_MOUSE_LEFT_STATE) {
SingleAbsoluteMouse.press(MOUSE_LEFT); SingleAbsoluteMouse.press(MOUSE_LEFT);
} else { } else {
SingleAbsoluteMouse.release(MOUSE_LEFT); 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); SingleAbsoluteMouse.press(MOUSE_RIGHT);
} else { } else {
SingleAbsoluteMouse.release(MOUSE_RIGHT); SingleAbsoluteMouse.release(MOUSE_RIGHT);
@ -72,45 +81,113 @@ INLINE void cmdMouseButtonEvent() { // 1 byte
} }
} }
INLINE void cmdMouseWheelEvent() { // 2 bytes INLINE void cmdMouseWheelEvent(const uint8_t *buffer) { // 2 bytes
CMD_SERIAL.read(); // delta_x is not supported by hid-project now // delta_x is not supported by hid-project now
signed char delta_y = CMD_SERIAL.read(); signed char delta_y = buffer[1];
CMD_SERIAL.read(); // unused
CMD_SERIAL.read(); // unused
SingleAbsoluteMouse.move(0, 0, delta_y); 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() { void setup() {
CMD_SERIAL.begin(CMD_SERIAL_SPEED);
BootKeyboard.begin(); BootKeyboard.begin();
SingleAbsoluteMouse.begin(); SingleAbsoluteMouse.begin();
Timer1.attachInterrupt(intRecvTimedOut);
CMD_SERIAL.begin(CMD_SERIAL_SPEED);
} }
void loop() { void loop() {
static unsigned long last_report = 0; uint8_t buffer[8];
bool cmd_processed = false; unsigned index = 0;
if (CMD_SERIAL.available() >= 5) { while (true) {
switch ((uint8_t)CMD_SERIAL.read()) { if (CMD_SERIAL.available() > 0) {
case 0: cmdResetHid(); break; buffer[index] = (uint8_t)CMD_SERIAL.read();
case 1: cmdKeyEvent(); break; if (index == 7) {
case 2: cmdMouseMoveEvent(); break; uint16_t crc = (uint16_t)buffer[6] << 8;
case 3: cmdMouseButtonEvent(); break; crc |= (uint16_t)buffer[7];
case 4: cmdMouseWheelEvent(); break;
default: readNoop(); break;
}
cmd_processed = true;
}
unsigned long now = millis(); if (makeCrc16(buffer, 6) == crc) {
if ( # define HANDLE(_handler) { _handler(buffer + 2); sendCmdResponse(PROTO_RESP_OK); break; }
cmd_processed switch (buffer[1]) {
|| (now >= last_report && now - last_report >= REPORT_INTERVAL) case PROTO_CMD_RESET_HID: HANDLE(cmdResetHid);
|| (now < last_report && ((unsigned long) -1) - last_report + now >= REPORT_INTERVAL) case PROTO_CMD_KEY_EVENT: HANDLE(cmdKeyEvent);
) { case PROTO_CMD_MOUSE_MOVE_EVENT: HANDLE(cmdMouseMoveEvent);
CMD_SERIAL.write(0); case PROTO_CMD_MOUSE_BUTTON_EVENT: HANDLE(cmdMouseButtonEvent);
last_report = now; 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;
}
} }
} }

View File

@ -35,9 +35,18 @@ def main() -> None:
hid = Hid( hid = Hid(
reset=int(config["hid"]["pinout"]["reset"]), reset=int(config["hid"]["pinout"]["reset"]),
reset_delay=float(config["hid"]["reset_delay"]),
device_path=str(config["hid"]["device"]), device_path=str(config["hid"]["device"]),
speed=int(config["hid"]["speed"]), 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( atx = Atx(

View File

@ -11,6 +11,7 @@ import time
from typing import Dict from typing import Dict
from typing import Set from typing import Set
from typing import NamedTuple from typing import NamedTuple
from typing import AsyncGenerator
import yaml import yaml
import serial import serial
@ -33,41 +34,92 @@ class _KeyEvent(NamedTuple):
key: str key: str
state: bool 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): class _MouseMoveEvent(NamedTuple):
to_x: int to_x: int
to_y: 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): class _MouseButtonEvent(NamedTuple):
button: str button: str
state: bool 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): class _MouseWheelEvent(NamedTuple):
delta_y: int 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 class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attributes
def __init__( def __init__( # pylint: disable=too-many-arguments
self, self,
reset: int, reset: int,
reset_delay: float,
device_path: str, device_path: str,
speed: int, speed: int,
reset_delay: float, read_timeout: float,
read_retries: int,
common_retries: int,
retries_delay: float,
noop: bool,
state_poll: float,
) -> None: ) -> None:
super().__init__(daemon=True) super().__init__(daemon=True)
self.__reset = gpio.set_output(reset) self.__reset = gpio.set_output(reset)
self.__reset_delay = reset_delay
self.__device_path = device_path self.__device_path = device_path
self.__speed = speed 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_keys: Set[str] = set()
self.__pressed_mouse_buttons: Set[str] = set() self.__pressed_mouse_buttons: Set[str] = set()
self.__lock = asyncio.Lock() 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() 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 ...") get_logger().info("Starting HID daemon ...")
super().start() 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 def reset(self) -> None:
async with self.__lock: async with self.__lock:
gpio.write(self.__reset, True) 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: async def send_key_event(self, key: str, state: bool) -> None:
if not self.__stop_event.is_set(): if not self.__stop_event.is_set():
async with self.__lock: async with self.__lock:
if _KeyEvent.is_valid(key):
if state and key not in self.__pressed_keys: if state and key not in self.__pressed_keys:
self.__pressed_keys.add(key) self.__pressed_keys.add(key)
self.__queue.put(_KeyEvent(key, state)) self.__events_queue.put(_KeyEvent(key, state))
elif not state and key in self.__pressed_keys: elif not state and key in self.__pressed_keys:
self.__pressed_keys.remove(key) self.__pressed_keys.remove(key)
self.__queue.put(_KeyEvent(key, state)) self.__events_queue.put(_KeyEvent(key, state))
async def send_mouse_move_event(self, to_x: int, to_y: int) -> None: async def send_mouse_move_event(self, to_x: int, to_y: int) -> None:
if not self.__stop_event.is_set(): if not self.__stop_event.is_set():
async with self.__lock: 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: async def send_mouse_button_event(self, button: str, state: bool) -> None:
if not self.__stop_event.is_set(): if not self.__stop_event.is_set():
async with self.__lock: async with self.__lock:
if _MouseButtonEvent.is_valid(button):
if state and button not in self.__pressed_mouse_buttons: if state and button not in self.__pressed_mouse_buttons:
self.__pressed_mouse_buttons.add(button) self.__pressed_mouse_buttons.add(button)
self.__queue.put(_MouseButtonEvent(button, state)) self.__events_queue.put(_MouseButtonEvent(button, state))
elif not state and button in self.__pressed_mouse_buttons: elif not state and button in self.__pressed_mouse_buttons:
self.__pressed_mouse_buttons.remove(button) self.__pressed_mouse_buttons.remove(button)
self.__queue.put(_MouseButtonEvent(button, state)) self.__events_queue.put(_MouseButtonEvent(button, state))
async def send_mouse_wheel_event(self, delta_y: int) -> None: async def send_mouse_wheel_event(self, delta_y: int) -> None:
if not self.__stop_event.is_set(): if not self.__stop_event.is_set():
async with self.__lock: async with self.__lock:
self.__queue.put(_MouseWheelEvent(delta_y)) self.__events_queue.put(_MouseWheelEvent(delta_y))
async def clear_events(self) -> None: async def clear_events(self) -> None:
if not self.__stop_event.is_set(): 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: async with self.__lock:
if self.is_alive(): if self.is_alive():
self.__unsafe_clear_events() self.__unsafe_clear_events()
get_logger().info("Stopping keyboard daemon ...") get_logger().info("Stopping HID daemon ...")
self.__stop_event.set() self.__stop_event.set()
self.join() self.join()
else: else:
@ -130,17 +192,17 @@ class Hid(multiprocessing.Process): # pylint: disable=too-many-instance-attribu
def __unsafe_clear_events(self) -> None: def __unsafe_clear_events(self) -> None:
for button in self.__pressed_mouse_buttons: 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() self.__pressed_mouse_buttons.clear()
for key in self.__pressed_keys: for key in self.__pressed_keys:
self.__queue.put(_KeyEvent(key, False)) self.__events_queue.put(_KeyEvent(key, False))
self.__pressed_keys.clear() self.__pressed_keys.clear()
def __emergency_clear_events(self) -> None: def __emergency_clear_events(self) -> None:
if os.path.exists(self.__device_path): if os.path.exists(self.__device_path):
try: try:
with serial.Serial(self.__device_path, self.__speed) as tty: with self.__get_serial() as tty:
self.__send_clear_hid(tty) self.__process_request(tty, b"\x10\x00\x00\x00\x00")
except Exception: except Exception:
get_logger().exception("Can't execute emergency clear HID events") 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) signal.signal(signal.SIGINT, signal.SIG_IGN)
setproctitle.setproctitle("[hid] " + setproctitle.getproctitle()) setproctitle.setproctitle("[hid] " + setproctitle.getproctitle())
try: try:
with serial.Serial(self.__device_path, self.__speed) as tty: with self.__get_serial() as tty:
hid_ready = False passed = 0
while True: while not (self.__stop_event.is_set() and self.__events_queue.qsize() == 0):
if hid_ready:
try: try:
event = self.__queue.get(timeout=0.05) event = self.__events_queue.get(timeout=0.05)
except queue.Empty: except queue.Empty:
pass if passed >= 20: # 20 * 0.05 = 1 sec
self.__process_request(tty, b"\x01\x00\x00\x00\x00") # Ping
passed = 0
else: else:
if isinstance(event, _KeyEvent): passed += 1
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: else:
raise RuntimeError("Unknown HID event") self.__process_request(tty, event.make_command())
hid_ready = False passed = 0
if tty.in_waiting:
while tty.in_waiting:
tty.read(tty.in_waiting)
hid_ready = True
else:
time.sleep(0.05)
if self.__stop_event.is_set() and self.__queue.qsize() == 0:
break
except Exception: except Exception:
get_logger().exception("Unhandled exception") get_logger().exception("Unhandled exception")
raise raise
def __send_key_event(self, tty: serial.Serial, event: _KeyEvent) -> None: def __get_serial(self) -> serial.Serial:
code = _KEYMAP.get(event.key) return serial.Serial(self.__device_path, self.__speed, timeout=self.__read_timeout)
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: def __process(self, tty: serial.Serial, command: bytes) -> None:
to_x = min(max(-32768, event.to_x), 32767) self.__process_request(tty, self.__make_request(command))
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: def __process_request(self, tty: serial.Serial, request: bytes) -> None: # pylint: disable=too-many-branches
if event.button == "left": logger = get_logger()
code = (0b10000000 | (0b00001000 if event.state else 0))
elif event.button == "right": common_retries = self.__common_retries
code = (0b01000000 | (0b00000100 if event.state else 0)) 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: else:
code = 0 response = b"\x33\x20" # Magic + OK
if code: response += struct.pack(">H", self.__make_crc16(response))
tty.write(b"\03" + bytes([code]) + b"\00\00\00")
def __send_mouse_wheel_event(self, tty: serial.Serial, event: _MouseWheelEvent) -> None: if len(response) < 4:
delta_y = min(max(-128, event.delta_y), 127) logger.error("No response from HID: request=%r", request)
tty.write(b"\04\00" + struct.pack(">b", delta_y) + b"\00\00") 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)
def __send_clear_hid(self, tty: serial.Serial) -> None: common_retries -= 1
tty.write(b"\00\00\00\00\00") 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

View File

@ -144,6 +144,7 @@ def _system_task(method: Callable) -> Callable:
async def wrap(self: "Server") -> None: async def wrap(self: "Server") -> None:
try: try:
await method(self) await method(self)
raise RuntimeError("Dead system task: %s" % (method))
except asyncio.CancelledError: except asyncio.CancelledError:
pass pass
except Exception: except Exception:
@ -201,6 +202,7 @@ def _valid_int(name: str, value: Optional[str], min_value: Optional[int]=None, m
class _Events(Enum): class _Events(Enum):
INFO_STATE = "info_state" INFO_STATE = "info_state"
HID_STATE = "hid_state"
ATX_STATE = "atx_state" ATX_STATE = "atx_state"
MSD_STATE = "msd_state" MSD_STATE = "msd_state"
STREAMER_STATE = "streamer_state" STREAMER_STATE = "streamer_state"
@ -357,6 +359,7 @@ class Server: # pylint: disable=too-many-instance-attributes
await self.__register_socket(ws) await self.__register_socket(ws)
await asyncio.gather(*[ await asyncio.gather(*[
self.__broadcast_event(_Events.INFO_STATE, (await self.__make_info())), 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.ATX_STATE, self.__atx.get_state()),
self.__broadcast_event(_Events.MSD_STATE, self.__msd.get_state()), self.__broadcast_event(_Events.MSD_STATE, self.__msd.get_state()),
self.__broadcast_event(_Events.STREAMER_STATE, (await self.__streamer.get_state())), self.__broadcast_event(_Events.STREAMER_STATE, (await self.__streamer.get_state())),
@ -567,12 +570,6 @@ class Server: # pylint: disable=too-many-instance-attributes
# ===== SYSTEM TASKS # ===== 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 @_system_task
async def __stream_controller(self) -> None: async def __stream_controller(self) -> None:
prev = 0 prev = 0
@ -606,6 +603,11 @@ class Server: # pylint: disable=too-many-instance-attributes
await self.__remove_socket(ws) await self.__remove_socket(ws)
await asyncio.sleep(0.1) 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 @_system_task
async def __poll_atx_state(self) -> None: async def __poll_atx_state(self) -> None:
async for state in self.__atx.poll_state(): async for state in self.__atx.poll_state():

View File

@ -16,9 +16,17 @@ kvmd:
pinout: pinout:
reset: 4 reset: 4
reset_delay: 0.1
device: /dev/ttyS10 device: /dev/ttyS10
speed: 115200 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: atx:
pinout: pinout:
@ -36,7 +44,7 @@ kvmd:
target: 12 target: 12
reset: 13 reset: 13
device: "/dev/kvmd-msd" device: /dev/kvmd-msd
init_delay: 2.0 init_delay: 2.0
reset_delay: 1.0 reset_delay: 1.0
write_meta: true write_meta: true

View File

@ -62,6 +62,11 @@ function Hid() {
__mouse.setSocket(ws); __mouse.setSocket(ws);
}; };
self.setState = function(state) {
__keyboard.setState(state);
__mouse.setState(state);
};
var __releaseAll = function() { var __releaseAll = function() {
__keyboard.releaseAll(); __keyboard.releaseAll();
}; };

View File

@ -4,6 +4,7 @@ function Keyboard() {
/********************************************************************************/ /********************************************************************************/
var __ws = null; var __ws = null;
var __ok = true;
var __keys = [].slice.call($$$("div#keyboard-desktop div.keyboard-block div.keyboard-row div.key")); 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")); var __modifiers = [].slice.call($$$("div#keyboard-desktop div.keyboard-block div.keyboard-row div.modifier"));
@ -53,6 +54,10 @@ function Keyboard() {
__updateLeds(); __updateLeds();
}; };
self.setState = function(state) {
__ok = state.ok;
};
self.releaseAll = function() { self.releaseAll = function() {
__keys.concat(__modifiers).forEach(function(el_key) { __keys.concat(__modifiers).forEach(function(el_key) {
if (__isActive(el_key)) { if (__isActive(el_key)) {
@ -73,8 +78,13 @@ function Keyboard() {
|| $("keyboard-window").classList.contains("window-active") || $("keyboard-window").classList.contains("window-active")
) )
) { ) {
if (__ok) {
$("hid-keyboard-led").className = "led-green"; $("hid-keyboard-led").className = "led-green";
$("hid-keyboard-led").title = "Keyboard captured"; $("hid-keyboard-led").title = "Keyboard captured";
} else {
$("hid-keyboard-led").className = "led-yellow";
$("hid-keyboard-led").title = "Keyboard captured, HID offline";
}
} else { } else {
$("hid-keyboard-led").className = "led-gray"; $("hid-keyboard-led").className = "led-gray";
$("hid-keyboard-led").title = "Keyboard free"; $("hid-keyboard-led").title = "Keyboard free";

View File

@ -4,6 +4,7 @@ function Mouse() {
/********************************************************************************/ /********************************************************************************/
var __ws = null; var __ws = null;
var __ok = true;
var __current_pos = {x: 0, y:0}; var __current_pos = {x: 0, y:0};
var __sent_pos = {x: 0, y:0}; var __sent_pos = {x: 0, y:0};
@ -44,6 +45,10 @@ function Mouse() {
__updateLeds(); __updateLeds();
}; };
self.setState = function(state) {
__ok = state.ok;
};
var __hoverStream = function() { var __hoverStream = function() {
__stream_hovered = true; __stream_hovered = true;
__updateLeds(); __updateLeds();
@ -57,8 +62,13 @@ function Mouse() {
var __updateLeds = function() { var __updateLeds = function() {
if (__ws && (__stream_hovered || tools.browser.is_ios)) { if (__ws && (__stream_hovered || tools.browser.is_ios)) {
// Mouse is always available on iOS via touchscreen // Mouse is always available on iOS via touchscreen
if (__ok) {
$("hid-mouse-led").className = "led-green"; $("hid-mouse-led").className = "led-green";
$("hid-mouse-led").title = "Mouse tracked"; $("hid-mouse-led").title = "Mouse tracked";
} else {
$("hid-mouse-led").className = "led-yellow";
$("hid-mouse-led").title = "Mouse tracked, HID offline";
}
} else { } else {
$("hid-mouse-led").className = "led-gray"; $("hid-mouse-led").className = "led-gray";
$("hid-mouse-led").title = "Mouse free"; $("hid-mouse-led").title = "Mouse free";

View File

@ -84,6 +84,8 @@ function Session() {
} else if (event.msg_type === "event") { } else if (event.msg_type === "event") {
if (event.msg.event === "info_state") { if (event.msg.event === "info_state") {
__setKvmdInfo(event.msg.event_attrs); __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") { } else if (event.msg.event === "atx_state") {
__atx.setState(event.msg.event_attrs); __atx.setState(event.msg.event_attrs);
} else if (event.msg.event === "msd_state") { } else if (event.msg.event === "msd_state") {