From 8b446f9a4c2db85a6937388a9ef2736eb76145da Mon Sep 17 00:00:00 2001 From: gsd Date: Sun, 8 Feb 2026 23:59:28 +0300 Subject: [PATCH] next --- botExtensions/PingPong.py | 8 +++ botExtensions/SignalTest.py | 8 +++ botManager.py | 108 ++++++++++++++++++++++++++++++++++++ logger.py | 12 ++++ mesht_device.py | 2 + service.py | 54 +++++++++++------- todo | 10 ++++ 7 files changed, 183 insertions(+), 19 deletions(-) create mode 100644 botExtensions/PingPong.py create mode 100644 botExtensions/SignalTest.py create mode 100644 botManager.py create mode 100644 logger.py create mode 100644 todo diff --git a/botExtensions/PingPong.py b/botExtensions/PingPong.py new file mode 100644 index 0000000..c47c5b6 --- /dev/null +++ b/botExtensions/PingPong.py @@ -0,0 +1,8 @@ +class BotExtension: + def __init__(self, bm): + self.bm = bm + self.name = "Base ping pong" + self.trigger = ['ping', 'p'] + + def __call__(self, msg, text): + return "P0N9" \ No newline at end of file diff --git a/botExtensions/SignalTest.py b/botExtensions/SignalTest.py new file mode 100644 index 0000000..b5e0b74 --- /dev/null +++ b/botExtensions/SignalTest.py @@ -0,0 +1,8 @@ +class BotExtension: + def __init__(self, bm): + self.bm = bm + self.name = "Signal check" + self.trigger = ['signal', 's'] + + def __call__(self, msg, text): + return f"snr: {msg.packet.get('rx_snr', 'unk')} dB\nrssi: {msg.packet.get('rx_rssi', 'unk')} dB\nhops: {msg.packet.get('hop_start', 0) - msg.packet.get('hop_limit', 0)}" \ No newline at end of file diff --git a/botManager.py b/botManager.py new file mode 100644 index 0000000..048dd60 --- /dev/null +++ b/botManager.py @@ -0,0 +1,108 @@ +from mesht_models import PUB_CH +from mesht_device import PORTNUMS + +import sys, os + +from logger import logger + +class MeshtasticMessage: + def __init__(self, from_radio, myId): + self.myId = myId + self.packet = from_radio.get("packet", {}) + self.decoded = self.packet.get("decoded", {}) + if not self.decoded: + return + + def getFrom(self): + return self.packet["from"] + + def getTo(self): + return self.packet["to"] + + def isDm(self): + return self.packet["to"] == self.myId + + def isPublic(self): + return self.packet["to"] == PUB_CH + + def getText(self): + try: + return self.decoded["payload"].decode() + except: + return "" + + def __str__(self): + return f"[{self.getFrom()}] -> [{self.getTo()}] [{'PUB' if self.isPublic() else 'DM'}] > {self.getText()}" + +class BotManager: + prefix = "/?" + + def __init__(self, core): + #delaem workdir esli ne delali + os.chdir(os.path.dirname(os.path.abspath(__file__))) + + self.coreService = core + self.exts = {} + self.extensionLoader(["botExtensions"]) + + def extensionLoader(self, search_paths = []): + if type(search_paths) == str: + search_paths = [search_paths] + + for path in search_paths: + logger.info(f"Try found extensions in {path}") + if not os.path.exists(path) or not os.path.isdir(path): + logger.info(f"Directory is not exists or not directory, skip") + continue + + sys.path.insert(0, path) + for extension in os.listdir(path): + extension, ext = os.path.splitext(extension) + if ext != ".py": + continue + logger.info(f"Found ext: {extension}") + self.exts[f"{path}/{extension}"] = __import__(extension).BotExtension(self) + sys.path.pop(0) + + logger.info(f"Found {self.exts.keys().__len__()} extension") + + async def handleMessage(self, from_radio): + if not self.isTextMessage(from_radio): + return + if not self.isToMe(from_radio): + return + + msg = MeshtasticMessage(from_radio, self.coreService.device.my_node_id_dec) + logger.info(msg) + + if msg.isPublic() and msg.getText().startswith(self.prefix): + await self.processMessage(msg, msg.getText()[len(self.prefix):])#remove prefix + elif msg.isDm(): + await self.processMessage(msg, msg.getText()) + else: + #nichego ne delaem + pass + + async def processMessage(self, msg: MeshtasticMessage, msgText: str): + if not msgText: + return + + for path, ext in self.exts.items(): + if msgText.split()[0].lower() in ext.trigger: + response = ext(msg, msgText) + return await self.reply(msg, response) + + async def reply(self, msg: MeshtasticMessage, text: str): + if msg.isDm(): + return await self.coreService.device.sendMsgToDM(text, msg.getFrom()) + elif msg.isPublic(): + return await self.coreService.device.sendMsgToChannel(text) + else: + return + + def isTextMessage(self, from_radio): + return from_radio and from_radio.get("packet", {}).get("decoded", {}).get("portnum", 0) == 1 + + def isToMe(self, from_radio): + return self.coreService.device.my_node_id_dec == from_radio.get("packet", {}).get("to", 0) or from_radio.get("packet", {}).get("to", 0) == PUB_CH + \ No newline at end of file diff --git a/logger.py b/logger.py new file mode 100644 index 0000000..3c6e98b --- /dev/null +++ b/logger.py @@ -0,0 +1,12 @@ +class logger: + @staticmethod + def info(t): + print("[INFO]", t) + + @staticmethod + def error(t): + print("[ERROR]", t) + + @staticmethod + def debug(t): + print("[DEBUG]", t) \ No newline at end of file diff --git a/mesht_device.py b/mesht_device.py index 56a2501..788c140 100644 --- a/mesht_device.py +++ b/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.my_node_id_dec = 0 self.nid = None async def start(self): @@ -261,6 +262,7 @@ class MeshtDevice: return self.nid = mi.get("my_node_num", 0) + self.my_node_id_dec = int(self.nid) self.my_node_id = f"{self.nid & 0xFFFFFFFF:08x}" def _maybe_store_channel(self, from_radio): diff --git a/service.py b/service.py index b44b03d..e74c7f9 100644 --- a/service.py +++ b/service.py @@ -5,19 +5,9 @@ import sys import asyncio from time import time from typing import List, Dict +import copy -class logger: - @staticmethod - def info(t): - print("[INFO]", t) - - @staticmethod - def error(t): - print("[ERROR]", t) - - @staticmethod - def debug(t): - print("[DEBUG]", t) +from logger import logger #mesh from mesht_device import MeshtDevice @@ -32,6 +22,9 @@ from contextlib import asynccontextmanager #mongo from pymongo import AsyncMongoClient +#other +from botManager import BotManager + def isInt(any): try: int(any) @@ -73,7 +66,7 @@ class MeshListener(MeshArgsParse): self.meshState = AVAILABLE while True: from_radio, _ = await self.device.recv() - logger.debug(from_radio) + #logger.debug(from_radio) await queue.put(from_radio) except asyncio.exceptions.CancelledError: logger.info("Kill mesh device") @@ -119,7 +112,7 @@ class MongoDriver(MeshArgsParse): self.dbStore = self.dbClient[self.args.mongo_db] #self.dbCollection = self.dbStore.from_radio - async def dbSaveRadio(self, from_radio): + async def dbSaveRadio(self, new_from_radio): '''try: anyJson = from_radio["packet"] except: @@ -128,6 +121,7 @@ class MongoDriver(MeshArgsParse): #logger.debug(from_radio) #logger.debug(len(list(from_radio.keys()))) + from_radio = copy.deepcopy(new_from_radio) for k, v in from_radio.items(): if type(v) != dict: v = {"data": v, "ts": time()} @@ -149,7 +143,7 @@ class MongoDriver(MeshArgsParse): await self.dbStore[k].insert_one(v) - async def dbQueueListener(self, queue: asyncio.Queue): + '''async def dbQueueListener(self, queue: asyncio.Queue): logger.info("Start db queue listener") run = True while run: @@ -163,7 +157,7 @@ class MongoDriver(MeshArgsParse): logger.info("Kill db listener") run = False except: - traceback.print_exc() + traceback.print_exc()''' class MeshCenter(MeshListener, MeshApi, MongoDriver, MeshArgsParse): queue: asyncio.Queue = asyncio.Queue() @@ -174,15 +168,37 @@ class MeshCenter(MeshListener, MeshApi, MongoDriver, MeshArgsParse): MeshApi.__init__(self, args) MongoDriver.__init__(self, args) self.buildBackgroundTasks() + self.bot = BotManager(self) + + async def queueHandler(self): + logger.info("Start queue handler") + run = True + while run: + try: + from_radio = await self.queue.get() + if from_radio: + yield from_radio + + except asyncio.exceptions.CancelledError: + run = False + except: + traceback.print_exc() + def buildBackgroundTasks(self): + #input queue async def mL(): await self.meshWorker(self.queue) self.tasks.append(mL) - async def dbL(): - await self.dbQueueListener(self.queue) - self.tasks.append(dbL) + #output + async def handlerTask(): + async for from_radio in self.queueHandler(): + #logger.debug(from_radio) + asyncio.create_task(self.dbSaveRadio(from_radio)) + asyncio.create_task(self.bot.handleMessage(from_radio)) + + self.tasks.append(handlerTask) if __name__ == "__main__": parser = argparse.ArgumentParser() diff --git a/todo b/todo new file mode 100644 index 0000000..38b3ccb --- /dev/null +++ b/todo @@ -0,0 +1,10 @@ +Viewing Collection: node_info + +график изменения количество snr и хопов относително времени + +график по пакетам от клиентов и также снр ссри +топ по количеству сранья протобафами + +мб можно развлечься с триангуляцией + +hop_start == hop_limit - директы \ No newline at end of file