Keyboard Adapter
Notes¶
A concrete InputAdapter that recieves browser key events forwarded over a WebSocket connection and maps them to appropriate Command objects
The drone SDK runs in the python process, meaning the browser cannot talk to it directly. The react frontend will thus capture raw keydown/up events and forwards them as a JSON object through a WebSocket to FastAPI. This adapter receives those messages and converts them into Commands that can be used by the DroneAdapter.
This means that this adapter is not a standard keyboard listener like was implemented as a proof of concept. Instead, it purely translates a message shape to a command.
Message format:
Currently we only consider keydown events. keyup is recieved and ignored for now, but its included such that we can eventually support continuous movement without changing any endpoints or parsing, only this file would be changed.
Key Mapping¶
KEY_MAP defines the mapping between browser keys and CommandType values.
Keymapping:
The KEY_MAP dict is the single source of truth. currently custom keybinds are not supported, however it can be added at a later stage.
Arrow keys : directional movement (forward/back/left/right)
W / S : altitude up / down
A / D : rotate counter-clockwise / clockwise
T : takeoff
L : land
Spacebar : hover (cancel movement)
Escape : EMERGENCY_STOP
Example Usage¶
Usage: FastAPI WebSockets
# intialisation
adapter = KeyboardAdapter()
adapter.set_handler(lambda cmd: asyncio.create_task(drone.execute(cmd)))
await adapter.start()
@app.websocket("/ws/keyboard")
async def ws_keyboard(ws: WebSocket):
await ws.accept()
while True:
msg = await ws.receive_json()
adapter.handle_message(msg)
Usage: PyTest unit testing:
received: list[Command] = []
adapter = KeyboardAdapter()
adapter.set_handler(received.append)
adapter.handle_message({"key": "t", "event": "keydown"})
assert received[0].type == CommandType.TAKEOFF
KeyboardAdapter Class¶
Inheritance¶
- InputAdapter
Lifecycle¶
start() -> None¶
Initialises the adapter.
Behavior: - no listeners are created - no background tasks are started - logs readiness for WebSocket input
Core Method¶
handle_message(message: dict[str, Any]) -> None¶
Processes a single keyboard event message from the frontend.
Parameters¶
- message: dict
- key: str (KeyboardEvent.key value)
- event: str ("keydown" | "keyup")
Processing logic¶
- Extract key and event
- Ignore non-keydown events
- Lookup key in KEY_MAP
- If no mapping exists, ignore
- Create Command
- Emit via _emit()
Ignored inputs¶
- keyup events
- unmapped keys
- malformed or irrelevant inputs
These are silently ignored to avoid noise from browser shortcuts and system keys.
Command emission¶
_emit(command: Command)¶
Delegates to InputAdapter._emit().
This routes the command to the registered handler, typically:
- DroneAdapter.execute()
Utility¶
get_bindings() -> dict[str, str]¶
Returns a readable mapping of keyboard keys to command names.