From 784d594a3b2e254b611fde39aae9af7387f69be2 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Wed, 22 Oct 2025 22:17:05 +0200 Subject: [PATCH] feat: add Command classes --- src/command.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/command.py diff --git a/src/command.py b/src/command.py new file mode 100644 index 0000000..aa54d69 --- /dev/null +++ b/src/command.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +import abc +from enum import IntEnum +import struct +from typing import Type + + +class CommandType(IntEnum): + CAR_CONTROL = 0 + + +class CarControl(IntEnum): + FORWARD = 0 + BACKWARD = 1 + LEFT = 2 + RIGHT = 3 + + +class Command(abc.ABC): + TYPE: CommandType + REGISTRY: dict[CommandType, Type[Command]] = {} + + def __init_subclass__(cls) -> None: + super().__init_subclass__() + if cls.TYPE in Command.REGISTRY: + raise ValueError( + f"Command type {cls.TYPE} already registered by {Command.REGISTRY[cls.TYPE]}" + ) + Command.REGISTRY[cls.TYPE] = cls + + @abc.abstractmethod + def get_payload(self) -> bytes: ... + + def pack(self) -> bytes: + payload: bytes = self.get_payload() + return struct.pack(">B", self.TYPE) + payload + + @staticmethod + def unpack(data: bytes) -> Command: + type: CommandType = CommandType(data[0]) + return Command.REGISTRY[type].from_payload(data[1:]) + + @classmethod + @abc.abstractmethod + def from_payload(cls, payload: bytes) -> Command: ... + + +class ControlCommand(Command): + TYPE = CommandType.CAR_CONTROL + __match_args__ = ("control", "active") + + def __init__(self, control: CarControl, active: bool) -> None: + super().__init__() + self.control: CarControl = control + self.active: bool = active + + def get_payload(self) -> bytes: + return struct.pack(">B", (self.control << 1) | self.active) + + @classmethod + def from_payload(cls, payload: bytes) -> Command: + value: int = payload[0] + active: bool = (value & 1) == 1 + control: int = value >> 1 + return ControlCommand(CarControl(control), active)