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
from fastapi import FastAPI, HTTPException
from fastapi import FastAPI, HTTPException, WebSocket
from fastapi.responses import HTMLResponse
from uvicorn import run as UR
from contextlib import asynccontextmanager
@ -28,13 +29,23 @@ class Servlet:
message: List[Message] = []
msh_state = NOT_CONNECTED
ws_clients: List[WebSocket] = []
lstChange = {
"channels": time(),
"messages": time(),
"nodes": time(),
"state": time()
}
def __init__(self, args):
self.app = FastAPI(lifespan=self.lifespan)
self.args = args
if args.transport == "serial":
self.transport = SerialTransport(port=args.serial_port, baudrate=args.serial_baudrate)
self.device = MeshtDevice(self.transport)
self.buildRoutes()
self.buildRoutes()
self.buildWS()
def run(self):
UR(self.app, host=self.args.host, port=self.args.port)#, reload = True)#todo
@ -46,57 +57,125 @@ class Servlet:
yield
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):
run = True
while run:
try:
await self.device.start()
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)
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:
self.msh_state = AVAILABLE
self.pulse = time()
from_radio, _ = await self.device.recv()
self.meshCollector(from_radio)
await self.meshCollector(from_radio)
except asyncio.exceptions.CancelledError:
print("Web close")
run = False
pass
except:
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...")
traceback.print_exc()
self.msh_state = RECONNECT
await self.ws_update({"event": WS_EVENT_STATE, "data": self.msh_state})
self.lstChange["state"] = time()
await asyncio.sleep(1)
finally:
await self.device.close()
def meshCollector(self, from_radio):
async def meshCollector(self, from_radio):
try:
print(from_radio)
if Channel.isThis(from_radio):
print("Found channel packet")
channel = Channel(from_radio)
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
if Node.isThis(from_radio):
print("Found node packet")
node = Node(from_radio)
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
if Message.isThis(from_radio):
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:
print("cannot parse packet")
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):
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
# Track local node number from MyNodeInfo
self.my_node_id = "00000000"
self.nid = None
async def start(self):
await self.transport.start()
@ -259,8 +260,8 @@ class MeshtDevice:
if not mi:
return
my_node_num = mi.get("my_node_num", 0)
self.my_node_id = f"{my_node_num & 0xFFFFFFFF:08x}"
self.nid = mi.get("my_node_num", 0)
self.my_node_id = f"{self.nid & 0xFFFFFFFF:08x}"
def _maybe_store_channel(self, from_radio):
if not isinstance(from_radio, dict):

28
pipboyMESH/mesht_models.py

@ -1,4 +1,5 @@
import pydantic
from time import time
PUB_CH = 0xFFFFFFFF
PACKET = "packet"
@ -9,6 +10,17 @@ AVAILABLE = 2
ERR = 3
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):
print("wait config")
packets = []
@ -34,7 +46,7 @@ class Channel:
class Node: #aka node info
def __init__(self, fr):
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.last_heard = fr["node_info"].get("last_heard", 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
def __str__(self):
return f"({self.user.short_name}) {self.user.long_name}"
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
return f"({self.user.get('short_name', None)}) {self.user.get('long_name', None)}"
class NewMessage(pydantic.BaseModel):
channel_id: int = 0
@ -73,6 +74,7 @@ class Message:
self.hop_limit = fr[PACKET].get("hop_limit", None)
self.rx_rssi = fr[PACKET].get("rx_rssi", None)
self.channel = fr[PACKET].get("channel", 0)
self.append_time = time()
@property
def isDm(self):

Loading…
Cancel
Save