From 8645e11b8d4c900f61611379a7c959f063976c9b Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Sat, 6 Jul 2024 21:37:49 -0500 Subject: [PATCH 01/28] Check connection function --- meshtastic_utils.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index e0e1b7c..8768fb6 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -41,11 +41,12 @@ def connect_meshtastic(force_connect=False): elif connection_type == "ble": ble_address = relay_config["meshtastic"].get("ble_address") + if ble_address: - logger.info(f"Connecting to BLE address or name {ble_address} ...") + logger.info(f"Connecting to BLE address {ble_address} ...") meshtastic_client = meshtastic.ble_interface.BLEInterface(address=ble_address) else: - logger.error("No BLE address or name provided.") + logger.error("No BLE address provided.") return None else: @@ -156,4 +157,23 @@ def on_meshtastic_message(packet, loop=None): ) found_matching_plugin = result.result() if found_matching_plugin: - logger.debug(f"Processed {portnum} with plugin {plugin.plugin_name}") \ No newline at end of file + logger.debug(f"Processed {portnum} with plugin {plugin.plugin_name}") + +async def check_connection(): + global meshtastic_client + connection_type = relay_config["meshtastic"]["connection_type"] + while True: + if meshtastic_client: + try: + # Attempt a read operation to check if the connection is alive + meshtastic_client.getMyNodeInfo() + except Exception as e: + logger.error(f"{connection_type.capitalize()} connection lost: {e}") + on_lost_meshtastic_connection(meshtastic_client) + await asyncio.sleep(60) # Check connection every 60 seconds + +if __name__ == "__main__": + meshtastic_client = connect_meshtastic() + loop = asyncio.get_event_loop() + loop.create_task(check_connection()) + loop.run_forever() From 2740df9202823f44f7301aef8b400406859e3d57 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:08:43 -0500 Subject: [PATCH 02/28] Ensure previous connection is closed --- meshtastic_utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 8768fb6..08d741b 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -20,7 +20,10 @@ def connect_meshtastic(force_connect=False): if meshtastic_client and not force_connect: return meshtastic_client - meshtastic_client = None + # Ensure previous connection is closed + if meshtastic_client: + meshtastic_client.close() + meshtastic_client = None # Initialize Meshtastic interface connection_type = relay_config["meshtastic"]["connection_type"] @@ -170,7 +173,7 @@ async def check_connection(): except Exception as e: logger.error(f"{connection_type.capitalize()} connection lost: {e}") on_lost_meshtastic_connection(meshtastic_client) - await asyncio.sleep(60) # Check connection every 60 seconds + await asyncio.sleep(5) # Check connection every 5 seconds if __name__ == "__main__": meshtastic_client = connect_meshtastic() From 0f84c132b23c51eae98ddbd7cbdaa63b0b311efe Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:36:55 -0500 Subject: [PATCH 03/28] Try/except for connection close & logging --- meshtastic_utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 08d741b..d71f192 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -22,7 +22,10 @@ def connect_meshtastic(force_connect=False): # Ensure previous connection is closed if meshtastic_client: - meshtastic_client.close() + try: + meshtastic_client.close() + except Exception as e: + logger.error(f"Error while closing previous connection: {e}") meshtastic_client = None # Initialize Meshtastic interface @@ -44,7 +47,6 @@ def connect_meshtastic(force_connect=False): elif connection_type == "ble": ble_address = relay_config["meshtastic"].get("ble_address") - if ble_address: logger.info(f"Connecting to BLE address {ble_address} ...") meshtastic_client = meshtastic.ble_interface.BLEInterface(address=ble_address) @@ -64,7 +66,7 @@ def connect_meshtastic(force_connect=False): except Exception as e: attempts += 1 if attempts <= retry_limit: - logger.warn(f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}") + logger.warning(f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}") time.sleep(attempts) else: logger.error(f"Could not connect: {e}") From e919ca093c2951ba445e8caf7041dc87ff212c04 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:50:03 -0500 Subject: [PATCH 04/28] Infinite retries --- meshtastic_utils.py | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index d71f192..daead22 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -30,15 +30,10 @@ def connect_meshtastic(force_connect=False): # Initialize Meshtastic interface connection_type = relay_config["meshtastic"]["connection_type"] - retry_limit = ( - relay_config["meshtastic"]["retry_limit"] - if "retry_limit" in relay_config["meshtastic"] - else 3 - ) - attempts = 1 - successful = False - - while not successful and attempts <= retry_limit: + max_backoff = 60 # maximum backoff time in seconds + backoff = 1 # initial backoff time in seconds + + while True: try: if connection_type == "serial": serial_port = relay_config["meshtastic"]["serial_port"] @@ -59,18 +54,14 @@ def connect_meshtastic(force_connect=False): logger.info(f"Connecting to host {target_host} ...") meshtastic_client = meshtastic.tcp_interface.TCPInterface(hostname=target_host) - successful = True nodeInfo = meshtastic_client.getMyNodeInfo() logger.info(f"Connected to {nodeInfo['user']['shortName']} / {nodeInfo['user']['hwModel']}") + break # exit the retry loop on successful connection except Exception as e: - attempts += 1 - if attempts <= retry_limit: - logger.warning(f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}") - time.sleep(attempts) - else: - logger.error(f"Could not connect: {e}") - return None + logger.error(f"Connection attempt failed: {e}") + time.sleep(backoff) + backoff = min(backoff * 2, max_backoff) # exponential backoff with a maximum limit return meshtastic_client From 35674f02a3bd8469388a8148c94da9a95453b1ce Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:54:03 -0500 Subject: [PATCH 05/28] Check to see if we're reconnecting already --- meshtastic_utils.py | 102 ++++---------------------------------------- 1 file changed, 8 insertions(+), 94 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index daead22..1f914f3 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -14,9 +14,10 @@ logger = get_logger(name="Meshtastic") meshtastic_client = None +reconnecting = False def connect_meshtastic(force_connect=False): - global meshtastic_client + global meshtastic_client, reconnecting if meshtastic_client and not force_connect: return meshtastic_client @@ -56,6 +57,7 @@ def connect_meshtastic(force_connect=False): nodeInfo = meshtastic_client.getMyNodeInfo() logger.info(f"Connected to {nodeInfo['user']['shortName']} / {nodeInfo['user']['hwModel']}") + reconnecting = False break # exit the retry loop on successful connection except Exception as e: @@ -66,6 +68,10 @@ def connect_meshtastic(force_connect=False): return meshtastic_client def on_lost_meshtastic_connection(interface): + global reconnecting + if reconnecting: + return + reconnecting = True logger.error("Lost connection. Reconnecting...") connect_meshtastic(force_connect=True) @@ -80,96 +86,4 @@ def on_meshtastic_message(packet, loop=None): 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}") - -async def check_connection(): - global meshtastic_client - connection_type = relay_config["meshtastic"]["connection_type"] - while True: - if meshtastic_client: - try: - # Attempt a read operation to check if the connection is alive - meshtastic_client.getMyNodeInfo() - except Exception as e: - logger.error(f"{connection_type.capitalize()} connection lost: {e}") - on_lost_meshtastic_connection(meshtastic_client) - await asyncio.sleep(5) # Check connection every 5 seconds - -if __name__ == "__main__": - meshtastic_client = connect_meshtastic() - loop = asyncio.get_event_loop() - loop.create_task(check_connection()) - loop.run_forever() + From e311ebfd2f20ac22d32e91de03fd525f07a95685 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 12:57:32 -0500 Subject: [PATCH 06/28] Fix pasting error --- meshtastic_utils.py | 94 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 1f914f3..3f6751f 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -86,4 +86,96 @@ def on_meshtastic_message(packet, loop=None): 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}") + +async def check_connection(): + global meshtastic_client + connection_type = relay_config["meshtastic"]["connection_type"] + while True: + if meshtastic_client: + try: + # Attempt a read operation to check if the connection is alive + meshtastic_client.getMyNodeInfo() + except Exception as e: + logger.error(f"{connection_type.capitalize()} connection lost: {e}") + on_lost_meshtastic_connection(meshtastic_client) + await asyncio.sleep(5) # Check connection every 5 seconds + +if __name__ == "__main__": + meshtastic_client = connect_meshtastic() + loop = asyncio.get_event_loop() + loop.create_task(check_connection()) + loop.run_forever() From b0ef58797df808808454ace6d4fbf6d50363f411 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:03:20 -0500 Subject: [PATCH 07/28] Ensure reconnecting flag is set --- meshtastic_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 3f6751f..6cb6ffd 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -162,7 +162,7 @@ def on_meshtastic_message(packet, loop=None): logger.debug(f"Processed {portnum} with plugin {plugin.plugin_name}") async def check_connection(): - global meshtastic_client + global meshtastic_client, reconnecting connection_type = relay_config["meshtastic"]["connection_type"] while True: if meshtastic_client: From f343b76a5610d7c852848286b9aaeb59bc0aa8fe Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:06:23 -0500 Subject: [PATCH 08/28] Adjust retry logic --- meshtastic_utils.py | 101 +++++--------------------------------------- 1 file changed, 11 insertions(+), 90 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 6cb6ffd..0945493 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -73,7 +73,16 @@ def on_lost_meshtastic_connection(interface): return reconnecting = True logger.error("Lost connection. Reconnecting...") - connect_meshtastic(force_connect=True) + + async def reconnect(): + backoff = 1 + max_backoff = 60 + while reconnecting: + connect_meshtastic(force_connect=True) + await asyncio.sleep(backoff) + backoff = min(backoff * 2, max_backoff) + + asyncio.ensure_future(reconnect()) def on_meshtastic_message(packet, loop=None): from matrix_utils import matrix_relay @@ -90,92 +99,4 @@ def on_meshtastic_message(packet, loop=None): 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}") - -async def check_connection(): - global meshtastic_client, reconnecting - connection_type = relay_config["meshtastic"]["connection_type"] - while True: - if meshtastic_client: - try: - # Attempt a read operation to check if the connection is alive - meshtastic_client.getMyNodeInfo() - except Exception as e: - logger.error(f"{connection_type.capitalize()} connection lost: {e}") - on_lost_meshtastic_connection(meshtastic_client) - await asyncio.sleep(5) # Check connection every 5 seconds - -if __name__ == "__main__": - meshtastic_client = connect_meshtastic() - loop = asyncio.get_event_loop() - loop.create_task(check_connection()) - loop.run_forever() + From 89f8fb717672cf8d094adb4a14ac4fbebe777f6f Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:10:58 -0500 Subject: [PATCH 09/28] Schedule reconnect properly --- meshtastic_utils.py | 110 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 99 insertions(+), 11 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 0945493..72a16db 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -67,22 +67,22 @@ def connect_meshtastic(force_connect=False): return meshtastic_client +async def reconnect(): + global reconnecting + backoff = 1 + max_backoff = 60 + while reconnecting: + connect_meshtastic(force_connect=True) + await asyncio.sleep(backoff) + backoff = min(backoff * 2, max_backoff) + def on_lost_meshtastic_connection(interface): global reconnecting if reconnecting: return reconnecting = True logger.error("Lost connection. Reconnecting...") - - async def reconnect(): - backoff = 1 - max_backoff = 60 - while reconnecting: - connect_meshtastic(force_connect=True) - await asyncio.sleep(backoff) - backoff = min(backoff * 2, max_backoff) - - asyncio.ensure_future(reconnect()) + asyncio.get_event_loop().create_task(reconnect()) def on_meshtastic_message(packet, loop=None): from matrix_utils import matrix_relay @@ -99,4 +99,92 @@ def on_meshtastic_message(packet, loop=None): 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}") + +async def check_connection(): + global meshtastic_client, reconnecting + connection_type = relay_config["meshtastic"]["connection_type"] + while True: + if meshtastic_client: + try: + # Attempt a read operation to check if the connection is alive + meshtastic_client.getMyNodeInfo() + except Exception as e: + logger.error(f"{connection_type.capitalize()} connection lost: {e}") + on_lost_meshtastic_connection(meshtastic_client) + await asyncio.sleep(5) # Check connection every 5 seconds + +if __name__ == "__main__": + meshtastic_client = connect_meshtastic() + loop = asyncio.get_event_loop() + loop.create_task(check_connection()) + loop.run_forever() From bbe247f7cb2a01991f666f7b95be6ff67cd3982d Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:23:06 -0500 Subject: [PATCH 10/28] Changes --- main.py | 36 ++++++++++++++++++++++++------------ meshtastic_utils.py | 16 +++++----------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/main.py b/main.py index 35cf1cc..a63cce3 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,7 @@ It uses Meshtastic-python and Matrix nio client library to interface with the radio and the Matrix server respectively. """ import asyncio +import signal from nio import ( RoomMessageText, RoomMessageNotice, @@ -23,6 +24,7 @@ connect_meshtastic, on_meshtastic_message, on_lost_meshtastic_connection, + check_connection, logger as meshtastic_logger, ) @@ -31,8 +33,15 @@ matrix_rooms: List[dict] = relay_config["matrix_rooms"] matrix_access_token = relay_config["matrix"]["access_token"] +# Function to handle graceful shutdown +def shutdown(signal, loop): + logger.info(f"Received exit signal {signal.name}...") + tasks = asyncio.all_tasks(loop=loop) + for task in tasks: + task.cancel() + loop.stop() -async def main(): +async def main(loop): # Initialize the SQLite database initialize_database() @@ -54,18 +63,14 @@ async def main(): # Register the Meshtastic message callback meshtastic_logger.info(f"Listening for inbound radio messages ...") - pub.subscribe( - on_meshtastic_message, "meshtastic.receive", loop=asyncio.get_event_loop() - ) - pub.subscribe( - on_lost_meshtastic_connection, - "meshtastic.connection.lost", - ) + pub.subscribe(on_meshtastic_message, "meshtastic.receive", loop=loop) + pub.subscribe(on_lost_meshtastic_connection, "meshtastic.connection.lost", loop=loop) + + loop.create_task(check_connection(loop)) + # Register the message callback matrix_logger.info(f"Listening for inbound matrix messages ...") - matrix_client.add_event_callback( - on_room_message, (RoomMessageText, RoomMessageNotice) - ) + matrix_client.add_event_callback(on_room_message, (RoomMessageText, RoomMessageNotice)) # Start the Matrix client while True: @@ -83,5 +88,12 @@ async def main(): await asyncio.sleep(60) # Update longnames & shortnames every 60 seconds +if __name__ == "__main__": + loop = asyncio.get_event_loop() + for sig in (signal.SIGINT, signal.SIGTERM): + loop.add_signal_handler(sig, shutdown, sig, loop) -asyncio.run(main()) + try: + loop.run_until_complete(main(loop)) + finally: + loop.close() diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 72a16db..5be630e 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -67,7 +67,7 @@ def connect_meshtastic(force_connect=False): return meshtastic_client -async def reconnect(): +async def reconnect(loop): global reconnecting backoff = 1 max_backoff = 60 @@ -76,13 +76,13 @@ async def reconnect(): await asyncio.sleep(backoff) backoff = min(backoff * 2, max_backoff) -def on_lost_meshtastic_connection(interface): +def on_lost_meshtastic_connection(interface, loop): global reconnecting if reconnecting: return reconnecting = True logger.error("Lost connection. Reconnecting...") - asyncio.get_event_loop().create_task(reconnect()) + loop.create_task(reconnect(loop)) def on_meshtastic_message(packet, loop=None): from matrix_utils import matrix_relay @@ -170,7 +170,7 @@ def on_meshtastic_message(packet, loop=None): if found_matching_plugin: logger.debug(f"Processed {portnum} with plugin {plugin.plugin_name}") -async def check_connection(): +async def check_connection(loop): global meshtastic_client, reconnecting connection_type = relay_config["meshtastic"]["connection_type"] while True: @@ -180,11 +180,5 @@ async def check_connection(): meshtastic_client.getMyNodeInfo() except Exception as e: logger.error(f"{connection_type.capitalize()} connection lost: {e}") - on_lost_meshtastic_connection(meshtastic_client) + on_lost_meshtastic_connection(meshtastic_client, loop) await asyncio.sleep(5) # Check connection every 5 seconds - -if __name__ == "__main__": - meshtastic_client = connect_meshtastic() - loop = asyncio.get_event_loop() - loop.create_task(check_connection()) - loop.run_forever() From e1859be50b6b9f102031527743cd3b3f84b53062 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:29:48 -0500 Subject: [PATCH 11/28] Fixing issues when reconnecting --- meshtastic_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 5be630e..0f30e3b 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -69,7 +69,7 @@ def connect_meshtastic(force_connect=False): async def reconnect(loop): global reconnecting - backoff = 1 + backoff = 10 # Initial backoff is now 10 seconds max_backoff = 60 while reconnecting: connect_meshtastic(force_connect=True) From 63b4b1c38de511a48ebc42842079cb530c156b99 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 13:39:30 -0500 Subject: [PATCH 12/28] Log when reconnection is successful --- meshtastic_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 0f30e3b..0c62791 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -72,7 +72,9 @@ async def reconnect(loop): backoff = 10 # Initial backoff is now 10 seconds max_backoff = 60 while reconnecting: - connect_meshtastic(force_connect=True) + meshtastic_client = connect_meshtastic(force_connect=True) + if meshtastic_client: + logger.info("Reconnection successful.") await asyncio.sleep(backoff) backoff = min(backoff * 2, max_backoff) From cbc7891acde22b23efebf84f0a3733e9ce883ce1 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:03:43 -0500 Subject: [PATCH 13/28] Add debug logging to matrix_utils --- matrix_utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/matrix_utils.py b/matrix_utils.py index 524f403..1f9bc62 100644 --- a/matrix_utils.py +++ b/matrix_utils.py @@ -140,8 +140,12 @@ async def on_room_message( full_display_name = "Unknown user" message_timestamp = event.server_timestamp + # Detailed logging for timestamp comparison + logger.debug(f"Message timestamp: {message_timestamp}, Bot start time: {bot_start_time}") + # We do not relay the past if message_timestamp < bot_start_time: + logger.debug(f"Ignoring old message received at {message_timestamp}") return room_config = None From 217316af08e78eb9f11af48393cac8aa42e74a97 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:16:47 -0500 Subject: [PATCH 14/28] Better BLE error handling hopefully --- meshtastic_utils.py | 78 +++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 0c62791..d9f0796 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -14,27 +14,28 @@ logger = get_logger(name="Meshtastic") meshtastic_client = None -reconnecting = False def connect_meshtastic(force_connect=False): - global meshtastic_client, reconnecting + global meshtastic_client if meshtastic_client and not force_connect: return meshtastic_client # Ensure previous connection is closed if meshtastic_client: - try: - meshtastic_client.close() - except Exception as e: - logger.error(f"Error while closing previous connection: {e}") + meshtastic_client.close() meshtastic_client = None # Initialize Meshtastic interface connection_type = relay_config["meshtastic"]["connection_type"] - max_backoff = 60 # maximum backoff time in seconds - backoff = 1 # initial backoff time in seconds - - while True: + retry_limit = ( + relay_config["meshtastic"]["retry_limit"] + if "retry_limit" in relay_config["meshtastic"] + else 3 + ) + attempts = 1 + successful = False + + while not successful and attempts <= retry_limit: try: if connection_type == "serial": serial_port = relay_config["meshtastic"]["serial_port"] @@ -43,6 +44,7 @@ def connect_meshtastic(force_connect=False): elif connection_type == "ble": ble_address = relay_config["meshtastic"].get("ble_address") + if ble_address: logger.info(f"Connecting to BLE address {ble_address} ...") meshtastic_client = meshtastic.ble_interface.BLEInterface(address=ble_address) @@ -55,36 +57,36 @@ def connect_meshtastic(force_connect=False): logger.info(f"Connecting to host {target_host} ...") meshtastic_client = meshtastic.tcp_interface.TCPInterface(hostname=target_host) + successful = True nodeInfo = meshtastic_client.getMyNodeInfo() logger.info(f"Connected to {nodeInfo['user']['shortName']} / {nodeInfo['user']['hwModel']}") - reconnecting = False - break # exit the retry loop on successful connection except Exception as e: - logger.error(f"Connection attempt failed: {e}") - time.sleep(backoff) - backoff = min(backoff * 2, max_backoff) # exponential backoff with a maximum limit + 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 return meshtastic_client -async def reconnect(loop): - global reconnecting - backoff = 10 # Initial backoff is now 10 seconds - max_backoff = 60 - while reconnecting: - meshtastic_client = connect_meshtastic(force_connect=True) - if meshtastic_client: - logger.info("Reconnection successful.") - await asyncio.sleep(backoff) - backoff = min(backoff * 2, max_backoff) - -def on_lost_meshtastic_connection(interface, loop): - global reconnecting - if reconnecting: - return - reconnecting = True +def on_lost_meshtastic_connection(interface): logger.error("Lost connection. Reconnecting...") - loop.create_task(reconnect(loop)) + asyncio.get_event_loop().create_task(reconnect()) + +async def reconnect(): + backoff_time = 10 + while True: + try: + await asyncio.sleep(backoff_time) + connect_meshtastic(force_connect=True) + logger.info("Reconnected successfully.") + break + except Exception as e: + logger.error(f"Reconnection attempt failed: {e}") + backoff_time = min(backoff_time * 2, 300) # Cap backoff at 5 minutes def on_meshtastic_message(packet, loop=None): from matrix_utils import matrix_relay @@ -172,8 +174,8 @@ def on_meshtastic_message(packet, loop=None): if found_matching_plugin: logger.debug(f"Processed {portnum} with plugin {plugin.plugin_name}") -async def check_connection(loop): - global meshtastic_client, reconnecting +async def check_connection(): + global meshtastic_client connection_type = relay_config["meshtastic"]["connection_type"] while True: if meshtastic_client: @@ -182,5 +184,11 @@ async def check_connection(loop): meshtastic_client.getMyNodeInfo() except Exception as e: logger.error(f"{connection_type.capitalize()} connection lost: {e}") - on_lost_meshtastic_connection(meshtastic_client, loop) + on_lost_meshtastic_connection(meshtastic_client) await asyncio.sleep(5) # Check connection every 5 seconds + +if __name__ == "__main__": + meshtastic_client = connect_meshtastic() + loop = asyncio.get_event_loop() + loop.create_task(check_connection()) + loop.run_forever() From 14c2eeb89e01e2d0a0893ed42842a121907105ba Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:19:48 -0500 Subject: [PATCH 15/28] Update main.py for reconnection changes --- main.py | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/main.py b/main.py index a63cce3..d43cbbc 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,6 @@ It uses Meshtastic-python and Matrix nio client library to interface with the radio and the Matrix server respectively. """ import asyncio -import signal from nio import ( RoomMessageText, RoomMessageNotice, @@ -24,7 +23,6 @@ connect_meshtastic, on_meshtastic_message, on_lost_meshtastic_connection, - check_connection, logger as meshtastic_logger, ) @@ -33,15 +31,7 @@ matrix_rooms: List[dict] = relay_config["matrix_rooms"] matrix_access_token = relay_config["matrix"]["access_token"] -# Function to handle graceful shutdown -def shutdown(signal, loop): - logger.info(f"Received exit signal {signal.name}...") - tasks = asyncio.all_tasks(loop=loop) - for task in tasks: - task.cancel() - loop.stop() - -async def main(loop): +async def main(): # Initialize the SQLite database initialize_database() @@ -63,14 +53,18 @@ async def main(loop): # Register the Meshtastic message callback meshtastic_logger.info(f"Listening for inbound radio messages ...") - pub.subscribe(on_meshtastic_message, "meshtastic.receive", loop=loop) - pub.subscribe(on_lost_meshtastic_connection, "meshtastic.connection.lost", loop=loop) - - loop.create_task(check_connection(loop)) - + pub.subscribe( + on_meshtastic_message, "meshtastic.receive", loop=asyncio.get_event_loop() + ) + pub.subscribe( + on_lost_meshtastic_connection, + "meshtastic.connection.lost", + ) # Register the message callback matrix_logger.info(f"Listening for inbound matrix messages ...") - matrix_client.add_event_callback(on_room_message, (RoomMessageText, RoomMessageNotice)) + matrix_client.add_event_callback( + on_room_message, (RoomMessageText, RoomMessageNotice) + ) # Start the Matrix client while True: @@ -88,12 +82,5 @@ async def main(loop): await asyncio.sleep(60) # Update longnames & shortnames every 60 seconds -if __name__ == "__main__": - loop = asyncio.get_event_loop() - for sig in (signal.SIGINT, signal.SIGTERM): - loop.add_signal_handler(sig, shutdown, sig, loop) - try: - loop.run_until_complete(main(loop)) - finally: - loop.close() +asyncio.run(main()) From e18ead1235e96f6c7fca71507a666521714cdab0 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:26:25 -0500 Subject: [PATCH 16/28] Removed debug logging --- matrix_utils.py | 4 ---- meshtastic_utils.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/matrix_utils.py b/matrix_utils.py index 1f9bc62..524f403 100644 --- a/matrix_utils.py +++ b/matrix_utils.py @@ -140,12 +140,8 @@ async def on_room_message( full_display_name = "Unknown user" message_timestamp = event.server_timestamp - # Detailed logging for timestamp comparison - logger.debug(f"Message timestamp: {message_timestamp}, Bot start time: {bot_start_time}") - # We do not relay the past if message_timestamp < bot_start_time: - logger.debug(f"Ignoring old message received at {message_timestamp}") return room_config = None diff --git a/meshtastic_utils.py b/meshtastic_utils.py index d9f0796..dcd4af6 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -64,7 +64,7 @@ def connect_meshtastic(force_connect=False): except Exception as e: attempts += 1 if attempts <= retry_limit: - logger.warn(f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}") + logger.warning(f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}") time.sleep(attempts) else: logger.error(f"Could not connect: {e}") From 067dcf47874f51017a721911e5b02f004064ca70 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:30:20 -0500 Subject: [PATCH 17/28] Use disconnected_callback for ble --- meshtastic_utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index dcd4af6..9c85f7d 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -47,7 +47,10 @@ def connect_meshtastic(force_connect=False): if ble_address: logger.info(f"Connecting to BLE address {ble_address} ...") - meshtastic_client = meshtastic.ble_interface.BLEInterface(address=ble_address) + meshtastic_client = meshtastic.ble_interface.BLEInterface( + address=ble_address, + disconnected_callback=lambda _: on_lost_meshtastic_connection() + ) else: logger.error("No BLE address provided.") return None @@ -64,7 +67,7 @@ def connect_meshtastic(force_connect=False): except Exception as e: attempts += 1 if attempts <= retry_limit: - logger.warning(f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}") + logger.warn(f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}") time.sleep(attempts) else: logger.error(f"Could not connect: {e}") @@ -72,7 +75,7 @@ def connect_meshtastic(force_connect=False): return meshtastic_client -def on_lost_meshtastic_connection(interface): +def on_lost_meshtastic_connection(): logger.error("Lost connection. Reconnecting...") asyncio.get_event_loop().create_task(reconnect()) @@ -184,7 +187,7 @@ async def check_connection(): meshtastic_client.getMyNodeInfo() except Exception as e: logger.error(f"{connection_type.capitalize()} connection lost: {e}") - on_lost_meshtastic_connection(meshtastic_client) + on_lost_meshtastic_connection() await asyncio.sleep(5) # Check connection every 5 seconds if __name__ == "__main__": From 9424f685a8000ae49727e87de8c8f9d15c2184b9 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:36:17 -0500 Subject: [PATCH 18/28] Remove disconnected_callback reference --- meshtastic_utils.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 9c85f7d..5c6bdb1 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -22,16 +22,15 @@ def connect_meshtastic(force_connect=False): # Ensure previous connection is closed if meshtastic_client: - meshtastic_client.close() + try: + meshtastic_client.close() + except Exception as e: + logger.warning(f"Error closing previous connection: {e}") meshtastic_client = None # Initialize Meshtastic interface connection_type = relay_config["meshtastic"]["connection_type"] - retry_limit = ( - relay_config["meshtastic"]["retry_limit"] - if "retry_limit" in relay_config["meshtastic"] - else 3 - ) + retry_limit = relay_config["meshtastic"].get("retry_limit", 3) attempts = 1 successful = False @@ -44,13 +43,9 @@ def connect_meshtastic(force_connect=False): elif connection_type == "ble": ble_address = relay_config["meshtastic"].get("ble_address") - if ble_address: logger.info(f"Connecting to BLE address {ble_address} ...") - meshtastic_client = meshtastic.ble_interface.BLEInterface( - address=ble_address, - disconnected_callback=lambda _: on_lost_meshtastic_connection() - ) + meshtastic_client = meshtastic.ble_interface.BLEInterface(address=ble_address) else: logger.error("No BLE address provided.") return None @@ -75,7 +70,7 @@ def connect_meshtastic(force_connect=False): return meshtastic_client -def on_lost_meshtastic_connection(): +def on_lost_meshtastic_connection(interface): logger.error("Lost connection. Reconnecting...") asyncio.get_event_loop().create_task(reconnect()) @@ -187,7 +182,7 @@ async def check_connection(): meshtastic_client.getMyNodeInfo() except Exception as e: logger.error(f"{connection_type.capitalize()} connection lost: {e}") - on_lost_meshtastic_connection() + on_lost_meshtastic_connection(meshtastic_client) await asyncio.sleep(5) # Check connection every 5 seconds if __name__ == "__main__": From 2fe202be55e38ced6bbf93b83b31449687e31052 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:45:28 -0500 Subject: [PATCH 19/28] BLE disconnection changes --- meshtastic_utils.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 5c6bdb1..c0c4bd6 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -45,7 +45,7 @@ def connect_meshtastic(force_connect=False): ble_address = relay_config["meshtastic"].get("ble_address") if ble_address: logger.info(f"Connecting to BLE address {ble_address} ...") - meshtastic_client = meshtastic.ble_interface.BLEInterface(address=ble_address) + meshtastic_client = meshtastic.ble_interface.BLEInterface(address=ble_address, disconnected_callback=on_ble_disconnected) else: logger.error("No BLE address provided.") return None @@ -62,7 +62,7 @@ def connect_meshtastic(force_connect=False): except Exception as e: attempts += 1 if attempts <= retry_limit: - logger.warn(f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}") + logger.warning(f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}") time.sleep(attempts) else: logger.error(f"Could not connect: {e}") @@ -70,6 +70,10 @@ def connect_meshtastic(force_connect=False): return meshtastic_client +def on_ble_disconnected(): + logger.error("BLE disconnected. Reconnecting...") + asyncio.get_event_loop().create_task(reconnect()) + def on_lost_meshtastic_connection(interface): logger.error("Lost connection. Reconnecting...") asyncio.get_event_loop().create_task(reconnect()) From 77a4e9654de790373d3fa2e6b6101a91161b354b Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:48:50 -0500 Subject: [PATCH 20/28] BLE updates - maybe getting somewhere --- meshtastic_utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index c0c4bd6..514ffbb 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -45,7 +45,13 @@ def connect_meshtastic(force_connect=False): ble_address = relay_config["meshtastic"].get("ble_address") if ble_address: logger.info(f"Connecting to BLE address {ble_address} ...") - meshtastic_client = meshtastic.ble_interface.BLEInterface(address=ble_address, disconnected_callback=on_ble_disconnected) + meshtastic_client = meshtastic.ble_interface.BLEInterface( + address=ble_address, + noProto=False, + debugOut=None, + noNodes=False + ) + meshtastic_client.client.set_disconnected_callback(on_ble_disconnected) else: logger.error("No BLE address provided.") return None @@ -70,7 +76,7 @@ def connect_meshtastic(force_connect=False): return meshtastic_client -def on_ble_disconnected(): +def on_ble_disconnected(client): logger.error("BLE disconnected. Reconnecting...") asyncio.get_event_loop().create_task(reconnect()) From a4248a25f0859ba29b8dd0bf1071661ec6e571f5 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:52:06 -0500 Subject: [PATCH 21/28] BLE change --- meshtastic_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 514ffbb..0d6d22a 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -51,7 +51,6 @@ def connect_meshtastic(force_connect=False): debugOut=None, noNodes=False ) - meshtastic_client.client.set_disconnected_callback(on_ble_disconnected) else: logger.error("No BLE address provided.") return None From 113071a582ca674980d8504ca7b85a9196642d1e Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:05:17 -0500 Subject: [PATCH 22/28] Check for Bleak errors! --- meshtastic_utils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 0d6d22a..53b529c 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -8,6 +8,7 @@ from log_utils import get_logger from db_utils import get_longname, get_shortname from plugin_loader import load_plugins +from bleak.exc import BleakDBusError, BleakError matrix_rooms: List[dict] = relay_config["matrix_rooms"] @@ -64,7 +65,7 @@ def connect_meshtastic(force_connect=False): nodeInfo = meshtastic_client.getMyNodeInfo() logger.info(f"Connected to {nodeInfo['user']['shortName']} / {nodeInfo['user']['hwModel']}") - except Exception as e: + except (BleakDBusError, BleakError, meshtastic.ble_interface.BLEInterface.BLEError, Exception) as e: attempts += 1 if attempts <= retry_limit: logger.warning(f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}") @@ -75,10 +76,6 @@ def connect_meshtastic(force_connect=False): return meshtastic_client -def on_ble_disconnected(client): - logger.error("BLE disconnected. Reconnecting...") - asyncio.get_event_loop().create_task(reconnect()) - def on_lost_meshtastic_connection(interface): logger.error("Lost connection. Reconnecting...") asyncio.get_event_loop().create_task(reconnect()) @@ -189,7 +186,7 @@ async def check_connection(): try: # Attempt a read operation to check if the connection is alive meshtastic_client.getMyNodeInfo() - except Exception as e: + except (BleakDBusError, BleakError, meshtastic.ble_interface.BLEInterface.BLEError, Exception) as e: logger.error(f"{connection_type.capitalize()} connection lost: {e}") on_lost_meshtastic_connection(meshtastic_client) await asyncio.sleep(5) # Check connection every 5 seconds From 192bc5a42bc9284003b13f9f6f0d1594fe6a65db Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:12:34 -0500 Subject: [PATCH 23/28] ensure on_lost_meshtastic_connection logic triggers --- meshtastic_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 53b529c..d3a071e 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -76,7 +76,7 @@ def connect_meshtastic(force_connect=False): return meshtastic_client -def on_lost_meshtastic_connection(interface): +def on_lost_meshtastic_connection(interface=None): logger.error("Lost connection. Reconnecting...") asyncio.get_event_loop().create_task(reconnect()) From 0a8ed50215236d3a9f7f3fe3760c501dfd3bfb1a Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:25:18 -0500 Subject: [PATCH 24/28] schedule the reconnect coroutine on the main event loop --- meshtastic_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index d3a071e..8535fe4 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -78,7 +78,8 @@ def connect_meshtastic(force_connect=False): def on_lost_meshtastic_connection(interface=None): logger.error("Lost connection. Reconnecting...") - asyncio.get_event_loop().create_task(reconnect()) + loop = asyncio.get_running_loop() + loop.create_task(reconnect()) async def reconnect(): backoff_time = 10 From de70998e6e277fbd82f57b8a7c403d0ca6e405a1 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:29:24 -0500 Subject: [PATCH 25/28] ensures that the reconnection logic runs on the main event loop --- meshtastic_utils.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 8535fe4..3eb2820 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -15,6 +15,7 @@ logger = get_logger(name="Meshtastic") meshtastic_client = None +main_loop = None def connect_meshtastic(force_connect=False): global meshtastic_client @@ -78,8 +79,9 @@ def connect_meshtastic(force_connect=False): def on_lost_meshtastic_connection(interface=None): logger.error("Lost connection. Reconnecting...") - loop = asyncio.get_running_loop() - loop.create_task(reconnect()) + global main_loop + if main_loop: + asyncio.run_coroutine_threadsafe(reconnect(), main_loop) async def reconnect(): backoff_time = 10 @@ -194,6 +196,6 @@ async def check_connection(): if __name__ == "__main__": meshtastic_client = connect_meshtastic() - loop = asyncio.get_event_loop() - loop.create_task(check_connection()) - loop.run_forever() + main_loop = asyncio.get_event_loop() + main_loop.create_task(check_connection()) + main_loop.run_forever() From 9364768b0c8d47f53ad01fd259f04a9934086b10 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:36:11 -0500 Subject: [PATCH 26/28] Log successful reconnection --- meshtastic_utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 3eb2820..5f4fc82 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -88,9 +88,10 @@ async def reconnect(): while True: try: await asyncio.sleep(backoff_time) - connect_meshtastic(force_connect=True) - logger.info("Reconnected successfully.") - break + meshtastic_client = connect_meshtastic(force_connect=True) + if meshtastic_client: + logger.info("Reconnected successfully.") + return except Exception as e: logger.error(f"Reconnection attempt failed: {e}") backoff_time = min(backoff_time * 2, 300) # Cap backoff at 5 minutes From 761b529c31ebf3a5adb476973374e218752b4cde Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:41:02 -0500 Subject: [PATCH 27/28] return -> break --- meshtastic_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index 5f4fc82..b4a8e68 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -91,7 +91,7 @@ async def reconnect(): meshtastic_client = connect_meshtastic(force_connect=True) if meshtastic_client: logger.info("Reconnected successfully.") - return + break except Exception as e: logger.error(f"Reconnection attempt failed: {e}") backoff_time = min(backoff_time * 2, 300) # Cap backoff at 5 minutes From cf8d53985bd4da50a05dae5d00423270e6618607 Mon Sep 17 00:00:00 2001 From: Jeremiah K <17190268+jeremiah-k@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:48:48 -0500 Subject: [PATCH 28/28] Logging changes --- meshtastic_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/meshtastic_utils.py b/meshtastic_utils.py index b4a8e68..7079294 100644 --- a/meshtastic_utils.py +++ b/meshtastic_utils.py @@ -87,6 +87,7 @@ async def reconnect(): backoff_time = 10 while True: try: + logger.info(f"Reconnection attempt starting in {backoff_time} seconds...") await asyncio.sleep(backoff_time) meshtastic_client = connect_meshtastic(force_connect=True) if meshtastic_client: