moar keyboard

This commit is contained in:
Devaev Maxim 2018-07-09 07:45:00 +00:00
parent dec9aedb19
commit 670be54348
3 changed files with 68 additions and 26 deletions

View File

@ -1,14 +1,29 @@
import asyncio
import multiprocessing import multiprocessing
import multiprocessing.queues import multiprocessing.queues
import queue import queue
import time import time
from typing import List
from typing import Set
from typing import NamedTuple
from .logging import get_logger from .logging import get_logger
from . import gpio from . import gpio
# ===== # =====
class _KeyEvent(NamedTuple):
key: str
state: bool
def _key_event_to_ps2_codes(event: _KeyEvent) -> List[int]:
get_logger().info(str(event))
return [] # TODO
class Ps2Keyboard(multiprocessing.Process): class Ps2Keyboard(multiprocessing.Process):
def __init__(self, clock: int, data: int, pulse: float) -> None: def __init__(self, clock: int, data: int, pulse: float) -> None:
super().__init__(daemon=True) super().__init__(daemon=True)
@ -17,39 +32,65 @@ class Ps2Keyboard(multiprocessing.Process):
self.__data = gpio.set_output(data, initial=True) self.__data = gpio.set_output(data, initial=True)
self.__pulse = pulse self.__pulse = pulse
self.__pressed_keys: Set[str] = set()
self.__lock = asyncio.Lock()
self.__queue: multiprocessing.queues.Queue = multiprocessing.Queue() self.__queue: multiprocessing.queues.Queue = multiprocessing.Queue()
self.__event = multiprocessing.Event()
self.__stop_event = multiprocessing.Event()
def start(self) -> None: def start(self) -> None:
get_logger().info("Starting keyboard daemon ...") get_logger().info("Starting keyboard daemon ...")
super().start() super().start()
def stop(self) -> None: async def send_event(self, key: str, state: bool) -> None:
get_logger().info("Stopping keyboard daemon ...") if not self.__stop_event.is_set():
self.__event.set() async with self.__lock:
self.join() 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))
def send_event(self, code: str, state: bool) -> None: async def clear_events(self) -> None:
if state: if not self.__stop_event.is_set():
get_logger().info("Key pressed: %s", code) async with self.__lock:
else: self.__unsafe_clear_events()
get_logger().info("Key released: %s", code)
# TODO: self.__queue.put(code)
def cleanup(self) -> None: async def cleanup(self) -> None:
if self.is_alive(): async with self.__lock:
self.stop() if self.is_alive():
self.__unsafe_clear_events()
get_logger().info("Stopping keyboard daemon ...")
self.__stop_event.set()
self.join()
else:
get_logger().warning("Emergency cleaning up keyboard events ...")
self.__emergency_clear_events()
def __unsafe_clear_events(self) -> None:
for key in self.__pressed_keys:
self.__queue.put(_KeyEvent(key, False))
self.__pressed_keys.clear()
def __emergency_clear_events(self) -> None:
for key in self.__pressed_keys:
for code in _key_event_to_ps2_codes(_KeyEvent(key, False)):
self.__send_byte(code)
def run(self) -> None: def run(self) -> None:
with gpio.bcm(): with gpio.bcm():
try: try:
while not self.__event.is_set(): while True:
try: try:
code = self.__queue.get(timeout=0.1) event = self.__queue.get(timeout=0.1)
except queue.Empty: except queue.Empty:
pass pass
else: else:
self.__send_byte(code) for code in _key_event_to_ps2_codes(event):
self.__send_byte(code)
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

View File

@ -135,11 +135,11 @@ class Server: # pylint: disable=too-many-instance-attributes
except Exception: except Exception:
logger.exception("Can't parse JSON event from websocket") logger.exception("Can't parse JSON event from websocket")
else: else:
if event.get("event_type") == "key_event": if event.get("event_type") == "key":
key_code = str(event.get("key_code", ""))[:64].strip() key = str(event.get("key", ""))[:64].strip()
key_state = event.get("key_state") state = event.get("state")
if key_code and key_state in [True, False]: if key and state in [True, False]:
self.__keyboard.send_event(key_code, key_state) await self.__keyboard.send_event(key, state)
continue continue
else: else:
logger.error("Invalid websocket event: %r", event) logger.error("Invalid websocket event: %r", event)
@ -236,7 +236,7 @@ class Server: # pylint: disable=too-many-instance-attributes
await self.__remove_socket(ws) await self.__remove_socket(ws)
async def __on_cleanup(self, _: aiohttp.web.Application) -> None: async def __on_cleanup(self, _: aiohttp.web.Application) -> None:
self.__keyboard.cleanup() await self.__keyboard.cleanup()
await self.__streamer.cleanup() await self.__streamer.cleanup()
await self.__msd.cleanup() await self.__msd.cleanup()
@ -307,6 +307,7 @@ class Server: # pylint: disable=too-many-instance-attributes
async def __remove_socket(self, ws: aiohttp.web.WebSocketResponse) -> None: async def __remove_socket(self, ws: aiohttp.web.WebSocketResponse) -> None:
async with self.__sockets_lock: async with self.__sockets_lock:
await self.__keyboard.clear_events()
try: try:
self.__sockets.remove(ws) self.__sockets.remove(ws)
get_logger().info("Removed client socket: remote=%s; id=%d; active=%d", get_logger().info("Removed client socket: remote=%s; id=%d; active=%d",

View File

@ -42,9 +42,9 @@ function onKeyEvent(event, state) {
// TODO: run this code under the lock // TODO: run this code under the lock
console.log("Key", (state ? "pressed:" : "released:"), event) console.log("Key", (state ? "pressed:" : "released:"), event)
ws.send(JSON.stringify({ ws.send(JSON.stringify({
event_type: "key_event", event_type: "key",
key_code: event.code, key: event.code,
key_state: state, state: state,
})); }));
} }