From 43a685f012e6ec6dbb2a8b0f7ac133c39aea83cc Mon Sep 17 00:00:00 2001 From: Niklas Roslund Date: Sun, 5 Oct 2025 12:55:59 +0200 Subject: [PATCH 1/2] cli: notmalize ignore IDs for dec, hex and 0x. support YAML [] clear Signed-off-by: Niklas Roslund --- meshtastic/__main__.py | 3 ++- meshtastic/node.py | 54 ++++++++++++++++-------------------------- 2 files changed, 23 insertions(+), 34 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 002f1ec30..2475f8fcd 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -277,7 +277,8 @@ def setPref(config, comp_name, raw_val) -> bool: else: print(f"Adding '{raw_val}' to the {pref.name} list") cur_vals = [x for x in getattr(config_values, pref.name) if x not in [0, "", b""]] - cur_vals.append(val) + if val not in cur_vals: + cur_vals.append(val) getattr(config_values, pref.name)[:] = cur_vals return True diff --git a/meshtastic/node.py b/meshtastic/node.py index b77ad9218..1daaee091 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -52,6 +52,20 @@ def __repr__(self): r += ")" return r + def _to_node_num(self, nodeId: Union[int, str]) -> int: + """Normalize node id from int | '!hex' | '0xhex' | 'decimal' to int.""" + if isinstance(nodeId, int): + return nodeId + s = str(nodeId).strip() + if s.startswith("!"): + s = s[1:] + if s.lower().startswith("0x"): + return int(s, 16) + try: + return int(s, 10) + except ValueError: + return int(s, 16) + def module_available(self, excluded_bit: int) -> bool: """Check DeviceMetadata.excluded_modules to see if a module is available.""" meta = getattr(self.iface, "metadata", None) @@ -714,11 +728,7 @@ def factoryReset(self, full: bool = False): def removeNode(self, nodeId: Union[int, str]): """Tell the node to remove a specific node by ID""" self.ensureSessionKey() - if isinstance(nodeId, str): - if nodeId.startswith("!"): - nodeId = int(nodeId[1:], 16) - else: - nodeId = int(nodeId) + nodeId = self._to_node_num(nodeId) p = admin_pb2.AdminMessage() p.remove_by_nodenum = nodeId @@ -732,11 +742,7 @@ def removeNode(self, nodeId: Union[int, str]): def setFavorite(self, nodeId: Union[int, str]): """Tell the node to set the specified node ID to be favorited on the NodeDB on the device""" self.ensureSessionKey() - if isinstance(nodeId, str): - if nodeId.startswith("!"): - nodeId = int(nodeId[1:], 16) - else: - nodeId = int(nodeId) + nodeId = self._to_node_num(nodeId) p = admin_pb2.AdminMessage() p.set_favorite_node = nodeId @@ -750,11 +756,7 @@ def setFavorite(self, nodeId: Union[int, str]): 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""" self.ensureSessionKey() - if isinstance(nodeId, str): - if nodeId.startswith("!"): - nodeId = int(nodeId[1:], 16) - else: - nodeId = int(nodeId) + nodeId = self._to_node_num(nodeId) p = admin_pb2.AdminMessage() p.remove_favorite_node = nodeId @@ -768,11 +770,7 @@ def removeFavorite(self, nodeId: Union[int, str]): def setIgnored(self, nodeId: Union[int, str]): """Tell the node to set the specified node ID to be ignored on the NodeDB on the device""" self.ensureSessionKey() - if isinstance(nodeId, str): - if nodeId.startswith("!"): - nodeId = int(nodeId[1:], 16) - else: - nodeId = int(nodeId) + nodeId = self._to_node_num(nodeId) p = admin_pb2.AdminMessage() p.set_ignored_node = nodeId @@ -786,11 +784,7 @@ def setIgnored(self, nodeId: Union[int, str]): def removeIgnored(self, nodeId: Union[int, str]): """Tell the node to set the specified node ID to be un-ignored on the NodeDB on the device""" self.ensureSessionKey() - if isinstance(nodeId, str): - if nodeId.startswith("!"): - nodeId = int(nodeId[1:], 16) - else: - nodeId = int(nodeId) + nodeId = self._to_node_num(nodeId) p = admin_pb2.AdminMessage() p.remove_ignored_node = nodeId @@ -1013,10 +1007,7 @@ def _sendAdmin( ): # unless a special channel index was used, we want to use the admin index adminIndex = self.iface.localNode._getAdminChannelIndex() logger.debug(f"adminIndex:{adminIndex}") - if isinstance(self.nodeNum, int): - nodeid = self.nodeNum - else: # assume string starting with ! - nodeid = int(self.nodeNum[1:],16) + nodeid = self._to_node_num(self.nodeNum) if "adminSessionPassKey" in self.iface._getOrCreateByNum(nodeid): p.session_passkey = self.iface._getOrCreateByNum(nodeid).get("adminSessionPassKey") return self.iface.sendData( @@ -1037,9 +1028,6 @@ def ensureSessionKey(self): f"Not ensuring session key, because protocol use is disabled by noProto" ) else: - if isinstance(self.nodeNum, int): - nodeid = self.nodeNum - else: # assume string starting with ! - nodeid = int(self.nodeNum[1:],16) + nodeid = self._to_node_num(self.nodeNum) if self.iface._getOrCreateByNum(nodeid).get("adminSessionPassKey") is None: self.requestConfig(admin_pb2.AdminMessage.SESSIONKEY_CONFIG) From 1d3a7d39f7220a31dbf857e27485092a19e91f01 Mon Sep 17 00:00:00 2001 From: Niklas Roslund Date: Thu, 6 Nov 2025 19:05:34 +0100 Subject: [PATCH 2/2] Move to_node_num to util and updated function calls --- meshtastic/node.py | 29 ++++++++--------------------- meshtastic/util.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/meshtastic/node.py b/meshtastic/node.py index 1daaee091..660c705ec 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -16,6 +16,7 @@ pskToString, stripnl, message_to_json, + to_node_num, ) logger = logging.getLogger(__name__) @@ -52,20 +53,6 @@ def __repr__(self): r += ")" return r - def _to_node_num(self, nodeId: Union[int, str]) -> int: - """Normalize node id from int | '!hex' | '0xhex' | 'decimal' to int.""" - if isinstance(nodeId, int): - return nodeId - s = str(nodeId).strip() - if s.startswith("!"): - s = s[1:] - if s.lower().startswith("0x"): - return int(s, 16) - try: - return int(s, 10) - except ValueError: - return int(s, 16) - def module_available(self, excluded_bit: int) -> bool: """Check DeviceMetadata.excluded_modules to see if a module is available.""" meta = getattr(self.iface, "metadata", None) @@ -728,7 +715,7 @@ def factoryReset(self, full: bool = False): def removeNode(self, nodeId: Union[int, str]): """Tell the node to remove a specific node by ID""" self.ensureSessionKey() - nodeId = self._to_node_num(nodeId) + nodeId = to_node_num(nodeId) p = admin_pb2.AdminMessage() p.remove_by_nodenum = nodeId @@ -742,7 +729,7 @@ def removeNode(self, nodeId: Union[int, str]): def setFavorite(self, nodeId: Union[int, str]): """Tell the node to set the specified node ID to be favorited on the NodeDB on the device""" self.ensureSessionKey() - nodeId = self._to_node_num(nodeId) + nodeId = to_node_num(nodeId) p = admin_pb2.AdminMessage() p.set_favorite_node = nodeId @@ -756,7 +743,7 @@ def setFavorite(self, nodeId: Union[int, str]): 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""" self.ensureSessionKey() - nodeId = self._to_node_num(nodeId) + nodeId = to_node_num(nodeId) p = admin_pb2.AdminMessage() p.remove_favorite_node = nodeId @@ -770,7 +757,7 @@ def removeFavorite(self, nodeId: Union[int, str]): def setIgnored(self, nodeId: Union[int, str]): """Tell the node to set the specified node ID to be ignored on the NodeDB on the device""" self.ensureSessionKey() - nodeId = self._to_node_num(nodeId) + nodeId = to_node_num(nodeId) p = admin_pb2.AdminMessage() p.set_ignored_node = nodeId @@ -784,7 +771,7 @@ def setIgnored(self, nodeId: Union[int, str]): def removeIgnored(self, nodeId: Union[int, str]): """Tell the node to set the specified node ID to be un-ignored on the NodeDB on the device""" self.ensureSessionKey() - nodeId = self._to_node_num(nodeId) + nodeId = to_node_num(nodeId) p = admin_pb2.AdminMessage() p.remove_ignored_node = nodeId @@ -1007,7 +994,7 @@ def _sendAdmin( ): # unless a special channel index was used, we want to use the admin index adminIndex = self.iface.localNode._getAdminChannelIndex() logger.debug(f"adminIndex:{adminIndex}") - nodeid = self._to_node_num(self.nodeNum) + nodeid = to_node_num(self.nodeNum) if "adminSessionPassKey" in self.iface._getOrCreateByNum(nodeid): p.session_passkey = self.iface._getOrCreateByNum(nodeid).get("adminSessionPassKey") return self.iface.sendData( @@ -1028,6 +1015,6 @@ def ensureSessionKey(self): f"Not ensuring session key, because protocol use is disabled by noProto" ) else: - nodeid = self._to_node_num(self.nodeNum) + nodeid = to_node_num(self.nodeNum) if self.iface._getOrCreateByNum(nodeid).get("adminSessionPassKey") is None: self.requestConfig(admin_pb2.AdminMessage.SESSIONKEY_CONFIG) diff --git a/meshtastic/util.py b/meshtastic/util.py index 243dfe933..0e2445a33 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -692,3 +692,20 @@ def message_to_json(message: Message, multiline: bool=False) -> str: except TypeError: json = MessageToJson(message, including_default_value_fields=True) # type: ignore[call-arg] # pylint: disable=E1123 return stripnl(json) if not multiline else json + + +def to_node_num(node_id: Union[int, str]) -> int: + """ + Normalize a node id from int | '!hex' | '0xhex' | 'decimal' to int. + """ + if isinstance(node_id, int): + return node_id + s = str(node_id).strip() + if s.startswith("!"): + s = s[1:] + if s.lower().startswith("0x"): + return int(s, 16) + try: + return int(s, 10) + except ValueError: + return int(s, 16)