Browse Source

ws support

main
gsd 7 months ago
parent
commit
3c3b92e84e
  1. 93
      pipboyMESH/mesh_server.py
  2. 65
      pipboyMESH/mesh_ws_debug.py
  3. 5
      pipboyMESH/mesht_device.py
  4. 28
      pipboyMESH/mesht_models.py

93
pipboyMESH/mesh_server.py

@ -1,5 +1,6 @@
#fs imports #fs imports
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException, WebSocket
from fastapi.responses import HTMLResponse
from uvicorn import run as UR from uvicorn import run as UR
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
@ -28,13 +29,23 @@ class Servlet:
message: List[Message] = [] message: List[Message] = []
msh_state = NOT_CONNECTED msh_state = NOT_CONNECTED
ws_clients: List[WebSocket] = []
lstChange = {
"channels": time(),
"messages": time(),
"nodes": time(),
"state": time()
}
def __init__(self, args): def __init__(self, args):
self.app = FastAPI(lifespan=self.lifespan) self.app = FastAPI(lifespan=self.lifespan)
self.args = args self.args = args
if args.transport == "serial": if args.transport == "serial":
self.transport = SerialTransport(port=args.serial_port, baudrate=args.serial_baudrate) self.transport = SerialTransport(port=args.serial_port, baudrate=args.serial_baudrate)
self.device = MeshtDevice(self.transport) self.device = MeshtDevice(self.transport)
self.buildRoutes() self.buildRoutes()
self.buildWS()
def run(self): def run(self):
UR(self.app, host=self.args.host, port=self.args.port)#, reload = True)#todo UR(self.app, host=self.args.host, port=self.args.port)#, reload = True)#todo
@ -46,57 +57,125 @@ class Servlet:
yield yield
print("server is shutdown now") print("server is shutdown now")
async def ws_update(self, data):
data.update({"type": WS_TYPE_NEW})
for client in self.ws_clients:
try:
await client.send_json(data)
except:
traceback.print_exc()
async def meshWorker(self): async def meshWorker(self):
run = True run = True
while run: while run:
try: try:
await self.device.start() await self.device.start()
self.msh_state = WAIT_CONFIG self.msh_state = WAIT_CONFIG
self.lstChange["state"] = time()
await self.ws_update({"event": WS_EVENT_STATE, "data": self.msh_state})
init_data = await _wait_for_config_complete(self.device) init_data = await _wait_for_config_complete(self.device)
for from_radio in init_data:#todo check dubs for from_radio in init_data:#todo check dubs
self.meshCollector(from_radio) await self.meshCollector(from_radio)
self.msh_state = AVAILABLE
self.lstChange["state"] = time()
await self.ws_update({"event": WS_EVENT_STATE, "data": self.msh_state})
await self.ws_update({"event": WS_EVENT_MYID, "data": self.device.my_node_id})
while True: while True:
self.msh_state = AVAILABLE
self.pulse = time() self.pulse = time()
from_radio, _ = await self.device.recv() from_radio, _ = await self.device.recv()
self.meshCollector(from_radio) await self.meshCollector(from_radio)
except asyncio.exceptions.CancelledError: except asyncio.exceptions.CancelledError:
print("Web close") print("Web close")
run = False run = False
pass pass
except: except:
self.msh_state = ERR self.msh_state = ERR
await self.ws_update({"event": WS_EVENT_STATE, "data": self.msh_state})
self.lstChange["state"] = time()
print("Mesh worker is has error, reconnect to mesh device after seconds...") print("Mesh worker is has error, reconnect to mesh device after seconds...")
traceback.print_exc() traceback.print_exc()
self.msh_state = RECONNECT self.msh_state = RECONNECT
await self.ws_update({"event": WS_EVENT_STATE, "data": self.msh_state})
self.lstChange["state"] = time()
await asyncio.sleep(1) await asyncio.sleep(1)
finally: finally:
await self.device.close() await self.device.close()
def meshCollector(self, from_radio): async def meshCollector(self, from_radio):
try: try:
print(from_radio) print(from_radio)
if Channel.isThis(from_radio): if Channel.isThis(from_radio):
print("Found channel packet") print("Found channel packet")
channel = Channel(from_radio) channel = Channel(from_radio)
self.channels[channel.index] = channel self.channels[channel.index] = channel
await self.ws_update({"type":WS_TYPE_NEW, "event": WS_EVENT_CHANNEL, "data": channel.__dict__})
self.lstChange["channels"] = time()
return return
if Node.isThis(from_radio): if Node.isThis(from_radio):
print("Found node packet") print("Found node packet")
node = Node(from_radio) node = Node(from_radio)
self.nodes[node.num] = node self.nodes[node.num] = node
await self.ws_update({"type":WS_TYPE_NEW, "event": WS_EVENT_NODE, "data": {"id": node.num, "name": str(node)}})
self.lstChange["nodes"] = time()
return return
if Message.isThis(from_radio): if Message.isThis(from_radio):
print("Found message packet") print("Found message packet")
self.message.append(Message(from_radio)) msg = Message(from_radio)
self.message.append(msg)
await self.ws_update({"type":WS_TYPE_NEW, "event": WS_EVENT_MESSAGE, "data": msg.__dict__})
self.lstChange["messages"] = time()
return
except: except:
print("cannot parse packet") print("cannot parse packet")
traceback.print_exc() traceback.print_exc()
def buildWS(self):
#from json import dumps, loads
from mesh_ws_debug import page
@self.app.get("/mesh/wsdebug", response_class=HTMLResponse)
async def wsdebug():
return page
async def ws_hello(websocket: WebSocket):
print("send hello")
try:
await websocket.send_json({"type":WS_TYPE_INIT, "event": WS_EVENT_MYID, "data": self.device.nid})
#await websocket.send_json({"type":WS_TYPE_INIT, "event": WS_EVENT_MYNODE, "data": self.nodes.get(self.device.nid).__dict__ if self.device.nid in self.nodes else None})
await websocket.send_json({"type":WS_TYPE_INIT, "event": WS_EVENT_CHANNEL, "data": [channel.__dict__ for channel in self.channels.values()]})
await websocket.send_json({"type":WS_TYPE_INIT, "event": WS_EVENT_STATE, "data": self.msh_state})
await websocket.send_json({"type":WS_TYPE_INIT, "event": WS_EVENT_NODE, "data": [{"id": node.num, "name": str(node)} for node in self.nodes.values()]})
await websocket.send_json({"type":WS_TYPE_INIT, "event": WS_EVENT_MESSAGE, "data": [message.__dict__ for message in self.message]})
except:
traceback.print_exc()
pass
@self.app.websocket("/mesh/ws")
async def ws_endpoint(websocket: WebSocket):
await websocket.accept()
self.ws_clients.append(websocket)
#грузим фуру кала
try:
await ws_hello(websocket)
while True:
msg = await websocket.receive_json()
print(msg)
except:
traceback.print_exc()
pass
finally:
self.ws_clients.remove(websocket)
try:
await websocket.close()
except:
pass
def buildRoutes(self): def buildRoutes(self):
route = "/mesh/api" route = "/mesh/api"

