From 6f492ef38236babe84a79704448368aab2a29c6c Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 13:15:54 -0800 Subject: [PATCH 01/24] interface Expansion --- config.template | 4 +- mesh_bot.py | 22 ++++++---- modules/settings.py | 72 +++++++++++++++++++++++++++++++ modules/system.py | 102 ++++++++++++++++++-------------------------- modules/web.py | 1 + 5 files changed, 132 insertions(+), 69 deletions(-) create mode 100644 modules/web.py diff --git a/config.template b/config.template index 430a093..fe22350 100644 --- a/config.template +++ b/config.template @@ -12,7 +12,7 @@ port = /dev/ttyACM0 # hostname = localhost # mac = 00:11:22:33:44:55 -# Additional interface for dual radio support +# Additional interface for multi radio support [interface2] enabled = False type = serial @@ -22,6 +22,8 @@ port = /dev/ttyUSB0 # hostname = meshtastic.local # mac = 00:11:22:33:44:55 +# example, the third interface would be [interface3] up to 9 + [general] # if False will respond on all channels but the default channel respond_by_dm_only = True diff --git a/mesh_bot.py b/mesh_bot.py index 52d9c52..a99d869 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -1229,18 +1229,24 @@ def onReceive(packet, interface): async def start_rx(): print (CustomFormatter.bold_white + f"\nMeshtastic Autoresponder Bot CTL+C to exit\n" + CustomFormatter.reset) + + # Start the receive subscriber using pubsub via meshtastic library + pub.subscribe(onReceive, 'meshtastic.receive') + pub.subscribe(onDisconnect, 'meshtastic.connection.lost') + + logger.info(f"System: Autoresponder Started for Device1 {get_name_from_number(myNodeNum1, 'long', 1)}," + f"{get_name_from_number(myNodeNum1, 'short', 1)}. NodeID: {myNodeNum1}, {decimal_to_hex(myNodeNum1)}") + + + if interface2_enabled: + logger.info(f"System: Autoresponder Started for Device2 {get_name_from_number(myNodeNum2, 'long', 2)}," + f"{get_name_from_number(myNodeNum2, 'short', 2)}. NodeID: {myNodeNum2}, {decimal_to_hex(myNodeNum2)}") + if llm_enabled: logger.debug(f"System: Ollama LLM Enabled, loading model {llmModel} please wait") llm_query(" ", myNodeNum1) logger.debug(f"System: LLM model {llmModel} loaded") - # Start the receive subscriber using pubsub via meshtastic library - pub.subscribe(onReceive, 'meshtastic.receive') - pub.subscribe(onDisconnect, 'meshtastic.connection.lost') - logger.info(f"System: Autoresponder Started for Device1 {get_name_from_number(myNodeNum1, 'long', 1)}," - f"{get_name_from_number(myNodeNum1, 'short', 1)}. NodeID: {myNodeNum1}, {decimal_to_hex(myNodeNum1)}") - if interface2_enabled: - logger.info(f"System: Autoresponder Started for Device2 {get_name_from_number(myNodeNum2, 'long', 2)}," - f"{get_name_from_number(myNodeNum2, 'short', 2)}. NodeID: {myNodeNum2}, {decimal_to_hex(myNodeNum2)}") + if log_messages_to_file: logger.debug("System: Logging Messages to disk") if syslog_to_file: diff --git a/modules/settings.py b/modules/settings.py index 034ae8c..0b1a0b0 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -96,6 +96,7 @@ interface1_type = config['interface'].get('type', 'serial') port1 = config['interface'].get('port', '') hostname1 = config['interface'].get('hostname', '') mac1 = config['interface'].get('mac', '') +interface1_enabled = True # gotta have at least one interface # interface2 settings if 'interface2' in config: @@ -107,6 +108,77 @@ if 'interface2' in config: else: interface2_enabled = False +# interface3 settings +if 'interface3' in config: + interface3_type = config['interface3'].get('type', 'serial') + port3 = config['interface3'].get('port', '') + hostname3 = config['interface3'].get('hostname', '') + mac3 = config['interface3'].get('mac', '') + interface3_enabled = config['interface3'].getboolean('enabled', False) +else: + interface3_enabled = False + +# interface4 settings +if 'interface4' in config: + interface4_type = config['interface4'].get('type', 'serial') + port4 = config['interface4'].get('port', '') + hostname4 = config['interface4'].get('hostname', '') + mac4 = config['interface4'].get('mac', '') + interface4_enabled = config['interface4'].getboolean('enabled', False) +else: + interface4_enabled = False + +# interface5 settings +if 'interface5' in config: + interface5_type = config['interface5'].get('type', 'serial') + port5 = config['interface5'].get('port', '') + hostname5 = config['interface5'].get('hostname', '') + mac5 = config['interface5'].get('mac', '') + interface5_enabled = config['interface5'].getboolean('enabled', False) +else: + interface5_enabled = False + +# interface6 settings +if 'interface6' in config: + interface6_type = config['interface6'].get('type', 'serial') + port6 = config['interface6'].get('port', '') + hostname6 = config['interface6'].get('hostname', '') + mac6 = config['interface6'].get('mac', '') + interface6_enabled = config['interface6'].getboolean('enabled', False) +else: + interface6_enabled = False + +# interface7 settings +if 'interface7' in config: + interface7_type = config['interface7'].get('type', 'serial') + port7 = config['interface7'].get('port', '') + hostname7 = config['interface7'].get('hostname', '') + mac7 = config['interface7'].get('mac', '') + interface7_enabled = config['interface7'].getboolean('enabled', False) +else: + interface7_enabled = False + +# interface8 settings +if 'interface8' in config: + interface8_type = config['interface8'].get('type', 'serial') + port8 = config['interface8'].get('port', '') + hostname8 = config['interface8'].get('hostname', '') + mac8 = config['interface8'].get('mac', '') + interface8_enabled = config['interface8'].getboolean('enabled', False) +else: + interface8_enabled = False + +# interface9 settings +if 'interface9' in config: + interface9_type = config['interface9'].get('type', 'serial') + port9 = config['interface9'].get('port', '') + hostname9 = config['interface9'].get('hostname', '') + mac9 = config['interface9'].get('mac', '') + interface9_enabled = config['interface9'].getboolean('enabled', False) +else: + interface9_enabled = False + + # variables from the config.ini file try: # general diff --git a/modules/system.py b/modules/system.py index 84dc77a..aa9d6b8 100644 --- a/modules/system.py +++ b/modules/system.py @@ -208,62 +208,44 @@ if len(help_message) > 20: help_message = ", ".join(help_message) # BLE dual interface prevention -if interface1_type == 'ble' and interface2_type == 'ble': - logger.critical(f"System: BLE Interface1 and Interface2 cannot both be BLE. Exiting") +ble_count = sum(1 for i in range(1, 10) if globals().get(f'interface{i}_type') == 'ble') +if ble_count > 1: + logger.critical(f"System: Multiple BLE interfaces detected. Only one BLE interface is allowed. Exiting") exit() -#initialize_interfaces(): -# Interface1 Configuration -try: - logger.debug(f"System: Initializing Interface1") - if interface1_type == 'serial': - interface1 = meshtastic.serial_interface.SerialInterface(port1) - elif interface1_type == 'tcp': - interface1 = meshtastic.tcp_interface.TCPInterface(hostname1) - elif interface1_type == 'ble': - interface1 = meshtastic.ble_interface.BLEInterface(mac1) +# Initialize interfaces +interface1 = interface2 = interface3 = interface4 = interface5 = interface6 = interface7 = interface8 = interface9 = None +for i in range(1, 10): + interface_type = globals().get(f'interface{i}_type') + if not interface_type or interface_type == 'none' or globals().get(f'interface{i}_enabled') == False: + # no valid interface found + continue + try: + if globals().get(f'interface{i}_enabled'): + if interface_type == 'serial': + globals()[f'interface{i}'] = meshtastic.serial_interface.SerialInterface(globals().get(f'port{i}')) + elif interface_type == 'tcp': + globals()[f'interface{i}'] = meshtastic.tcp_interface.TCPInterface(globals().get(f'hostname{i}')) + elif interface_type == 'ble': + globals()[f'interface{i}'] = meshtastic.ble_interface.BLEInterface(globals().get(f'mac{i}')) + else: + logger.critical(f"System: Interface Type: {interface_type} not supported. Validate your config against config.template Exiting") + exit() + except Exception as e: + logger.critical(f"System: abort. Initializing Interface{i} {e}") + exit() + +# Get the node number of the devices, check if the devices are connected meshtastic devices +for i in range(1, 10): + if globals().get(f'interface{i}'): + if globals().get(f'interface{i}_enabled'): + try: + globals()[f'myNodeNum{i}'] = globals()[f'interface{i}'].getMyNodeInfo()['num'] + logger.debug(f"System: Initalized Radio Device{i} Node Number: {globals()[f'myNodeNum{i}']}") + except Exception as e: + logger.critical(f"System: critical error initializing interface{i} {e}") else: - logger.critical(f"System: Interface Type: {interface1_type} not supported. Validate your config against config.template Exiting") - exit() -except Exception as e: - logger.critical(f"System: script abort. Initializing Interface1 {e}") - exit() - -# Interface2 Configuration -if interface2_enabled: - logger.debug(f"System: Initializing Interface2") - try: - if interface2_type == 'serial': - interface2 = meshtastic.serial_interface.SerialInterface(port2) - elif interface2_type == 'tcp': - interface2 = meshtastic.tcp_interface.TCPInterface(hostname2) - elif interface2_type == 'ble': - interface2 = meshtastic.ble_interface.BLEInterface(mac2) - else: - logger.critical(f"System: Interface Type: {interface2_type} not supported. Validate your config against config.template Exiting") - exit() - except Exception as e: - logger.critical(f"System: script abort. Initializing Interface2 {e}") - exit() - -#Get the node number of the device, check if the device is connected -try: - myinfo = interface1.getMyNodeInfo() - myNodeNum1 = myinfo['num'] -except Exception as e: - logger.critical(f"System: script abort. {e}") - exit() - -if interface2_enabled: - try: - myinfo2 = interface2.getMyNodeInfo() - myNodeNum2 = myinfo2['num'] - except Exception as e: - logger.critical(f"System: script abort. {e}") - exit() -else: - myNodeNum2 = 777 - + globals()[f'myNodeNum{i}'] = 777 #### FUN-ctions #### @@ -271,7 +253,7 @@ def decimal_to_hex(decimal_number): return f"!{decimal_number:08x}" def get_name_from_number(number, type='long', nodeInt=1): - interface = interface1 if nodeInt == 1 else interface2 + interface = globals()[f'interface{nodeInt}'] name = "" for node in interface.nodes.values(): @@ -288,7 +270,7 @@ def get_name_from_number(number, type='long', nodeInt=1): def get_num_from_short_name(short_name, nodeInt=1): - interface = interface1 if nodeInt == 1 else interface2 + interface = globals()[f'interface{nodeInt}'] # Get the node number from the short name, converting all to lowercase for comparison (good practice?) logger.debug(f"System: Getting Node Number from Short Name: {short_name} on Device: {nodeInt}") for node in interface.nodes.values(): @@ -308,7 +290,7 @@ def get_num_from_short_name(short_name, nodeInt=1): return 0 def get_node_list(nodeInt=1): - interface = interface1 if nodeInt == 1 else interface2 + interface = globals()[f'interface{nodeInt}'] # Get a list of nodes on the device node_list = "" node_list1 = [] @@ -362,7 +344,7 @@ def get_node_list(nodeInt=1): return node_list def get_node_location(number, nodeInt=1, channel=0): - interface = interface1 if nodeInt == 1 else interface2 + interface = globals()[f'interface{nodeInt}'] # Get the location of a node by its number from nodeDB on device latitude = latitudeValue longitude = longitudeValue @@ -395,7 +377,7 @@ def get_node_location(number, nodeInt=1, channel=0): def get_closest_nodes(nodeInt=1,returnCount=3): - interface = interface1 if nodeInt == 1 else interface2 + interface = globals()[f'interface{nodeInt}'] node_list = [] if interface.nodes: @@ -509,7 +491,7 @@ def messageChunker(message): def send_message(message, ch, nodeid=0, nodeInt=1, bypassChuncking=False): # Send a message to a channel or DM - interface = interface1 if nodeInt == 1 else interface2 + interface = globals()[f'interface{nodeInt}'] # Check if the message is empty if message == "" or message == None or len(message) == 0: return False @@ -784,7 +766,7 @@ def initialize_telemetryData(): initialize_telemetryData() def getNodeFirmware(nodeID=0, nodeInt=1): - interface = interface1 if nodeInt == 1 else interface2 + interface = globals()[f'interface{nodeInt}'] # get the firmware version of the node # this is a workaround because .localNode.getMetadata spits out a lot of debug info which cant be suppressed # Create a StringIO object to capture the diff --git a/modules/web.py b/modules/web.py new file mode 100644 index 0000000..59d76c4 --- /dev/null +++ b/modules/web.py @@ -0,0 +1 @@ +# investigate python3 -m http.server to serve up reports and config data? From b78cf4d0222142599fd0bdd7e7ee90521a05cf6b Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 13:18:21 -0800 Subject: [PATCH 02/24] Update system.py --- modules/system.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/system.py b/modules/system.py index aa9d6b8..c419507 100644 --- a/modules/system.py +++ b/modules/system.py @@ -214,6 +214,7 @@ if ble_count > 1: exit() # Initialize interfaces +logger.debug(f"System: Initializing Interfaces") interface1 = interface2 = interface3 = interface4 = interface5 = interface6 = interface7 = interface8 = interface9 = None for i in range(1, 10): interface_type = globals().get(f'interface{i}_type') From c7b7b182b95897b03bc13fd0695bfb5285a808a6 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 13:36:24 -0800 Subject: [PATCH 03/24] Update system.py --- modules/system.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/modules/system.py b/modules/system.py index c419507..907beba 100644 --- a/modules/system.py +++ b/modules/system.py @@ -281,6 +281,7 @@ def get_num_from_short_name(short_name, nodeInt=1): elif str(short_name.lower()) == node['user']['shortName'].lower(): return node['num'] else: + # TODO check the other interface if interface2_enabled: interface = interface2 if nodeInt == 1 else interface1 # check the other interface for node in interface.nodes.values(): @@ -301,7 +302,7 @@ def get_node_list(nodeInt=1): if interface.nodes: for node in interface.nodes.values(): # ignore own - if node['num'] != myNodeNum2 and node['num'] != myNodeNum1: + if all(node['num'] != globals().get(f'myNodeNum{i}', 777) for i in range(1, 10)): node_name = get_name_from_number(node['num'], 'short', nodeInt) snr = node.get('snr', 0) @@ -399,7 +400,7 @@ def get_closest_nodes(nodeInt=1,returnCount=3): distance = round(geopy.distance.geodesic((latitudeValue, longitudeValue), (latitude, longitude)).m, 2) if (distance < sentry_radius): - if nodeID != myNodeNum1 and myNodeNum2 and str(nodeID) not in sentryIgnoreList: + if (nodeID not in [globals().get(f'myNodeNum{i}', 777) for i in range(1, 10)]) and str(nodeID) not in sentryIgnoreList: node_list.append({'id': nodeID, 'latitude': latitude, 'longitude': longitude, 'distance': distance}) except Exception as e: @@ -781,18 +782,15 @@ def getNodeFirmware(nodeID=0, nodeInt=1): return -1 def displayNodeTelemetry(nodeID=0, rxNode=0, userRequested=False): - interface = interface1 if rxNode == 1 else interface2 + interface = globals()[f'interface{rxNode}'] + myNodeNum = globals().get(f'myNodeNum{rxNode}', 777) global telemetryData # throttle the telemetry requests to prevent spamming the device - if rxNode == 1: - if time.time() - telemetryData[0]['interface1'] < 600 and not userRequested: + if 1 <= rxNode <= 9: + if time.time() - telemetryData[0][f'interface{rxNode}'] < 600 and not userRequested: return -1 - telemetryData[0]['interface1'] = time.time() - elif rxNode == 2: - if time.time() - telemetryData[0]['interface2'] < 600 and not userRequested: - return -1 - telemetryData[0]['interface2'] = time.time() + telemetryData[0][f'interface{rxNode}'] = time.time() # some telemetry data is not available in python-meshtastic? # bring in values from the last telemetry dump for the node @@ -804,13 +802,13 @@ def displayNodeTelemetry(nodeID=0, rxNode=0, userRequested=False): totalOnlineNodes = telemetryData[rxNode]['numOnlineNodes'] # get the telemetry data for a node - chutil = round(interface.nodes.get(decimal_to_hex(myNodeNum1), {}).get("deviceMetrics", {}).get("channelUtilization", 0), 1) - airUtilTx = round(interface.nodes.get(decimal_to_hex(myNodeNum1), {}).get("deviceMetrics", {}).get("airUtilTx", 0), 1) - uptimeSeconds = interface.nodes.get(decimal_to_hex(myNodeNum1), {}).get("deviceMetrics", {}).get("uptimeSeconds", 0) - batteryLevel = interface.nodes.get(decimal_to_hex(myNodeNum1), {}).get("deviceMetrics", {}).get("batteryLevel", 0) - voltage = interface.nodes.get(decimal_to_hex(myNodeNum1), {}).get("deviceMetrics", {}).get("voltage", 0) - #numPacketsRx = interface.nodes.get(decimal_to_hex(myNodeNum1), {}).get("localStats", {}).get("numPacketsRx", 0) - #numPacketsTx = interface.nodes.get(decimal_to_hex(myNodeNum1), {}).get("localStats", {}).get("numPacketsTx", 0) + chutil = round(interface.nodes.get(decimal_to_hex(myNodeNum), {}).get("deviceMetrics", {}).get("channelUtilization", 0), 1) + airUtilTx = round(interface.nodes.get(decimal_to_hex(myNodeNum), {}).get("deviceMetrics", {}).get("airUtilTx", 0), 1) + uptimeSeconds = interface.nodes.get(decimal_to_hex(myNodeNum), {}).get("deviceMetrics", {}).get("uptimeSeconds", 0) + batteryLevel = interface.nodes.get(decimal_to_hex(myNodeNum), {}).get("deviceMetrics", {}).get("batteryLevel", 0) + voltage = interface.nodes.get(decimal_to_hex(myNodeNum), {}).get("deviceMetrics", {}).get("voltage", 0) + #numPacketsRx = interface.nodes.get(decimal_to_hex(myNodeNum), {}).get("localStats", {}).get("numPacketsRx", 0) + #numPacketsTx = interface.nodes.get(decimal_to_hex(myNodeNum), {}).get("localStats", {}).get("numPacketsTx", 0) numTotalNodes = len(interface.nodes) dataResponse = f"Telemetry:{rxNode}" @@ -1015,8 +1013,8 @@ async def handleFileWatcher(): async def retry_interface(nodeID=1): global interface1, interface2, retry_int1, retry_int2, max_retry_count1, max_retry_count2 - interface = interface1 if nodeID == 1 else interface2 - retry_int = retry_int1 if nodeID == 1 else retry_int2 + interface = globals()[f'interface{nodeID}'] + retry_int = globals()[f'interface{nodeID}'] # retry connecting to the interface # add a check to see if the interface is already open or trying to open if interface is not None: From ab54dc06d789559dffd5a3713c8ab5e8be52868a Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 14:02:30 -0800 Subject: [PATCH 04/24] enhance --- mesh_bot.py | 29 ++++++++++++----------------- modules/system.py | 19 +++++++++---------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index a99d869..a7de28f 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -134,6 +134,7 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, channel_number): global multiPing + myNodeNum = globals().get(f'myNodeNum{deviceID}', 777) if "?" in message and isDM: return message.split("?")[0].title() + " command returns SNR and RSSI, or hopcount from your message. Try adding e.g. @place or #tag" @@ -153,10 +154,7 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, chann msg = random.choice(["✋ACK-ACK!\n", "✋Ack to you!\n"]) type = "✋ACK" elif "cqcq" in message.lower() or "cq" in message.lower() or "cqcqcq" in message.lower(): - if deviceID == 1: - myname = get_name_from_number(myNodeNum1, 'short', 1) - elif deviceID == 2: - myname = get_name_from_number(myNodeNum2, 'short', 2) + myname = get_name_from_number(myNodeNum, 'short', deviceID) msg = f"QSP QSL OM DE {myname} K\n" else: msg = "🔊 Can you hear me now?" @@ -221,6 +219,7 @@ def handle_alertBell(message_from_id, deviceID, message): return random.choice(msg) def handle_emergency(message_from_id, deviceID, message): + myNodeNum = globals().get(f'myNodeNum{deviceID}', 777) # if user in bbs_ban_list return if str(message_from_id) in bbs_ban_list: # silent discard @@ -228,13 +227,11 @@ def handle_emergency(message_from_id, deviceID, message): return '' # trgger alert to emergency_responder_alert_channel if message_from_id != 0: - if deviceID == 1: rxNode = myNodeNum1 - elif deviceID == 2: rxNode = myNodeNum2 nodeLocation = get_node_location(message_from_id, deviceID) # if default location is returned set to Unknown if nodeLocation[0] == latitudeValue and nodeLocation[1] == longitudeValue: nodeLocation = ["?", "?"] - nodeInfo = f"{get_name_from_number(message_from_id, 'short', deviceID)} detected by {get_name_from_number(rxNode, 'short', deviceID)} lastGPS {nodeLocation[0]}, {nodeLocation[1]}" + nodeInfo = f"{get_name_from_number(message_from_id, 'short', deviceID)} detected by {get_name_from_number(myNodeNum, 'short', deviceID)} lastGPS {nodeLocation[0]}, {nodeLocation[1]}" msg = f"🔔🚨Intercepted Possible Emergency Assistance needed for: {nodeInfo}" # alert the emergency_responder_alert_channel time.sleep(responseDelay) @@ -1048,7 +1045,7 @@ def onReceive(packet, interface): message_string = message_bytes.decode('utf-8') # check if the packet is from us - if message_from_id == myNodeNum1 or message_from_id == myNodeNum2: + if message_from_id in [myNodeNum1, myNodeNum2, myNodeNum3, myNodeNum4, myNodeNum5, myNodeNum6, myNodeNum7, myNodeNum8, myNodeNum9]: logger.warning(f"System: Packet from self {message_from_id} loop or traffic replay deteted") # get the signal strength and snr if available @@ -1110,7 +1107,7 @@ def onReceive(packet, interface): return # If the packet is a DM (Direct Message) respond to it, otherwise validate its a message for us on the channel - if packet['to'] == myNodeNum1 or packet['to'] == myNodeNum2: + if packet['to'] in [myNodeNum1, myNodeNum2, myNodeNum3, myNodeNum4, myNodeNum5, myNodeNum6, myNodeNum7, myNodeNum8, myNodeNum9]: # message is DM to us isDM = True # check if the message contains a trap word, DMs are always responded to @@ -1234,17 +1231,15 @@ async def start_rx(): pub.subscribe(onReceive, 'meshtastic.receive') pub.subscribe(onDisconnect, 'meshtastic.connection.lost') - logger.info(f"System: Autoresponder Started for Device1 {get_name_from_number(myNodeNum1, 'long', 1)}," - f"{get_name_from_number(myNodeNum1, 'short', 1)}. NodeID: {myNodeNum1}, {decimal_to_hex(myNodeNum1)}") + for i in range(1, 10): + if globals().get(f'interface{i}_enabled', False): + myNodeNum = globals().get(f'myNodeNum{i}', 0) + logger.info(f"System: Autoresponder Started for Device{i} {get_name_from_number(myNodeNum, 'long', i)}," + f"{get_name_from_number(myNodeNum, 'short', i)}. NodeID: {myNodeNum}, {decimal_to_hex(myNodeNum)}") - - if interface2_enabled: - logger.info(f"System: Autoresponder Started for Device2 {get_name_from_number(myNodeNum2, 'long', 2)}," - f"{get_name_from_number(myNodeNum2, 'short', 2)}. NodeID: {myNodeNum2}, {decimal_to_hex(myNodeNum2)}") - if llm_enabled: logger.debug(f"System: Ollama LLM Enabled, loading model {llmModel} please wait") - llm_query(" ", myNodeNum1) + llm_query(" ") logger.debug(f"System: LLM model {llmModel} loaded") if log_messages_to_file: diff --git a/modules/system.py b/modules/system.py index 907beba..c64ad16 100644 --- a/modules/system.py +++ b/modules/system.py @@ -238,13 +238,12 @@ for i in range(1, 10): # Get the node number of the devices, check if the devices are connected meshtastic devices for i in range(1, 10): - if globals().get(f'interface{i}'): - if globals().get(f'interface{i}_enabled'): - try: - globals()[f'myNodeNum{i}'] = globals()[f'interface{i}'].getMyNodeInfo()['num'] - logger.debug(f"System: Initalized Radio Device{i} Node Number: {globals()[f'myNodeNum{i}']}") - except Exception as e: - logger.critical(f"System: critical error initializing interface{i} {e}") + if globals().get(f'interface{i}') and globals().get(f'interface{i}_enabled'): + try: + globals()[f'myNodeNum{i}'] = globals()[f'interface{i}'].getMyNodeInfo()['num'] + logger.debug(f"System: Initalized Radio Device{i} Node Number: {globals()[f'myNodeNum{i}']}") + except Exception as e: + logger.critical(f"System: critical error initializing interface{i} {e}") else: globals()[f'myNodeNum{i}'] = 777 @@ -302,7 +301,7 @@ def get_node_list(nodeInt=1): if interface.nodes: for node in interface.nodes.values(): # ignore own - if all(node['num'] != globals().get(f'myNodeNum{i}', 777) for i in range(1, 10)): + if all(node['num'] != globals().get(f'myNodeNum{i}') for i in range(1, 10)): node_name = get_name_from_number(node['num'], 'short', nodeInt) snr = node.get('snr', 0) @@ -400,7 +399,7 @@ def get_closest_nodes(nodeInt=1,returnCount=3): distance = round(geopy.distance.geodesic((latitudeValue, longitudeValue), (latitude, longitude)).m, 2) if (distance < sentry_radius): - if (nodeID not in [globals().get(f'myNodeNum{i}', 777) for i in range(1, 10)]) and str(nodeID) not in sentryIgnoreList: + if (nodeID not in [globals().get(f'myNodeNum{i}') for i in range(1, 10)]) and str(nodeID) not in sentryIgnoreList: node_list.append({'id': nodeID, 'latitude': latitude, 'longitude': longitude, 'distance': distance}) except Exception as e: @@ -783,7 +782,7 @@ def getNodeFirmware(nodeID=0, nodeInt=1): def displayNodeTelemetry(nodeID=0, rxNode=0, userRequested=False): interface = globals()[f'interface{rxNode}'] - myNodeNum = globals().get(f'myNodeNum{rxNode}', 777) + myNodeNum = globals().get(f'myNodeNum{rxNode}') global telemetryData # throttle the telemetry requests to prevent spamming the device From f917df709c18f070ec3d8b4eafa349a210f6a2d6 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 14:58:48 -0800 Subject: [PATCH 05/24] refactorWatchDog --- modules/system.py | 171 +++++++++++++++------------------------------- 1 file changed, 55 insertions(+), 116 deletions(-) diff --git a/modules/system.py b/modules/system.py index c64ad16..f99fd8b 100644 --- a/modules/system.py +++ b/modules/system.py @@ -216,6 +216,7 @@ if ble_count > 1: # Initialize interfaces logger.debug(f"System: Initializing Interfaces") interface1 = interface2 = interface3 = interface4 = interface5 = interface6 = interface7 = interface8 = interface9 = None +retry_int1 = retry_int2 = retry_int3 = retry_int4 = retry_int5 = retry_int6 = retry_int7 = retry_int8 = retry_int9 = False for i in range(1, 10): interface_type = globals().get(f'interface{i}_type') if not interface_type or interface_type == 'none' or globals().get(f'interface{i}_enabled') == False: @@ -712,30 +713,18 @@ def handleAlertBroadcast(deviceID=1): return True def onDisconnect(interface): - global retry_int1, retry_int2 + global retry_int1, retry_int2, retry_int3, retry_int4, retry_int5, retry_int6, retry_int7, retry_int8, retry_int9 rxType = type(interface).__name__ - if rxType == 'SerialInterface': - rxInterface = interface.__dict__.get('devPath', 'unknown') - logger.critical("System: Lost Connection to Device {rxInterface}") - if port1 in rxInterface: - retry_int1 = True - elif interface2_enabled and port2 in rxInterface: - retry_int2 = True - - if rxType == 'TCPInterface': - rxHost = interface.__dict__.get('hostname', 'unknown') - logger.critical("System: Lost Connection to Device {rxHost}") - if hostname1 in rxHost and interface1_type == 'tcp': - retry_int1 = True - elif interface2_enabled and hostname2 in rxHost and interface2_type == 'tcp': - retry_int2 = True - - if rxType == 'BLEInterface': - logger.critical("System: Lost Connection to Device BLE") - if interface1_type == 'ble': - retry_int1 = True - elif interface2_enabled and interface2_type == 'ble': - retry_int2 = True + if rxType in ['SerialInterface', 'TCPInterface', 'BLEInterface']: + identifier = interface.__dict__.get('devPath', interface.__dict__.get('hostname', 'BLE')) + logger.critical(f"System: Lost Connection to Device {identifier}") + for i in range(1, 10): + if globals().get(f'interface{i}_enabled'): + if (rxType == 'SerialInterface' and globals().get(f'port{i}') in identifier) or \ + (rxType == 'TCPInterface' and globals().get(f'hostname{i}') in identifier) or \ + (rxType == 'BLEInterface' and globals().get(f'interface{i}_type') == 'ble'): + globals()[f'retry_int{i}'] = True + break def exit_handler(): # Close the interface and save the BBS messages @@ -1010,77 +999,66 @@ async def handleFileWatcher(): await asyncio.sleep(1) pass -async def retry_interface(nodeID=1): - global interface1, interface2, retry_int1, retry_int2, max_retry_count1, max_retry_count2 +async def retry_interface(nodeID): + global max_retry_count interface = globals()[f'interface{nodeID}'] - retry_int = globals()[f'interface{nodeID}'] - # retry connecting to the interface - # add a check to see if the interface is already open or trying to open + retry_int = globals()[f'retry_int{nodeID}'] + max_retry_count = globals()[f'max_retry_count{nodeID}'] + if interface is not None: retry_int = True - max_retry_count1 -= 1 + max_retry_count -= 1 try: interface.close() except Exception as e: logger.error(f"System: closing interface{nodeID}: {e}") - + logger.debug(f"System: Retrying interface{nodeID} in 15 seconds") - if max_retry_count1 == 0: - logger.critical(f"System: Max retry count reached for interface1") + if max_retry_count == 0: + logger.critical(f"System: Max retry count reached for interface{nodeID}") exit_handler() - if max_retry_count2 == 0: - logger.critical(f"System: Max retry count reached for interface2") - exit_handler() - # wait 15 seconds before retrying + await asyncio.sleep(15) - # retry the interface try: if retry_int: interface = None - if nodeID == 1: - interface1 = None - if nodeID == 2: - interface2 = None + globals()[f'interface{nodeID}'] = None logger.debug(f"System: Retrying Interface{nodeID}") - interface_type = interface1_type if nodeID == 1 else interface2_type + interface_type = globals()[f'interface{nodeID}_type'] if interface_type == 'serial': - interface1 = meshtastic.serial_interface.SerialInterface(port1) + globals()[f'interface{nodeID}'] = meshtastic.serial_interface.SerialInterface(globals().get(f'port{nodeID}')) elif interface_type == 'tcp': - interface1 = meshtastic.tcp_interface.TCPInterface(hostname1) + globals()[f'interface{nodeID}'] = meshtastic.tcp_interface.TCPInterface(globals().get(f'hostname{nodeID}')) elif interface_type == 'ble': - interface1 = meshtastic.ble_interface.BLEInterface(mac1) - logger.debug(f"System: Interface1 Opened!") - retry_int1 = False + globals()[f'interface{nodeID}'] = meshtastic.ble_interface.BLEInterface(globals().get(f'mac{nodeID}')) + logger.debug(f"System: Interface{nodeID} Opened!") + globals()[f'retry_int{nodeID}'] = False except Exception as e: logger.error(f"System: Error Opening interface{nodeID} on: {e}") - handleSentinel_spotted = "" handleSentinel_loop = 0 -async def handleSentinel(deviceID=1): +async def handleSentinel(deviceID): global handleSentinel_spotted, handleSentinel_loop - # Locate Closest Nodes and report them to a secure channel - # async function for possibly demanding back location data enemySpotted = "" resolution = "unknown" closest_nodes = get_closest_nodes(deviceID) if closest_nodes != ERROR_FETCHING_DATA and closest_nodes: if closest_nodes[0]['id'] is not None: - enemySpotted = get_name_from_number(closest_nodes[0]['id'], 'long', 1) - enemySpotted += ", " + get_name_from_number(closest_nodes[0]['id'], 'short', 1) + enemySpotted = get_name_from_number(closest_nodes[0]['id'], 'long', deviceID) + enemySpotted += ", " + get_name_from_number(closest_nodes[0]['id'], 'short', deviceID) enemySpotted += ", " + str(closest_nodes[0]['id']) enemySpotted += ", " + decimal_to_hex(closest_nodes[0]['id']) enemySpotted += f" at {closest_nodes[0]['distance']}m" - + if handleSentinel_loop >= sentry_holdoff and handleSentinel_spotted != enemySpotted: - # check the positionMetadata for nodeID and get metadata if closest_nodes and positionMetadata and closest_nodes[0]['id'] in positionMetadata: metadata = positionMetadata[closest_nodes[0]['id']] if metadata.get('precisionBits') is not None: resolution = metadata.get('precisionBits') - - logger.warning(f"System: {enemySpotted} is close to your location on Interface1 Accuracy is {resolution}bits") + + logger.warning(f"System: {enemySpotted} is close to your location on Interface{deviceID} Accuracy is {resolution}bits") send_message(f"Sentry{deviceID}: {enemySpotted}", secure_channel, 0, deviceID) if enableSMTP and email_sentry_alerts: for email in sysopEmails: @@ -1091,76 +1069,37 @@ async def handleSentinel(deviceID=1): handleSentinel_loop += 1 async def watchdog(): - global retry_int1, retry_int2, telemetryData - int1Data, int2Data = "", "" + global telemetryData, retry_int1, retry_int2, retry_int3, retry_int4, retry_int5, retry_int6, retry_int7, retry_int8, retry_int9 while True: await asyncio.sleep(20) - #print(f"MeshBot System: watchdog running\r", end="") - if interface1 is not None and not retry_int1: - # getting firmware is a heartbeat to check if the interface is still connected - try: - firmware = getNodeFirmware(0, 1) - except Exception as e: - logger.error(f"System: communicating with interface1, trying to reconnect: {e}") - retry_int1 = True - - if not retry_int1: - # Locate Closest Nodes and report them to a secure channel - if sentry_enabled: - await handleSentinel(1) - - # multiPing handler - handleMultiPing(0,1) - - # Alert Broadcast - if wxAlertBroadcastEnabled or emergencyAlertBrodcastEnabled: - # weather alerts - handleAlertBroadcast(1) - - # Telemetry data - int1Data = displayNodeTelemetry(0, 1) - if int1Data != -1 and telemetryData[0]['lastAlert1'] != int1Data: - logger.debug(int1Data + f" Firmware:{firmware}") - telemetryData[0]['lastAlert1'] = int1Data - - if retry_int1: - try: - await retry_interface(1) - except Exception as e: - logger.error(f"System: retrying interface1: {e}") - - if interface2_enabled: - if interface2 is not None and not retry_int2: - # getting firmware is a heartbeat to check if the interface is still connected + for i in range(1, 10): + interface = globals().get(f'interface{i}') + retry_int = globals().get(f'retry_int{i}') + if interface is not None and not retry_int: try: - firmware2 = getNodeFirmware(0, 1) + firmware = getNodeFirmware(0, i) except Exception as e: - logger.error(f"System: communicating with interface1, trying to reconnect: {e}") - retry_int2 = True + logger.error(f"System: communicating with interface{i}, trying to reconnect: {e}") + globals()[f'retry_int{i}'] = True - if not retry_int2: - # Locate Closest Nodes and report them to a secure channel + if not globals()[f'retry_int{i}']: if sentry_enabled: - await handleSentinel(2) + await handleSentinel(i) - # multiPing handler - handleMultiPing(0,1) + handleMultiPing(0, i) - # Alert Broadcast if wxAlertBroadcastEnabled or emergencyAlertBrodcastEnabled: - # weather alerts - handleAlertBroadcast(1) + handleAlertBroadcast(i) - # Telemetry data - int2Data = displayNodeTelemetry(0, 2) - if int2Data != -1 and telemetryData[0]['lastAlert2'] != int2Data: - logger.debug(int2Data + f" Firmware:{firmware2}") - telemetryData[0]['lastAlert2'] = int2Data - - if retry_int2: + intData = displayNodeTelemetry(0, i) + if intData != -1 and telemetryData[0][f'lastAlert{i}'] != intData: + logger.debug(intData + f" Firmware:{firmware}") + telemetryData[0][f'lastAlert{i}'] = intData + + if globals()[f'retry_int{i}'] and globals()[f'interface{i}_enabled']: try: - await retry_interface(2) + await retry_interface(i) except Exception as e: - logger.error(f"System: retrying interface2: {e}") + logger.error(f"System: retrying interface{i}: {e}") From 868009b650db8f55df7e1655e518f5910510a8e1 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 15:07:54 -0800 Subject: [PATCH 06/24] Update system.py --- modules/system.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/system.py b/modules/system.py index f99fd8b..6b1fa97 100644 --- a/modules/system.py +++ b/modules/system.py @@ -1060,6 +1060,9 @@ async def handleSentinel(deviceID): logger.warning(f"System: {enemySpotted} is close to your location on Interface{deviceID} Accuracy is {resolution}bits") send_message(f"Sentry{deviceID}: {enemySpotted}", secure_channel, 0, deviceID) + for i in range(1, 10): + if globals().get(f'interface{i}_enabled'): + send_message(f"Sentry{deviceID}: {enemySpotted}", secure_channel, 0, i) if enableSMTP and email_sentry_alerts: for email in sysopEmails: send_email(email, f"Sentry{deviceID}: {enemySpotted}") From b16d9322e3251282d85105f213af19dbbc366b95 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 15:21:20 -0800 Subject: [PATCH 07/24] Update system.py --- modules/system.py | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/system.py b/modules/system.py index 6b1fa97..4377f1d 100644 --- a/modules/system.py +++ b/modules/system.py @@ -1059,7 +1059,6 @@ async def handleSentinel(deviceID): resolution = metadata.get('precisionBits') logger.warning(f"System: {enemySpotted} is close to your location on Interface{deviceID} Accuracy is {resolution}bits") - send_message(f"Sentry{deviceID}: {enemySpotted}", secure_channel, 0, deviceID) for i in range(1, 10): if globals().get(f'interface{i}_enabled'): send_message(f"Sentry{deviceID}: {enemySpotted}", secure_channel, 0, i) From 0ac683b5c0c15f409d036ff0a0f4d1b6b4d0c8fc Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 15:33:03 -0800 Subject: [PATCH 08/24] Update locationdata.py --- modules/locationdata.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/locationdata.py b/modules/locationdata.py index 421c821..1594caf 100644 --- a/modules/locationdata.py +++ b/modules/locationdata.py @@ -357,11 +357,16 @@ def getWeatherAlertsNOAA(lat=0, lon=0, useDefaultLatLon=False): alerts = "" alertxml = xml.dom.minidom.parseString(alert_data.text) - + #old for i in alertxml.getElementsByTagName("entry"): alerts += ( i.getElementsByTagName("title")[0].childNodes[0].nodeValue + "\n" ) + #new + # for i in alertxml.getElementsByTagName("entry"): + # title = i.getElementsByTagName("title")[0].childNodes[0].nodeValue + # area_desc = i.getElementsByTagName("cap:areaDesc")[0].childNodes[0].nodeValue + # alerts += f"{title}\nArea: {area_desc}\n\n" if alerts == "" or alerts == None: return NO_ALERTS From d163bffba67094f74f71246fa5f6bb3d35de49a5 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 15:35:22 -0800 Subject: [PATCH 09/24] Update locationdata.py --- modules/locationdata.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/modules/locationdata.py b/modules/locationdata.py index 1594caf..01bacf1 100644 --- a/modules/locationdata.py +++ b/modules/locationdata.py @@ -357,16 +357,10 @@ def getWeatherAlertsNOAA(lat=0, lon=0, useDefaultLatLon=False): alerts = "" alertxml = xml.dom.minidom.parseString(alert_data.text) - #old for i in alertxml.getElementsByTagName("entry"): - alerts += ( - i.getElementsByTagName("title")[0].childNodes[0].nodeValue + "\n" - ) - #new - # for i in alertxml.getElementsByTagName("entry"): - # title = i.getElementsByTagName("title")[0].childNodes[0].nodeValue - # area_desc = i.getElementsByTagName("cap:areaDesc")[0].childNodes[0].nodeValue - # alerts += f"{title}\nArea: {area_desc}\n\n" + title = i.getElementsByTagName("title")[0].childNodes[0].nodeValue + area_desc = i.getElementsByTagName("cap:areaDesc")[0].childNodes[0].nodeValue + alerts += f"{title}\nArea: {area_desc}\n\n" if alerts == "" or alerts == None: return NO_ALERTS From 9aaebaad62c0f49fa913c61435b7d229a8d50a6f Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 15:36:37 -0800 Subject: [PATCH 10/24] Update locationdata.py --- modules/locationdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/locationdata.py b/modules/locationdata.py index 01bacf1..177ff37 100644 --- a/modules/locationdata.py +++ b/modules/locationdata.py @@ -360,7 +360,7 @@ def getWeatherAlertsNOAA(lat=0, lon=0, useDefaultLatLon=False): for i in alertxml.getElementsByTagName("entry"): title = i.getElementsByTagName("title")[0].childNodes[0].nodeValue area_desc = i.getElementsByTagName("cap:areaDesc")[0].childNodes[0].nodeValue - alerts += f"{title}\nArea: {area_desc}\n\n" + alerts += f"{title}\nArea: {area_desc}\n" if alerts == "" or alerts == None: return NO_ALERTS From 502a4f266693148c8241a4b70600c3dfd242ca78 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 15:37:41 -0800 Subject: [PATCH 11/24] Update locationdata.py --- modules/locationdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/locationdata.py b/modules/locationdata.py index 177ff37..a5f2f1e 100644 --- a/modules/locationdata.py +++ b/modules/locationdata.py @@ -360,7 +360,7 @@ def getWeatherAlertsNOAA(lat=0, lon=0, useDefaultLatLon=False): for i in alertxml.getElementsByTagName("entry"): title = i.getElementsByTagName("title")[0].childNodes[0].nodeValue area_desc = i.getElementsByTagName("cap:areaDesc")[0].childNodes[0].nodeValue - alerts += f"{title}\nArea: {area_desc}\n" + alerts += f"{title}. {area_desc}\n" if alerts == "" or alerts == None: return NO_ALERTS From 725cbd8045a0d3b41f3561b2cfb7053e3009d678 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 16:01:12 -0800 Subject: [PATCH 12/24] Update locationdata.py --- modules/locationdata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/locationdata.py b/modules/locationdata.py index a5f2f1e..78cd6d6 100644 --- a/modules/locationdata.py +++ b/modules/locationdata.py @@ -360,7 +360,7 @@ def getWeatherAlertsNOAA(lat=0, lon=0, useDefaultLatLon=False): for i in alertxml.getElementsByTagName("entry"): title = i.getElementsByTagName("title")[0].childNodes[0].nodeValue area_desc = i.getElementsByTagName("cap:areaDesc")[0].childNodes[0].nodeValue - alerts += f"{title}. {area_desc}\n" + alerts += f"{title}. {area_desc.replace(' ', '')}\n" if alerts == "" or alerts == None: return NO_ALERTS From 45c67024e7c687ade6967122e033f242d9449ca7 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 16:19:59 -0800 Subject: [PATCH 13/24] enhanceSpotter --- modules/web.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/modules/web.py b/modules/web.py index 59d76c4..a84f2a9 100644 --- a/modules/web.py +++ b/modules/web.py @@ -1 +1,25 @@ -# investigate python3 -m http.server to serve up reports and config data? +#!/usr/bin/env python3 + +SSL = False +PORT = 8008 # python3 -m http.server 8008 + +import http.server +if SSL: + import ssl + +# boot up simple HTTP server +httpd = http.server.HTTPServer(('127.0.0.1', PORT), http.server.SimpleHTTPRequestHandler) + +if SSL: + # Generate with: openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes + ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + try: + ctx.load_cert_chain(certfile='./server.pem') + except FileNotFoundError: + print("SSL certificate file not found. Please generate it using the command provided in the comments.") + exit(1) + #ctx.load_cert_chain(certfile='server.crt', keyfile='server.key') + httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True) + + +httpd.serve_forever() \ No newline at end of file From f12fa0fe9bfae2438193d98232c4a4888048f2d4 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 16:20:17 -0800 Subject: [PATCH 14/24] enhance --- modules/system.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/system.py b/modules/system.py index 4377f1d..544e798 100644 --- a/modules/system.py +++ b/modules/system.py @@ -1037,36 +1037,36 @@ async def retry_interface(nodeID): except Exception as e: logger.error(f"System: Error Opening interface{nodeID} on: {e}") -handleSentinel_spotted = "" +handleSentinel_spotted = [] handleSentinel_loop = 0 async def handleSentinel(deviceID): global handleSentinel_spotted, handleSentinel_loop - enemySpotted = "" + detectedNearby = "" resolution = "unknown" closest_nodes = get_closest_nodes(deviceID) if closest_nodes != ERROR_FETCHING_DATA and closest_nodes: if closest_nodes[0]['id'] is not None: - enemySpotted = get_name_from_number(closest_nodes[0]['id'], 'long', deviceID) - enemySpotted += ", " + get_name_from_number(closest_nodes[0]['id'], 'short', deviceID) - enemySpotted += ", " + str(closest_nodes[0]['id']) - enemySpotted += ", " + decimal_to_hex(closest_nodes[0]['id']) - enemySpotted += f" at {closest_nodes[0]['distance']}m" + detectedNearby = get_name_from_number(closest_nodes[0]['id'], 'long', deviceID) + detectedNearby += ", " + get_name_from_number(closest_nodes[0]['id'], 'short', deviceID) + detectedNearby += ", " + str(closest_nodes[0]['id']) + detectedNearby += ", " + decimal_to_hex(closest_nodes[0]['id']) + detectedNearby += f" at {closest_nodes[0]['distance']}m" - if handleSentinel_loop >= sentry_holdoff and handleSentinel_spotted != enemySpotted: + if handleSentinel_loop >= sentry_holdoff and closest_nodes[0]['id'] not in handleSentinel_spotted: if closest_nodes and positionMetadata and closest_nodes[0]['id'] in positionMetadata: metadata = positionMetadata[closest_nodes[0]['id']] if metadata.get('precisionBits') is not None: resolution = metadata.get('precisionBits') - logger.warning(f"System: {enemySpotted} is close to your location on Interface{deviceID} Accuracy is {resolution}bits") + logger.warning(f"System: {detectedNearby} is close to your location on Interface{deviceID} Accuracy is {resolution}bits") for i in range(1, 10): if globals().get(f'interface{i}_enabled'): - send_message(f"Sentry{deviceID}: {enemySpotted}", secure_channel, 0, i) + send_message(f"Sentry{deviceID}: {detectedNearby}", secure_channel, 0, i) if enableSMTP and email_sentry_alerts: for email in sysopEmails: - send_email(email, f"Sentry{deviceID}: {enemySpotted}") + send_email(email, f"Sentry{deviceID}: {detectedNearby}") handleSentinel_loop = 0 - handleSentinel_spotted = enemySpotted + handleSentinel_spotted.append(closest_nodes[0]['id']) else: handleSentinel_loop += 1 From 1ff5895bad0572ef8f83c078e6b2da9544893227 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 16:39:00 -0800 Subject: [PATCH 15/24] reporting server @g7kse check this out --- modules/web.py | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/modules/web.py b/modules/web.py index a84f2a9..5cfa42d 100644 --- a/modules/web.py +++ b/modules/web.py @@ -1,25 +1,46 @@ #!/usr/bin/env python3 +# This is a simple web server that serves up the content of the webRoot directory +# The reporting data is all that is currently being served up +# TODO - add interaction to mesh? +# to use this today run it seperately and open a browser to http://localhost:8420 -SSL = False -PORT = 8008 # python3 -m http.server 8008 - +import os import http.server + +# Set the port for the server +PORT = 8420 + +# set webRoot index.html location +webRoot = "../etc/www" + +# Set to True to enable logging sdtout +webServerLogs = False + +# Generate with: openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes +SSL = False + if SSL: import ssl +# disable logging +class QuietHandler(http.server.SimpleHTTPRequestHandler): + def log_message(self, format, *args): + if webServerLogs: + super().log_message(format, *args) + +# Change the current working directory to webRoot +os.chdir(webRoot) + # boot up simple HTTP server -httpd = http.server.HTTPServer(('127.0.0.1', PORT), http.server.SimpleHTTPRequestHandler) +httpd = http.server.HTTPServer(('127.0.0.1', PORT), QuietHandler) if SSL: - # Generate with: openssl req -new -x509 -keyout server.pem -out server.pem -days 365 -nodes ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) try: ctx.load_cert_chain(certfile='./server.pem') except FileNotFoundError: print("SSL certificate file not found. Please generate it using the command provided in the comments.") exit(1) - #ctx.load_cert_chain(certfile='server.crt', keyfile='server.key') httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True) - httpd.serve_forever() \ No newline at end of file From af734ccb1f67401e2d55d8452274e960c638c8c1 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 17:01:07 -0800 Subject: [PATCH 16/24] enhanceSentry --- modules/system.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/modules/system.py b/modules/system.py index 544e798..2f2c9c3 100644 --- a/modules/system.py +++ b/modules/system.py @@ -1044,15 +1044,28 @@ async def handleSentinel(deviceID): detectedNearby = "" resolution = "unknown" closest_nodes = get_closest_nodes(deviceID) + closest_node = closest_nodes[0]['id'] if closest_nodes != ERROR_FETCHING_DATA and closest_nodes else None + closest_distance = closest_nodes[0]['distance'] if closest_nodes != ERROR_FETCHING_DATA and closest_nodes else None + + # check if the handleSentinel_spotted list contains the closest node already + if closest_node in [i['id'] for i in handleSentinel_spotted]: + # check if the distance is closer than the last time, if not just return + for i in range(len(handleSentinel_spotted)): + if handleSentinel_spotted[i]['id'] == closest_node and closest_distance < handleSentinel_spotted[i]['distance']: + handleSentinel_spotted[i]['distance'] = closest_distance + break + else: + return + if closest_nodes != ERROR_FETCHING_DATA and closest_nodes: if closest_nodes[0]['id'] is not None: - detectedNearby = get_name_from_number(closest_nodes[0]['id'], 'long', deviceID) + detectedNearby = get_name_from_number(closest_node, 'long', deviceID) detectedNearby += ", " + get_name_from_number(closest_nodes[0]['id'], 'short', deviceID) detectedNearby += ", " + str(closest_nodes[0]['id']) detectedNearby += ", " + decimal_to_hex(closest_nodes[0]['id']) - detectedNearby += f" at {closest_nodes[0]['distance']}m" + detectedNearby += f" at {closest_distance}m" - if handleSentinel_loop >= sentry_holdoff and closest_nodes[0]['id'] not in handleSentinel_spotted: + if handleSentinel_loop >= sentry_holdoff: if closest_nodes and positionMetadata and closest_nodes[0]['id'] in positionMetadata: metadata = positionMetadata[closest_nodes[0]['id']] if metadata.get('precisionBits') is not None: @@ -1066,7 +1079,7 @@ async def handleSentinel(deviceID): for email in sysopEmails: send_email(email, f"Sentry{deviceID}: {detectedNearby}") handleSentinel_loop = 0 - handleSentinel_spotted.append(closest_nodes[0]['id']) + handleSentinel_spotted.append({'id': closest_node, 'distance': closest_distance}) else: handleSentinel_loop += 1 From ae844f8ecd4a4751c4e9f6b5daabf5b58a032135 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 17:05:04 -0800 Subject: [PATCH 17/24] Update system.py --- modules/system.py | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/system.py b/modules/system.py index 2f2c9c3..7856218 100644 --- a/modules/system.py +++ b/modules/system.py @@ -1075,6 +1075,7 @@ async def handleSentinel(deviceID): for i in range(1, 10): if globals().get(f'interface{i}_enabled'): send_message(f"Sentry{deviceID}: {detectedNearby}", secure_channel, 0, i) + time.sleep(responseDelay + 1) if enableSMTP and email_sentry_alerts: for email in sysopEmails: send_email(email, f"Sentry{deviceID}: {detectedNearby}") From 7069ba1f4333a1dbc9de9de8ceeef8a7a66974c0 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 17:29:00 -0800 Subject: [PATCH 18/24] Update system.py --- modules/system.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/system.py b/modules/system.py index 7856218..f2a6157 100644 --- a/modules/system.py +++ b/modules/system.py @@ -1089,10 +1089,11 @@ async def watchdog(): while True: await asyncio.sleep(20) + # check all interfaces for i in range(1, 10): interface = globals().get(f'interface{i}') retry_int = globals().get(f'retry_int{i}') - if interface is not None and not retry_int: + if interface is not None and not retry_int and globals().get(f'interface{i}_enabled'): try: firmware = getNodeFirmware(0, i) except Exception as e: From 9bae30bcb1c95eef6e2d745de06dc5885f9a3698 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 17:42:29 -0800 Subject: [PATCH 19/24] Update config.template --- config.template | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.template b/config.template index fe22350..1a890c8 100644 --- a/config.template +++ b/config.template @@ -93,7 +93,7 @@ emailSentryAlerts = False # radius in meters to detect someone close to the bot SentryRadius = 100 # channel to send a message to when the watchdog is triggered -SentryChannel = 9 +SentryChannel = 2 # holdoff time multiplied by seconds(20) of the watchdog SentryHoldoff = 9 # list of ignored nodes numbers ex: 2813308004,4258675309 From 9014a7e8f9d465d3130214baf17f132f8cf17e15 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 18:13:38 -0800 Subject: [PATCH 20/24] Update system.py --- modules/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system.py b/modules/system.py index f2a6157..b9be57c 100644 --- a/modules/system.py +++ b/modules/system.py @@ -1051,7 +1051,7 @@ async def handleSentinel(deviceID): if closest_node in [i['id'] for i in handleSentinel_spotted]: # check if the distance is closer than the last time, if not just return for i in range(len(handleSentinel_spotted)): - if handleSentinel_spotted[i]['id'] == closest_node and closest_distance < handleSentinel_spotted[i]['distance']: + if handleSentinel_spotted[i]['id'] == closest_node and closest_distance is not None and closest_distance < handleSentinel_spotted[i]['distance']: handleSentinel_spotted[i]['distance'] = closest_distance break else: From f0a93b0191c245f109cdcf28f10806976b0695c3 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 18:24:11 -0800 Subject: [PATCH 21/24] Update system.py --- modules/system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/system.py b/modules/system.py index b9be57c..db4150d 100644 --- a/modules/system.py +++ b/modules/system.py @@ -1065,7 +1065,7 @@ async def handleSentinel(deviceID): detectedNearby += ", " + decimal_to_hex(closest_nodes[0]['id']) detectedNearby += f" at {closest_distance}m" - if handleSentinel_loop >= sentry_holdoff: + if handleSentinel_loop >= sentry_holdoff and detectedNearby not in ["", None]: if closest_nodes and positionMetadata and closest_nodes[0]['id'] in positionMetadata: metadata = positionMetadata[closest_nodes[0]['id']] if metadata.get('precisionBits') is not None: From 5829cdcef9eba190dcbc2e13f8d05e1facb79cc4 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 20:18:02 -0800 Subject: [PATCH 22/24] reportingEnhance --- logs/README.md | 5 ++++- modules/web.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/logs/README.md b/logs/README.md index e18eccc..1413dc4 100644 --- a/logs/README.md +++ b/logs/README.md @@ -23,4 +23,7 @@ To change the stdout (what you see on the console) logging level (default is DEB ``` # Set level for stdout handler stdout_handler.setLevel(logging.INFO) -``` \ No newline at end of file +``` + +There is a web-server module you can run `python modules/web.py` from the project root directory and it will serve up the web content. +by default. http://localhost:8420 \ No newline at end of file diff --git a/modules/web.py b/modules/web.py index 5cfa42d..2ae2359 100644 --- a/modules/web.py +++ b/modules/web.py @@ -11,7 +11,7 @@ import http.server PORT = 8420 # set webRoot index.html location -webRoot = "../etc/www" +webRoot = "etc/www" # Set to True to enable logging sdtout webServerLogs = False @@ -43,4 +43,10 @@ if SSL: exit(1) httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True) -httpd.serve_forever() \ No newline at end of file +print("Serving reports at http://localhost:", PORT) +print("Press ^C to quit.") +if not webServerLogs: + print("Server Logs are disabled") +# Serve forever, that is until the user interrupts the process +httpd.serve_forever() +exit(0) From b44fa22c11c1a2b44177c299ea553bc1bacf85bb Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 20:20:34 -0800 Subject: [PATCH 23/24] Update web.py --- modules/web.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/web.py b/modules/web.py index 2ae2359..a340622 100644 --- a/modules/web.py +++ b/modules/web.py @@ -43,8 +43,7 @@ if SSL: exit(1) httpd.socket = ctx.wrap_socket(httpd.socket, server_side=True) -print("Serving reports at http://localhost:", PORT) -print("Press ^C to quit.") +print(f"Serving reports at http://localhost:{PORT} Press ^C to quit.\n\n") if not webServerLogs: print("Server Logs are disabled") # Serve forever, that is until the user interrupts the process From 54e716d2ccd32d01c789c5b0ceb0366885c8e07a Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Sun, 5 Jan 2025 20:53:30 -0800 Subject: [PATCH 24/24] enhanceMultiNodeTelemetry --- modules/system.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/system.py b/modules/system.py index db4150d..a550586 100644 --- a/modules/system.py +++ b/modules/system.py @@ -749,9 +749,11 @@ def exit_handler(): # Telemetry Functions telemetryData = {} def initialize_telemetryData(): - telemetryData[0] = {'interface1': 0, 'interface2': 0, 'lastAlert1': '', 'lastAlert2': ''} - telemetryData[1] = {'numPacketsTx': 0, 'numPacketsRx': 0, 'numOnlineNodes': 0, 'numPacketsTxErr': 0, 'numPacketsRxErr': 0, 'numTotalNodes': 0} - telemetryData[2] = {'numPacketsTx': 0, 'numPacketsRx': 0, 'numOnlineNodes': 0, 'numPacketsTxErr': 0, 'numPacketsRxErr': 0, 'numTotalNodes': 0} + telemetryData[0] = {f'interface{i}': 0 for i in range(1, 10)} + telemetryData[0].update({f'lastAlert{i}': '' for i in range(1, 10)}) + for i in range(1, 10): + telemetryData[i] = {'numPacketsTx': 0, 'numPacketsRx': 0, 'numOnlineNodes': 0, 'numPacketsTxErr': 0, 'numPacketsRxErr': 0, 'numTotalNodes': 0} + # indented to be called from the main loop initialize_telemetryData()