2022-04-15 20:02:42 +02:00
|
|
|
from dataclasses import asdict, dataclass, field, fields, is_dataclass
|
|
|
|
from enum import IntEnum, unique
|
|
|
|
from typing import TypeVar
|
2022-04-04 14:48:43 +02:00
|
|
|
|
2022-04-15 20:02:42 +02:00
|
|
|
from c3nav.mesh.dataformats import (BoolFormat, FixedStrFormat, HexFormat, LedConfig, LedConfigFormat,
|
|
|
|
MacAddressesListFormat, MacAddressFormat, SimpleFormat, VarStrFormat)
|
2022-04-04 14:48:43 +02:00
|
|
|
|
2022-04-15 20:02:42 +02:00
|
|
|
ROOT_ADDRESS = '00:00:00:00:00:00'
|
|
|
|
PARENT_ADDRESS = '00:00:00:ff:ff:ff'
|
|
|
|
NO_LAYER = 0xFF
|
2022-04-06 23:44:24 +02:00
|
|
|
|
|
|
|
|
2022-04-15 20:02:42 +02:00
|
|
|
@unique
|
|
|
|
class MessageType(IntEnum):
|
|
|
|
ECHO_REQUEST = 0x01
|
|
|
|
ECHO_RESPONSE = 0x02
|
2022-04-06 23:44:24 +02:00
|
|
|
|
2022-04-15 20:02:42 +02:00
|
|
|
MESH_SIGNIN = 0x03
|
|
|
|
MESH_LAYER_ANNOUNCE = 0x04
|
|
|
|
MESH_ADD_DESTINATIONS = 0x05
|
|
|
|
MESH_REMOVE_DESTINATIONS = 0x06
|
2022-04-06 23:44:24 +02:00
|
|
|
|
2022-04-15 20:02:42 +02:00
|
|
|
CONFIG_DUMP = 0x10
|
|
|
|
CONFIG_FIRMWARE = 0x11
|
|
|
|
CONFIG_POSITION = 0x12
|
|
|
|
CONFIG_LED = 0x13
|
|
|
|
CONFIG_UPLINK = 0x14
|
2022-04-06 23:44:24 +02:00
|
|
|
|
|
|
|
|
2022-04-15 20:02:42 +02:00
|
|
|
M = TypeVar('M', bound='Message')
|
2022-04-04 14:48:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class Message:
|
2022-04-06 17:25:46 +02:00
|
|
|
dst: str = field(metadata={'format': MacAddressFormat()})
|
|
|
|
src: str = field(metadata={'format': MacAddressFormat()})
|
2022-04-15 20:02:42 +02:00
|
|
|
msg_id: int = field(metadata={'format': SimpleFormat('B')}, init=False, repr=False)
|
2022-04-04 14:48:43 +02:00
|
|
|
msg_types = {}
|
|
|
|
|
|
|
|
# noinspection PyMethodOverriding
|
|
|
|
def __init_subclass__(cls, /, msg_id=None, **kwargs):
|
|
|
|
super().__init_subclass__(**kwargs)
|
|
|
|
if msg_id:
|
|
|
|
cls.msg_id = msg_id
|
2022-04-15 20:02:42 +02:00
|
|
|
if msg_id in Message.msg_types:
|
|
|
|
raise TypeError('duplicate use of msg_id %d' % msg_id)
|
2022-04-04 14:48:43 +02:00
|
|
|
Message.msg_types[msg_id] = cls
|
|
|
|
|
|
|
|
def encode(self):
|
2022-04-06 17:25:46 +02:00
|
|
|
data = bytes()
|
2022-04-04 14:48:43 +02:00
|
|
|
for field_ in fields(self):
|
|
|
|
data += field_.metadata['format'].encode(getattr(self, field_.name))
|
|
|
|
return data
|
|
|
|
|
|
|
|
@classmethod
|
2022-04-15 20:02:42 +02:00
|
|
|
def decode(cls, data: bytes) -> M:
|
|
|
|
# print('decode', data.hex(' '))
|
2022-04-06 17:25:46 +02:00
|
|
|
klass = cls.msg_types[data[12]]
|
2022-04-04 14:48:43 +02:00
|
|
|
values = {}
|
|
|
|
for field_ in fields(klass):
|
|
|
|
values[field_.name], data = field_.metadata['format'].decode(data)
|
2022-04-06 17:25:46 +02:00
|
|
|
values.pop('msg_id')
|
2022-04-04 14:48:43 +02:00
|
|
|
return klass(**values)
|
|
|
|
|
2022-04-15 20:02:42 +02:00
|
|
|
def tojson(self):
|
|
|
|
return asdict(self)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def fromjson(cls, data) -> M:
|
|
|
|
kwargs = data.copy()
|
|
|
|
klass = cls.msg_types[kwargs.pop('msg_id')]
|
|
|
|
for field_ in fields(klass):
|
|
|
|
if is_dataclass(field_.type):
|
|
|
|
kwargs[field_.name] = field_.type.fromjson(kwargs[field_.name])
|
|
|
|
return klass(**kwargs)
|
|
|
|
|
2022-04-04 14:48:43 +02:00
|
|
|
|
|
|
|
@dataclass
|
2022-04-15 20:02:42 +02:00
|
|
|
class EchoRequestMessage(Message, msg_id=MessageType.ECHO_REQUEST):
|
2022-04-04 14:48:43 +02:00
|
|
|
content: str = field(default='', metadata={'format': VarStrFormat()})
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2022-04-15 20:02:42 +02:00
|
|
|
class EchoResponseMessage(Message, msg_id=MessageType.ECHO_RESPONSE):
|
2022-04-06 21:05:52 +02:00
|
|
|
content: str = field(default='', metadata={'format': VarStrFormat()})
|
2022-04-04 14:48:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2022-04-15 20:02:42 +02:00
|
|
|
class MeshSigninMessage(Message, msg_id=MessageType.MESH_SIGNIN):
|
2022-04-06 21:05:52 +02:00
|
|
|
pass
|
2022-04-04 14:48:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2022-04-15 20:02:42 +02:00
|
|
|
class MeshLayerAnnounceMessage(Message, msg_id=MessageType.MESH_LAYER_ANNOUNCE):
|
2022-04-06 21:05:52 +02:00
|
|
|
layer: int = field(metadata={'format': SimpleFormat('B')})
|
2022-04-04 14:48:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2022-04-15 20:02:42 +02:00
|
|
|
class MeshAddDestinationsMessage(Message, msg_id=MessageType.MESH_ADD_DESTINATIONS):
|
2022-04-06 21:05:52 +02:00
|
|
|
mac_addresses: list[str] = field(default_factory=list, metadata={'format': MacAddressesListFormat()})
|
2022-04-04 14:48:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2022-04-15 20:02:42 +02:00
|
|
|
class MeshRemoveDestinationsMessage(Message, msg_id=MessageType.MESH_REMOVE_DESTINATIONS):
|
2022-04-06 21:05:52 +02:00
|
|
|
mac_addresses: list[str] = field(default_factory=list, metadata={'format': MacAddressesListFormat()})
|
2022-04-06 22:56:08 +02:00
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2022-04-15 20:02:42 +02:00
|
|
|
class ConfigDumpMessage(Message, msg_id=MessageType.CONFIG_DUMP):
|
2022-04-06 22:56:08 +02:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2022-04-15 20:02:42 +02:00
|
|
|
class ConfigFirmwareMessage(Message, msg_id=MessageType.CONFIG_FIRMWARE):
|
2023-10-02 22:02:25 +02:00
|
|
|
chip: int = field(metadata={'format': SimpleFormat('H')})
|
|
|
|
revision: int = field(metadata={'format': SimpleFormat('2B')})
|
2022-04-06 22:56:08 +02:00
|
|
|
magic_word: int = field(metadata={'format': SimpleFormat('I')}, repr=False)
|
|
|
|
secure_version: int = field(metadata={'format': SimpleFormat('I')})
|
2022-04-06 23:44:24 +02:00
|
|
|
reserv1: list[int] = field(metadata={'format': SimpleFormat('2I')}, repr=False)
|
2022-04-06 22:56:08 +02:00
|
|
|
version: str = field(metadata={'format': FixedStrFormat(32)})
|
|
|
|
project_name: str = field(metadata={'format': FixedStrFormat(32)})
|
|
|
|
compile_time: str = field(metadata={'format': FixedStrFormat(16)})
|
|
|
|
compile_date: str = field(metadata={'format': FixedStrFormat(16)})
|
|
|
|
idf_version: str = field(metadata={'format': FixedStrFormat(32)})
|
|
|
|
app_elf_sha256: str = field(metadata={'format': HexFormat(32)})
|
2022-04-06 23:44:24 +02:00
|
|
|
reserv2: list[int] = field(metadata={'format': SimpleFormat('20I')}, repr=False)
|
|
|
|
|
2023-10-02 22:02:25 +02:00
|
|
|
def to_model_data(self):
|
|
|
|
return {
|
|
|
|
'chip': self.chip,
|
|
|
|
'project_name': self.project_name,
|
|
|
|
'version': self.version,
|
|
|
|
'idf_version': self.idf_version,
|
|
|
|
'sha256_hash': self.app_elf_sha256,
|
|
|
|
}
|
|
|
|
|
2022-04-06 23:44:24 +02:00
|
|
|
|
|
|
|
@dataclass
|
2022-04-15 20:02:42 +02:00
|
|
|
class ConfigPositionMessage(Message, msg_id=MessageType.CONFIG_POSITION):
|
2022-04-06 23:44:24 +02:00
|
|
|
x_pos: int = field(metadata={'format': SimpleFormat('I')})
|
|
|
|
y_pos: int = field(metadata={'format': SimpleFormat('I')})
|
|
|
|
z_pos: int = field(metadata={'format': SimpleFormat('H')})
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2022-04-15 20:02:42 +02:00
|
|
|
class ConfigLedMessage(Message, msg_id=MessageType.CONFIG_LED):
|
2022-04-06 23:44:24 +02:00
|
|
|
led_config: LedConfig = field(metadata={'format': LedConfigFormat()})
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
2022-04-15 20:02:42 +02:00
|
|
|
class ConfigUplinkMessage(Message, msg_id=MessageType.CONFIG_UPLINK):
|
2022-04-06 23:44:24 +02:00
|
|
|
enabled: bool = field(metadata={'format': BoolFormat()})
|
|
|
|
ssid: str = field(metadata={'format': FixedStrFormat(32)})
|
|
|
|
password: str = field(metadata={'format': FixedStrFormat(64)})
|
|
|
|
channel: int = field(metadata={'format': SimpleFormat('B')})
|
|
|
|
udp: bool = field(metadata={'format': BoolFormat()})
|
|
|
|
ssl: bool = field(metadata={'format': BoolFormat()})
|
|
|
|
host: str = field(metadata={'format': FixedStrFormat(64)})
|
|
|
|
port: int = field(metadata={'format': SimpleFormat('H')})
|