65
pipboyMESH/mesh_ws_debug.py

@ -0,0 +1,65 @@
page = """<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"UTF-8\">
<title>Home page</title>
<link href=\"https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.3.1/semantic.min.css\"
rel=\"stylesheet\">
</head>
<body>
<div class=\"ui container\">
<h1>Debug websocket page</h1>
<div class=\"two column grid\">
<div class=\"row\">
<div class=\"column\">
<label for=\"myMessage\">Message</label>
</div>
<div class=\"column\">
<div class=\"ui input\">
<input type=\"text\" id=\"myMessage\">
</div>
</div>
</div>
<div class=\"row\">
<div class=\"column\">
<label for=\"output\">Response from Server</label>
</div>
<div class=\"column\">
<textarea rows=\"8\" cols=\"50\" id=\"output\" readonly=\"readonly\"></textarea>
</div>
</div>
<div class=\"row\">
<button class=\"ui button\" onclick=\"send()\">Send</button>
</div>
</div>
</div>
<script>
const socketConn = new WebSocket('ws://localhost:8868/mesh/ws');
function send() {
const clientMsg = document.getElementById('myMessage');
if (clientMsg.value) {
socketConn.send(clientMsg.value);
}
}
socketConn.onmessage = (e) => {
const output = document.getElementById('output');
output.value += `${e.data}\\n`;
}
</script>
</body>
</html>"""

