From 4bf61d4c9803f0f44e6a44c462fd7bb61177e3ba Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 3 Mar 2025 06:48:06 -0600 Subject: [PATCH 1/8] Add backup / restore admin commands --- meshtastic/__main__.py | 15 +++++++++++++++ meshtastic/node.py | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index e5a92435..7528e2a9 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -462,6 +462,21 @@ def onConnected(interface): waitForAckNak = True interface.getNode(args.dest, False, **getNode_kwargs).removeFavorite(args.remove_favorite_node) + if args.backup_prefs: + closeNow = True + waitForAckNak = True + interface.getNode(args.dest, False, **getNode_kwargs).re(args.backup_preferences) + + if args.restore_prefs: + closeNow = True + waitForAckNak = True + interface.getNode(args.dest, False, **getNode_kwargs).re(args.backup_preferences) + + if args.remove_backup_prefs: + closeNow = True + waitForAckNak = True + interface.getNode(args.dest, False, **getNode_kwargs).re(args.remove_backup_preferences) + if args.set_ignored_node: closeNow = True waitForAckNak = True diff --git a/meshtastic/node.py b/meshtastic/node.py index f8e81f63..98fbdccd 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -707,8 +707,47 @@ def setFavorite(self, nodeId: Union[int, str]): onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) + def backupPreferences(self, location: int = 0): + """Tell the node to backup preferences to flash.""" + self.ensureSessionKey() + + p = admin_pb2.AdminMessage() + p.backup_preferences = location + + if self == self.iface.localNode: + onResponse = None + else: + onResponse = self.onAckNak + return self._sendAdmin(p, onResponse=onResponse) + + def restorePreferences(self, location: int = 0): + """Tell the node to restore preferences from backup.""" + self.ensureSessionKey() + + p = admin_pb2.AdminMessage() + p.restore_preferences = location + + if self == self.iface.localNode: + onResponse = None + else: + onResponse = self.onAckNak + return self._sendAdmin(p, onResponse=onResponse) + + def removePreferencesBackups(self, location: int = 0): + """Tell the node to remove backup preferences from the filesystem.""" + self.ensureSessionKey() + + p = admin_pb2.AdminMessage() + p.remove_backup_preferences = location + + if self == self.iface.localNode: + onResponse = None + else: + onResponse = self.onAckNak + return self._sendAdmin(p, onResponse=onResponse) + def removeFavorite(self, nodeId: Union[int, str]): - """Tell the node to set the specified node ID to be un-favorited on the NodeDB on the device""" + """Tell the node to set the specified node ID to be un-favorited on the NodeDB on the device.""" self.ensureSessionKey() if isinstance(nodeId, str): if nodeId.startswith("!"): From 3008e039fbab120b49c258defbe294c8daf160dc Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 3 Mar 2025 06:51:12 -0600 Subject: [PATCH 2/8] Bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 577e13f9..1ab290e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "meshtastic" -version = "2.5.12" +version = "2.6.0" description = "Python API & client shell for talking to Meshtastic devices" authors = ["Meshtastic Developers "] license = "GPL-3.0-only" From 336e5154a37eda5f0ab586b7c41df33d6800ce6c Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 3 Mar 2025 06:58:45 -0600 Subject: [PATCH 3/8] Add args for cli --- meshtastic/__main__.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 7528e2a9..bca133d3 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -1809,12 +1809,40 @@ def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars action="store_true", ) + group.add_argument( + "--backup-prefs", + help="Tell the destination node to create a backup preferences file." + "Location: 0 for local flash, 1 for SD card.", + default=None, + nargs="?", + const=0, + ) + + group.add_argument( + "--restore-prefs", + help="Tell the destination node to remove backup preferences files." + "Location: 0 for local flash, 1 for SD card.", + default=None, + nargs="?", + const=0, + ) + + group.add_argument( + "--remove-backup-prefs", + help="Tell the destination node to remove backup preferences files." + "Location: 0 for local flash, 1 for SD card.", + default=None, + nargs="?", + const=0, + ) + group.add_argument( "--remove-node", help="Tell the destination node to remove a specific node from its NodeDB. " "Use the node ID with a '!' or '0x' prefix or the node number.", metavar="!xxxxxxxx" ) + group.add_argument( "--set-favorite-node", help="Tell the destination node to set the specified node to be favorited on the NodeDB. " From fb48fc20b3aa3f5c2739d2745a3fdb0864ae34e1 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 3 Mar 2025 07:56:03 -0600 Subject: [PATCH 4/8] Optionals --- meshtastic/node.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/meshtastic/node.py b/meshtastic/node.py index 98fbdccd..c9ce7ecc 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -707,7 +707,7 @@ def setFavorite(self, nodeId: Union[int, str]): onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) - def backupPreferences(self, location: int = 0): + def backupPreferences(self, location: Optional[int] = 0): """Tell the node to backup preferences to flash.""" self.ensureSessionKey() @@ -720,7 +720,7 @@ def backupPreferences(self, location: int = 0): onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) - def restorePreferences(self, location: int = 0): + def restorePreferences(self, location: Optional[int] = 0): """Tell the node to restore preferences from backup.""" self.ensureSessionKey() @@ -733,7 +733,7 @@ def restorePreferences(self, location: int = 0): onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) - def removePreferencesBackups(self, location: int = 0): + def removePreferencesBackups(self, location: Optional[int] = 0): """Tell the node to remove backup preferences from the filesystem.""" self.ensureSessionKey() From 2d88d8e918f1a45aaec0466d8f1ee18bb14fc0a4 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 3 Mar 2025 15:23:21 -0600 Subject: [PATCH 5/8] Good lawd I did that in a hurry --- meshtastic/__main__.py | 6 +++--- meshtastic/node.py | 7 ++++--- pyproject.toml | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index bca133d3..e588471a 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -465,17 +465,17 @@ def onConnected(interface): if args.backup_prefs: closeNow = True waitForAckNak = True - interface.getNode(args.dest, False, **getNode_kwargs).re(args.backup_preferences) + interface.getNode(args.dest, False, **getNode_kwargs).backup_preferences(args.backup_preferences) if args.restore_prefs: closeNow = True waitForAckNak = True - interface.getNode(args.dest, False, **getNode_kwargs).re(args.backup_preferences) + interface.getNode(args.dest, False, **getNode_kwargs).restore_prefs(args.restore_prefs) if args.remove_backup_prefs: closeNow = True waitForAckNak = True - interface.getNode(args.dest, False, **getNode_kwargs).re(args.remove_backup_preferences) + interface.getNode(args.dest, False, **getNode_kwargs).remove_backup_preferences(args.remove_backup_preferences) if args.set_ignored_node: closeNow = True diff --git a/meshtastic/node.py b/meshtastic/node.py index c9ce7ecc..dce7edde 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -707,8 +707,9 @@ def setFavorite(self, nodeId: Union[int, str]): onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) - def backupPreferences(self, location: Optional[int] = 0): + def backupPreferences(self, location: Optional[admin_pb2.AdminMessage.BackupLocation.ValueType] = 0): """Tell the node to backup preferences to flash.""" + print(f"Backing up preferences to location {location}") self.ensureSessionKey() p = admin_pb2.AdminMessage() @@ -720,7 +721,7 @@ def backupPreferences(self, location: Optional[int] = 0): onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) - def restorePreferences(self, location: Optional[int] = 0): + def restorePreferences(self, location: Optional[admin_pb2.AdminMessage.BackupLocation.ValueType] = 0): """Tell the node to restore preferences from backup.""" self.ensureSessionKey() @@ -733,7 +734,7 @@ def restorePreferences(self, location: Optional[int] = 0): onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) - def removePreferencesBackups(self, location: Optional[int] = 0): + def removePreferencesBackups(self, location: Optional[admin_pb2.AdminMessage.BackupLocation.ValueType] = 0): """Tell the node to remove backup preferences from the filesystem.""" self.ensureSessionKey() diff --git a/pyproject.toml b/pyproject.toml index 1ab290e7..979ecfe3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "meshtastic" -version = "2.6.0" +version = "2.6.0a1" description = "Python API & client shell for talking to Meshtastic devices" authors = ["Meshtastic Developers "] license = "GPL-3.0-only" From ec13bd308a6daef02ad68469565561cca45cb415 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 3 Mar 2025 15:23:48 -0600 Subject: [PATCH 6/8] Remove print --- meshtastic/node.py | 1 - 1 file changed, 1 deletion(-) diff --git a/meshtastic/node.py b/meshtastic/node.py index dce7edde..985f4c96 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -709,7 +709,6 @@ def setFavorite(self, nodeId: Union[int, str]): def backupPreferences(self, location: Optional[admin_pb2.AdminMessage.BackupLocation.ValueType] = 0): """Tell the node to backup preferences to flash.""" - print(f"Backing up preferences to location {location}") self.ensureSessionKey() p = admin_pb2.AdminMessage() From 5ee02647379d3e72ec308fc77eb9e7712d7aa815 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Mon, 3 Mar 2025 15:28:14 -0600 Subject: [PATCH 7/8] Where's intellisense when you need it --- meshtastic/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index e588471a..731c4cc0 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -465,17 +465,17 @@ def onConnected(interface): if args.backup_prefs: closeNow = True waitForAckNak = True - interface.getNode(args.dest, False, **getNode_kwargs).backup_preferences(args.backup_preferences) + interface.getNode(args.dest, False, **getNode_kwargs).backupPreferences(args.backup_preferences) if args.restore_prefs: closeNow = True waitForAckNak = True - interface.getNode(args.dest, False, **getNode_kwargs).restore_prefs(args.restore_prefs) + interface.getNode(args.dest, False, **getNode_kwargs).restorePreferences(args.restore_prefs) if args.remove_backup_prefs: closeNow = True waitForAckNak = True - interface.getNode(args.dest, False, **getNode_kwargs).remove_backup_preferences(args.remove_backup_preferences) + interface.getNode(args.dest, False, **getNode_kwargs).removePreferencesBackups(args.remove_backup_preferences) if args.set_ignored_node: closeNow = True From 7422d9d314c3f8966e0c31399272bc7fc266e999 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 4 Mar 2025 08:15:55 -0600 Subject: [PATCH 8/8] Update --- meshtastic/__main__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 731c4cc0..a9aa05e7 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -59,7 +59,7 @@ have_powermon = False powermon_exception = e meter = None -from meshtastic.protobuf import channel_pb2, config_pb2, portnums_pb2 +from meshtastic.protobuf import admin_pb2, channel_pb2, config_pb2, portnums_pb2 from meshtastic.version import get_active_version def onReceive(packet, interface) -> None: @@ -465,7 +465,8 @@ def onConnected(interface): if args.backup_prefs: closeNow = True waitForAckNak = True - interface.getNode(args.dest, False, **getNode_kwargs).backupPreferences(args.backup_preferences) + print(f"Backing up preferences to {args.backup_prefs}") + interface.getNode(args.dest, False, **getNode_kwargs).backupPreferences(args.backup_prefs) if args.restore_prefs: closeNow = True @@ -475,7 +476,7 @@ def onConnected(interface): if args.remove_backup_prefs: closeNow = True waitForAckNak = True - interface.getNode(args.dest, False, **getNode_kwargs).removePreferencesBackups(args.remove_backup_preferences) + interface.getNode(args.dest, False, **getNode_kwargs).removePreferencesBackups(args.remove_backup_prefs) if args.set_ignored_node: closeNow = True @@ -1813,7 +1814,7 @@ def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars "--backup-prefs", help="Tell the destination node to create a backup preferences file." "Location: 0 for local flash, 1 for SD card.", - default=None, + default=admin_pb2.AdminMessage.BackupLocation.FLASH, nargs="?", const=0, ) @@ -1822,7 +1823,7 @@ def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars "--restore-prefs", help="Tell the destination node to remove backup preferences files." "Location: 0 for local flash, 1 for SD card.", - default=None, + default=admin_pb2.AdminMessage.BackupLocation.FLASH, nargs="?", const=0, ) @@ -1831,7 +1832,7 @@ def addRemoteAdminArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentPars "--remove-backup-prefs", help="Tell the destination node to remove backup preferences files." "Location: 0 for local flash, 1 for SD card.", - default=None, + default=admin_pb2.AdminMessage.BackupLocation.FLASH, nargs="?", const=0, )