From 65f41ee87c2c56fc98d1a746d6bc74ac958698a3 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:53:52 -0500 Subject: [PATCH 1/3] Trying BLE Support --- meshtastic_utils.py | 137 +++++++++++--------------------------------- 1 file changed, 35 insertions(+), 102 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 11f4dd3..9b88975 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -3,20 +3,18 @@ import time import meshtastic.tcp_interface import meshtastic.serial_interface from typing import List - from config import relay_config from log_utils import get_logger from db_utils import get_longname, get_shortname from plugin_loader import load_plugins +from ble_interface import BLEInterface matrix_rooms: List[dict] = relay_config["matrix_rooms"] logger = get_logger(name="Meshtastic") - meshtastic_client = None - def connect_meshtastic(force_connect=False): global meshtastic_client if meshtastic_client and not force_connect: @@ -33,6 +31,7 @@ def connect_meshtastic(force_connect=False): ) attempts = 1 successful = False + if connection_type == "serial": serial_port = relay_config["meshtastic"]["serial_port"] logger.info(f"Connecting to serial port {serial_port} ...") @@ -52,6 +51,39 @@ def connect_meshtastic(force_connect=False): else: logger.error(f"Could not connect: {e}") return None + + elif connection_type == "ble": + ble_address = relay_config["meshtastic"].get("ble_address") + ble_name = relay_config["meshtastic"].get("ble_name") + + if ble_address and ble_name: + logger.info(f"Connecting to BLE address {ble_address} ...") + elif ble_address: + logger.info(f"Connecting to BLE address {ble_address} ...") + elif ble_name: + logger.info(f"Connecting to BLE name {ble_name} ...") + else: + logger.error("No BLE address or name provided.") + return None + + while not successful and attempts <= retry_limit: + try: + if ble_address: + meshtastic_client = BLEInterface(address=ble_address) + elif ble_name: + meshtastic_client = BLEInterface(address=ble_name) + successful = True + except Exception as e: + attempts += 1 + if attempts <= retry_limit: + logger.warn( + f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}" + ) + time.sleep(attempts) + else: + logger.error(f"Could not connect: {e}") + return None + else: target_host = relay_config["meshtastic"]["host"] logger.info(f"Connecting to host {target_host} ...") @@ -77,102 +109,3 @@ def connect_meshtastic(force_connect=False): f"Connected to {nodeInfo['user']['shortName']} / {nodeInfo['user']['hwModel']}" ) return meshtastic_client - - -def on_lost_meshtastic_connection(interface): - logger.error("Lost connection. Reconnecting...") - connect_meshtastic(force_connect=True) - - -# Callback for new messages from Meshtastic -def on_meshtastic_message(packet, loop=None): - from matrix_utils import matrix_relay - - sender = packet["fromId"] - - if "text" in packet["decoded"] and packet["decoded"]["text"]: - text = packet["decoded"]["text"] - - if "channel" in packet: - channel = packet["channel"] - else: - if packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP": - channel = 0 - else: - logger.debug(f"Unknown packet") - return - - # Check if the channel is mapped to a Matrix room in the configuration - channel_mapped = False - for room in matrix_rooms: - if room["meshtastic_channel"] == channel: - channel_mapped = True - break - - if not channel_mapped: - logger.debug(f"Skipping message from unmapped channel {channel}") - return - - logger.info( - f"Processing inbound radio message from {sender} on channel {channel}" - ) - - longname = get_longname(sender) or sender - shortname = get_shortname(sender) or sender - meshnet_name = relay_config["meshtastic"]["meshnet_name"] - - formatted_message = f"[{longname}/{meshnet_name}]: {text}" - - # Plugin functionality - plugins = load_plugins() - - found_matching_plugin = False - for plugin in plugins: - if not found_matching_plugin: - result = asyncio.run_coroutine_threadsafe( - plugin.handle_meshtastic_message( - packet, formatted_message, longname, meshnet_name - ), - loop=loop, - ) - found_matching_plugin = result.result() - if found_matching_plugin: - logger.debug(f"Processed by plugin {plugin.plugin_name}") - - if found_matching_plugin: - return - - logger.info( - f"Relaying Meshtastic message from {longname} to Matrix: {formatted_message}" - ) - - for room in matrix_rooms: - if room["meshtastic_channel"] == channel: - asyncio.run_coroutine_threadsafe( - matrix_relay( - room["id"], - formatted_message, - longname, - shortname, - meshnet_name, - ), - loop=loop, - ) - else: - portnum = packet["decoded"]["portnum"] - - plugins = load_plugins() - found_matching_plugin = False - for plugin in plugins: - if not found_matching_plugin: - result = asyncio.run_coroutine_threadsafe( - plugin.handle_meshtastic_message( - packet, formatted_message=None, longname=None, meshnet_name=None - ), - loop=loop, - ) - found_matching_plugin = result.result() - if found_matching_plugin: - logger.debug( - f"Processed {portnum} with plugin {plugin.plugin_name}" - ) From 10f509f731d6e7b766c4184f5c3ef6db0829916b Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Mon, 1 Jul 2024 12:50:56 -0500 Subject: [PATCH 2/3] It's working --- meshtastic_utils.py | 193 ++++++++++++++++++++++++++++---------------- 1 file changed, 123 insertions(+), 70 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 9b88975..6c49d88 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -2,12 +2,12 @@ import asyncio import time import meshtastic.tcp_interface import meshtastic.serial_interface +import meshtastic.ble_interface from typing import List from config import relay_config from log_utils import get_logger from db_utils import get_longname, get_shortname from plugin_loader import load_plugins -from ble_interface import BLEInterface matrix_rooms: List[dict] = relay_config["matrix_rooms"] @@ -32,80 +32,133 @@ def connect_meshtastic(force_connect=False): attempts = 1 successful = False - if connection_type == "serial": - serial_port = relay_config["meshtastic"]["serial_port"] - logger.info(f"Connecting to serial port {serial_port} ...") - while not successful and attempts <= retry_limit: - try: - meshtastic_client = meshtastic.serial_interface.SerialInterface( - serial_port - ) - successful = True - except Exception as e: - attempts += 1 - if attempts <= retry_limit: - logger.warn( - f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}" - ) - time.sleep(attempts) - else: - logger.error(f"Could not connect: {e}") - return None + while not successful and attempts <= retry_limit: + try: + if connection_type == "serial": + serial_port = relay_config["meshtastic"]["serial_port"] + logger.info(f"Connecting to serial port {serial_port} ...") + meshtastic_client = meshtastic.serial_interface.SerialInterface(serial_port) + + elif connection_type == "ble": + ble_address = relay_config["meshtastic"].get("ble_address") + ble_name = relay_config["meshtastic"].get("ble_name") - elif connection_type == "ble": - ble_address = relay_config["meshtastic"].get("ble_address") - ble_name = relay_config["meshtastic"].get("ble_name") - - if ble_address and ble_name: - logger.info(f"Connecting to BLE address {ble_address} ...") - elif ble_address: - logger.info(f"Connecting to BLE address {ble_address} ...") - elif ble_name: - logger.info(f"Connecting to BLE name {ble_name} ...") - else: - logger.error("No BLE address or name provided.") - return None - - while not successful and attempts <= retry_limit: - try: if ble_address: - meshtastic_client = BLEInterface(address=ble_address) + logger.info(f"Connecting to BLE address {ble_address} ...") + meshtastic_client = meshtastic.ble_interface.BLEInterface(address=ble_address) elif ble_name: - meshtastic_client = BLEInterface(address=ble_name) - successful = True - except Exception as e: - attempts += 1 - if attempts <= retry_limit: - logger.warn( - f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}" - ) - time.sleep(attempts) + logger.info(f"Connecting to BLE name {ble_name} ...") + meshtastic_client = meshtastic.ble_interface.BLEInterface(address=ble_name) else: - logger.error(f"Could not connect: {e}") + logger.error("No BLE address or name provided.") return None + + else: + target_host = relay_config["meshtastic"]["host"] + logger.info(f"Connecting to host {target_host} ...") + meshtastic_client = meshtastic.tcp_interface.TCPInterface(hostname=target_host) - else: - target_host = relay_config["meshtastic"]["host"] - logger.info(f"Connecting to host {target_host} ...") - while not successful and attempts <= retry_limit: - try: - meshtastic_client = meshtastic.tcp_interface.TCPInterface( - hostname=target_host - ) - successful = True - except Exception as e: - attempts += 1 - if attempts <= retry_limit: - logger.warn( - f"Attempt #{attempts-1} failed. Retrying in {attempts} secs... {e}" - ) - time.sleep(attempts) - else: - logger.error(f"Could not connect: {e}") - return None + successful = True + nodeInfo = meshtastic_client.getMyNodeInfo() + logger.info(f"Connected to {nodeInfo['user']['shortName']} / {nodeInfo['user']['hwModel']}") + + except Exception as e: + attempts += 1 + if attempts <= retry_limit: + logger.warn(f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}") + time.sleep(attempts) + else: + logger.error(f"Could not connect: {e}") + return None - nodeInfo = meshtastic_client.getMyNodeInfo() - logger.info( - f"Connected to {nodeInfo['user']['shortName']} / {nodeInfo['user']['hwModel']}" - ) return meshtastic_client + +def on_lost_meshtastic_connection(interface): + logger.error("Lost connection. Reconnecting...") + connect_meshtastic(force_connect=True) + +def on_meshtastic_message(packet, loop=None): + from matrix_utils import matrix_relay + + sender = packet["fromId"] + + if "text" in packet["decoded"] and packet["decoded"]["text"]: + text = packet["decoded"]["text"] + + if "channel" in packet: + channel = packet["channel"] + else: + if packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP": + channel = 0 + else: + logger.debug(f"Unknown packet") + return + + # Check if the channel is mapped to a Matrix room in the configuration + channel_mapped = False + for room in matrix_rooms: + if room["meshtastic_channel"] == channel: + channel_mapped = True + break + + if not channel_mapped: + logger.debug(f"Skipping message from unmapped channel {channel}") + return + + logger.info(f"Processing inbound radio message from {sender} on channel {channel}") + + longname = get_longname(sender) or sender + shortname = get_shortname(sender) or sender + meshnet_name = relay_config["meshtastic"]["meshnet_name"] + + formatted_message = f"[{longname}/{meshnet_name}]: {text}" + + # Plugin functionality + plugins = load_plugins() + + found_matching_plugin = False + for plugin in plugins: + if not found_matching_plugin: + result = asyncio.run_coroutine_threadsafe( + plugin.handle_meshtastic_message( + packet, formatted_message, longname, meshnet_name + ), + loop=loop, + ) + found_matching_plugin = result.result() + if found_matching_plugin: + logger.debug(f"Processed by plugin {plugin.plugin_name}") + + if found_matching_plugin: + return + + logger.info(f"Relaying Meshtastic message from {longname} to Matrix: {formatted_message}") + + for room in matrix_rooms: + if room["meshtastic_channel"] == channel: + asyncio.run_coroutine_threadsafe( + matrix_relay( + room["id"], + formatted_message, + longname, + shortname, + meshnet_name, + ), + loop=loop, + ) + else: + portnum = packet["decoded"]["portnum"] + + plugins = load_plugins() + found_matching_plugin = False + for plugin in plugins: + if not found_matching_plugin: + result = asyncio.run_coroutine_threadsafe( + plugin.handle_meshtastic_message( + packet, formatted_message=None, longname=None, meshnet_name=None + ), + loop=loop, + ) + found_matching_plugin = result.result() + if found_matching_plugin: + logger.debug(f"Processed {portnum} with plugin {plugin.plugin_name}") From 5a80fc3a049744ef5c938685d4b142826093557a Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:19:20 -0500 Subject: [PATCH 3/3] Update sample config w/ BLE changes --- sample_config.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sample_config.yaml b/sample_config.yaml index 54ed440..bb76e5c 100644 --- a/sample_config.yaml +++ b/sample_config.yaml @@ -10,11 +10,13 @@ matrix_rooms: # Needs at least 1 room & channel, but supports all Meshtastic cha meshtastic_channel: 2 meshtastic: - connection_type: serial # Choose either "network" or "serial" + connection_type: serial # Choose either "network", "serial", or "ble" serial_port: /dev/ttyUSB0 # Only used when connection is "serial" host: "meshtastic.local" # Only used when connection is "network" + ble_address: "AA:BB:CC:DD:EE:FF" # Only used when connection is "ble" - Choose one. If used, don't set `ble_name` + ble_name: "Your BLE Name" # Only used when connection is "ble" - Choose one. If used, don't set `ble_address` meshnet_name: "Your Meshnet Name" # This is displayed in full on Matrix, but is truncated when sent to a Meshnet - broadcast_enabled: true + broadcast_enabled: true # Must be set to true to enable Matrix to Meshtastic messages logging: level: "info"