5
pipboyMESH/mesht_device.py

@ -201,6 +201,7 @@ class MeshtDevice:
self.lora_config = None self.lora_config = None
# Track local node number from MyNodeInfo # Track local node number from MyNodeInfo
self.my_node_id = "00000000" self.my_node_id = "00000000"
self.nid = None
async def start(self): async def start(self):
await self.transport.start() await self.transport.start()
@ -259,8 +260,8 @@ class MeshtDevice:
if not mi: if not mi:
return return
my_node_num = mi.get("my_node_num", 0) self.nid = mi.get("my_node_num", 0)
self.my_node_id = f"{my_node_num & 0xFFFFFFFF:08x}" self.my_node_id = f"{self.nid & 0xFFFFFFFF:08x}"
def _maybe_store_channel(self, from_radio): def _maybe_store_channel(self, from_radio):
if not isinstance(from_radio, dict): if not isinstance(from_radio, dict):

28
pipboyMESH/mesht_models.py

@ -1,4 +1,5 @@
import pydantic import pydantic
from time import time
PUB_CH = 0xFFFFFFFF PUB_CH = 0xFFFFFFFF
PACKET = "packet" PACKET = "packet"
@ -9,6 +10,17 @@ AVAILABLE = 2
ERR = 3 ERR = 3
RECONNECT = 4 RECONNECT = 4
WS_EVENT_CHANNEL = 0
WS_EVENT_NODE = 1
WS_EVENT_STATE = 2
WS_EVENT_MESSAGE = 3
WS_EVENT_MYID = 4
#WS_EVENT_MYNODE = 5
WS_TYPE_INIT = 0
WS_TYPE_NEW = 1
WS_TYPE_FRONTEND = 2
async def _wait_for_config_complete(device, extraInfo = False): async def _wait_for_config_complete(device, extraInfo = False):
print("wait config") print("wait config")
packets = [] packets = []
@ -34,7 +46,7 @@ class Channel:
class Node: #aka node info class Node: #aka node info
def __init__(self, fr): def __init__(self, fr):
self.num = fr["node_info"]["num"] self.num = fr["node_info"]["num"]
self.user = User(fr["node_info"]) self.user = fr["node_info"].get("user", {})
self.snr = fr["node_info"].get("snr", None) self.snr = fr["node_info"].get("snr", None)
self.last_heard = fr["node_info"].get("last_heard", None) self.last_heard = fr["node_info"].get("last_heard", None)
self.hops_away = fr["node_info"].get("hops_away", None) self.hops_away = fr["node_info"].get("hops_away", None)
@ -44,18 +56,7 @@ class Node: #aka node info
return isinstance(fr, dict) and "node_info" in fr return isinstance(fr, dict) and "node_info" in fr
def __str__(self): def __str__(self):
return f"({self.user.short_name}) {self.user.long_name}" return f"({self.user.get('short_name', None)}) {self.user.get('long_name', None)}"
class User:
def __init__(self, ni):
if "user" in ni:
self.id = ni["user"]["id"]
self.long_name = ni["user"]["long_name"]
self.short_name = ni["user"]["short_name"]
else:
self.id = None
self.long_name = None
self.short_name = None
class NewMessage(pydantic.BaseModel): class NewMessage(pydantic.BaseModel):
channel_id: int = 0 channel_id: int = 0
@ -73,6 +74,7 @@ class Message:
self.hop_limit = fr[PACKET].get("hop_limit", None) self.hop_limit = fr[PACKET].get("hop_limit", None)
self.rx_rssi = fr[PACKET].get("rx_rssi", None) self.rx_rssi = fr[PACKET].get("rx_rssi", None)
self.channel = fr[PACKET].get("channel", 0) self.channel = fr[PACKET].get("channel", 0)
self.append_time = time()
@property @property
def isDm(self): def isDm(self):

Loading…
Cancel
Save