set up base websocket server for mesh communication
This commit is contained in:
parent
8af6bca51c
commit
683f80ef75
3 changed files with 117 additions and 9 deletions
|
@ -1,12 +1,19 @@
|
|||
from channels.generic.websocket import WebsocketConsumer
|
||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
|
||||
from c3nav.mesh import messages
|
||||
|
||||
|
||||
class EchoConsumer(WebsocketConsumer):
|
||||
def connect(self):
|
||||
self.accept()
|
||||
class MeshConsumer(AsyncWebsocketConsumer):
|
||||
async def connect(self):
|
||||
await self.accept()
|
||||
|
||||
def disconnect(self, close_code):
|
||||
async def disconnect(self, close_code):
|
||||
pass
|
||||
|
||||
def receive(self, text_data):
|
||||
self.send(text_data=text_data)
|
||||
async def receive(self, text_data=None, bytes_data=None):
|
||||
if bytes_data is None:
|
||||
return
|
||||
msg = messages.Message.decode(bytes_data)
|
||||
print('Received message:', msg)
|
||||
if isinstance(msg, messages.MeshSigninMessage):
|
||||
await self.send(messages.MeshLayerAnnounceMessage(messages.NO_LAYER).encode())
|
||||
|
|
101
src/c3nav/mesh/messages.py
Normal file
101
src/c3nav/mesh/messages.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
import struct
|
||||
from dataclasses import dataclass, field, fields
|
||||
|
||||
NO_LAYER = 0xFF
|
||||
MAC_FMT = '%02x:%02x:%02x:%02x:%02x:%02x'
|
||||
|
||||
|
||||
class SimpleFormat:
|
||||
def __init__(self, fmt):
|
||||
self.fmt = fmt
|
||||
self.size = struct.calcsize(fmt)
|
||||
|
||||
def encode(self, value):
|
||||
return struct.pack(self.fmt, value)
|
||||
|
||||
def decode(self, data: bytes):
|
||||
return struct.unpack(self.fmt, data[:self.size]), data[self.size:]
|
||||
|
||||
|
||||
class VarStrFormat:
|
||||
def encode(self, value: str) -> bytes:
|
||||
return bytes((len(value)+1, )) + value.encode() + bytes((0, ))
|
||||
|
||||
def decode(self, data: bytes):
|
||||
return data[1:data[0]].decode(), data[data[0]+1:]
|
||||
|
||||
|
||||
class MacAddressFormat:
|
||||
def encode(self, value: str) -> bytes:
|
||||
return bytes(int(value[i*3:i*3+2], 16) for i in range(6))
|
||||
|
||||
def decode(self, data: bytes):
|
||||
return (MAC_FMT % tuple(data[:6])), data[6:]
|
||||
|
||||
|
||||
class MacAddressesListFormat:
|
||||
def encode(self, value: list[str]) -> bytes:
|
||||
return bytes((len(value), )) + sum(
|
||||
(bytes((int(mac[i*3:i*3+2], 16) for i in range(6))) for mac in value),
|
||||
b''
|
||||
)
|
||||
|
||||
def decode(self, data: bytes):
|
||||
return [MAC_FMT % tuple(data[1+6*i:1+6+6*i]) for i in range(data[0])], data[1+data[0]*6]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Message:
|
||||
msg_types = {}
|
||||
|
||||
# noinspection PyMethodOverriding
|
||||
def __init_subclass__(cls, /, msg_id=None, **kwargs):
|
||||
super().__init_subclass__(**kwargs)
|
||||
if msg_id:
|
||||
cls.msg_id = msg_id
|
||||
Message.msg_types[msg_id] = cls
|
||||
|
||||
def encode(self):
|
||||
data = bytes((self.msg_id, ))
|
||||
for field_ in fields(self):
|
||||
data += field_.metadata['format'].encode(getattr(self, field_.name))
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def decode(cls, data: bytes):
|
||||
klass = cls.msg_types[data[0]]
|
||||
data = data[1:]
|
||||
values = {}
|
||||
for field_ in fields(klass):
|
||||
values[field_.name], data = field_.metadata['format'].decode(data)
|
||||
return klass(**values)
|
||||
|
||||
|
||||
@dataclass
|
||||
class EchoMessage(Message, msg_id=0x01):
|
||||
content: str = field(default='', metadata={'format': VarStrFormat()})
|
||||
|
||||
|
||||
@dataclass
|
||||
class MeshSigninMessage(Message, msg_id=0x02):
|
||||
mac_address: str = field(metadata={'format': MacAddressFormat()})
|
||||
|
||||
|
||||
@dataclass
|
||||
class MeshLayerAnnounceMessage(Message, msg_id=0x03):
|
||||
layer: int = field(metadata={'format': SimpleFormat('B')})
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseMeshDestinationsMessage(Message):
|
||||
mac_addresses: list[str] = field(default_factory=list, metadata={'format': MacAddressesListFormat()})
|
||||
|
||||
|
||||
@dataclass
|
||||
class MeshAddDestinationsMessage(BaseMeshDestinationsMessage, msg_id=0x04):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class MeshRemoveDestinationsMessage(BaseMeshDestinationsMessage, msg_id=0x05):
|
||||
pass
|
|
@ -1,7 +1,7 @@
|
|||
from django.urls import path
|
||||
|
||||
from c3nav.mesh.consumers import EchoConsumer
|
||||
from c3nav.mesh.consumers import MeshConsumer
|
||||
|
||||
websocket_urlpatterns = [
|
||||
path('ws', EchoConsumer.as_asgi()),
|
||||
path('ws', MeshConsumer.as_asgi()),
|
||||
]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue