From 1b5b07e752ded365db705b87757f8e6cb1fa11be Mon Sep 17 00:00:00 2001 From: pdxlocations Date: Sat, 21 Jun 2025 13:44:25 -0700 Subject: [PATCH 1/8] add --set-is-unmessageable --- meshtastic/__main__.py | 27 +++++++++++++++++++++++++++ meshtastic/node.py | 17 +++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index ee869e9b..26410432 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -350,6 +350,26 @@ def onConnected(interface): print(f"Setting device owner short to {args.set_owner_short}") interface.getNode(args.dest, False, **getNode_kwargs).setOwner(long_name=args.set_owner, short_name=args.set_owner_short) + if args.set_is_unmessageable: + closeNow = True + waitForAckNak = True + print(f"Setting is_unmessagable to {args.set_is_unmessageable}") + if isinstance(args.set_is_unmessageable, str): + val = meshtastic.util.fromStr(args.set_is_unmessageable) + else: + val = args.set_is_unmessageable + interface.getNode(args.dest, **getNode_kwargs).setIsUnmessageable(is_unmessagable=val) + + if args.set_is_unmessagable: + closeNow = True + waitForAckNak = True + print(f"Setting is_unmessagable to {args.set_is_unmessageable}") + if isinstance(args.set_is_unmessageable, str): + val = meshtastic.util.fromStr(args.set_is_unmessageable) + else: + val = args.set_is_unmessageable + interface.getNode(args.dest, **getNode_kwargs).setIsUnmessageable(is_unmessagable=val) + # TODO: add to export-config and configure if args.set_canned_message: closeNow = True @@ -1540,6 +1560,13 @@ def addConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: "--set-ham", help="Set licensed Ham ID and turn off encryption", action="store" ) + group.add_argument( + "--set-is-unmessageable", help="Set if a node is messageable or not", action="store" + ) + + group.add_argument( + "--set-is-unmessagable", help="Set if a node is messageable or not", action="store" +) group.add_argument( "--ch-set-url", "--seturl", help="Set all channels and set LoRa config from a supplied URL", diff --git a/meshtastic/node.py b/meshtastic/node.py index e54963c1..934a65a4 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -326,6 +326,23 @@ def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None else: onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) + + def setIsUnmessageable(self, is_unmessagable: Optional[bool]=False): + """Set if a device is messagable or not""" + self.ensureSessionKey() + p = admin_pb2.AdminMessage() + + if is_unmessagable is not None: + p.set_owner.is_unmessagable = is_unmessagable + + # Note: These debug lines are used in unit tests + logging.debug(f"p.set_owner.is_unmessageable:{p.set_owner.is_unmessagable}:") + # If sending to a remote node, wait for ACK/NAK + if self == self.iface.localNode: + onResponse = None + else: + onResponse = self.onAckNak + return self._sendAdmin(p, onResponse=onResponse) def getURL(self, includeAll: bool = True): """The sharable URL that describes the current channel""" From b73fcbff884a224de50fb4e810c60d26d7d3f0ee Mon Sep 17 00:00:00 2001 From: pdxlocations Date: Sat, 21 Jun 2025 13:50:30 -0700 Subject: [PATCH 2/8] didn't spell it wrong --- meshtastic/__main__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 26410432..8eb8981f 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -363,11 +363,11 @@ def onConnected(interface): if args.set_is_unmessagable: closeNow = True waitForAckNak = True - print(f"Setting is_unmessagable to {args.set_is_unmessageable}") - if isinstance(args.set_is_unmessageable, str): - val = meshtastic.util.fromStr(args.set_is_unmessageable) + print(f"Setting is_unmessagable to {args.set_is_unmessagable}") + if isinstance(args.set_is_unmessagable, str): + val = meshtastic.util.fromStr(args.set_is_unmessagable) else: - val = args.set_is_unmessageable + val = args.set_is_unmessagable interface.getNode(args.dest, **getNode_kwargs).setIsUnmessageable(is_unmessagable=val) # TODO: add to export-config and configure From 7160e79fbff5d04d3947e9117b9f8e000d76709d Mon Sep 17 00:00:00 2001 From: pdxlocations Date: Sat, 21 Jun 2025 13:51:58 -0700 Subject: [PATCH 3/8] more not incorrect spelling --- meshtastic/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtastic/node.py b/meshtastic/node.py index 934a65a4..7ca0487b 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -336,7 +336,7 @@ def setIsUnmessageable(self, is_unmessagable: Optional[bool]=False): p.set_owner.is_unmessagable = is_unmessagable # Note: These debug lines are used in unit tests - logging.debug(f"p.set_owner.is_unmessageable:{p.set_owner.is_unmessagable}:") + logging.debug(f"p.set_owner.is_unmessagable:{p.set_owner.is_unmessagable}:") # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: onResponse = None From 8752a0de6e26f077f02f84a3a65ff425646b6896 Mon Sep 17 00:00:00 2001 From: pdxlocations Date: Sat, 21 Jun 2025 16:25:48 -0700 Subject: [PATCH 4/8] remove whitespace --- meshtastic/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtastic/node.py b/meshtastic/node.py index 7ca0487b..40881a0e 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -326,7 +326,7 @@ def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None else: onResponse = self.onAckNak return self._sendAdmin(p, onResponse=onResponse) - + def setIsUnmessageable(self, is_unmessagable: Optional[bool]=False): """Set if a device is messagable or not""" self.ensureSessionKey() From 51b543ff40e8e19a8eac58ea7480f20911ff1c55 Mon Sep 17 00:00:00 2001 From: pdxlocations Date: Sat, 21 Jun 2025 18:16:24 -0700 Subject: [PATCH 5/8] add tests --- meshtastic/tests/test_main.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/meshtastic/tests/test_main.py b/meshtastic/tests/test_main.py index 8b70a4e8..c75251e1 100644 --- a/meshtastic/tests/test_main.py +++ b/meshtastic/tests/test_main.py @@ -454,6 +454,37 @@ def test_main_set_owner_short_to_bob(capsys): assert err == "" mo.assert_called() +@pytest.mark.unit +@pytest.mark.usefixtures("reset_mt_config") +def test_main_set_is_unmessageable_to_true(capsys): + """Test --set-is-unmessageable true""" + sys.argv = ["", "--set-is-unmessageable", "true"] + mt_config.args = sys.argv + + iface = MagicMock(autospec=SerialInterface) + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: + main() + out, err = capsys.readouterr() + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Setting is_unmessagable to true", out, re.MULTILINE) + assert err == "" + mo.assert_called() + +@pytest.mark.unit +@pytest.mark.usefixtures("reset_mt_config") +def test_main_set_is_unmessagable_to_true(capsys): + """Test --set-is-unmessagable true""" + sys.argv = ["", "--set-is-unmessagable", "true"] + mt_config.args = sys.argv + + iface = MagicMock(autospec=SerialInterface) + with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: + main() + out, err = capsys.readouterr() + assert re.search(r"Connected to radio", out, re.MULTILINE) + assert re.search(r"Setting is_unmessagable to true", out, re.MULTILINE) + assert err == "" + mo.assert_called() @pytest.mark.unit @pytest.mark.usefixtures("reset_mt_config") From 373b8a3139429162a2052778a224b822d8d10084 Mon Sep 17 00:00:00 2001 From: pdxlocations Date: Mon, 23 Jun 2025 16:14:59 -0700 Subject: [PATCH 6/8] combine with set_owner handling --- meshtastic/__main__.py | 41 +++++++++++++++-------------------- meshtastic/node.py | 22 ++++--------------- meshtastic/tests/test_main.py | 4 ++-- 3 files changed, 24 insertions(+), 43 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 8eb8981f..bce45e35 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -339,36 +339,31 @@ def onConnected(interface): # can include lat/long/alt etc: latitude = 37.5, longitude = -122.1 interface.getNode(args.dest, False, **getNode_kwargs).setFixedPosition(lat, lon, alt) - if args.set_owner or args.set_owner_short: + if args.set_owner or args.set_owner_short or args.set_is_unmessageable or args.set_is_unmessagable: closeNow = True waitForAckNak = True if args.set_owner and args.set_owner_short: print(f"Setting device owner to {args.set_owner} and short name to {args.set_owner_short}") elif args.set_owner: print(f"Setting device owner to {args.set_owner}") - else: # short name only + elif args.set_owner_short and not args.set_owner: print(f"Setting device owner short to {args.set_owner_short}") - interface.getNode(args.dest, False, **getNode_kwargs).setOwner(long_name=args.set_owner, short_name=args.set_owner_short) - - if args.set_is_unmessageable: - closeNow = True - waitForAckNak = True - print(f"Setting is_unmessagable to {args.set_is_unmessageable}") - if isinstance(args.set_is_unmessageable, str): - val = meshtastic.util.fromStr(args.set_is_unmessageable) - else: - val = args.set_is_unmessageable - interface.getNode(args.dest, **getNode_kwargs).setIsUnmessageable(is_unmessagable=val) - - if args.set_is_unmessagable: - closeNow = True - waitForAckNak = True - print(f"Setting is_unmessagable to {args.set_is_unmessagable}") - if isinstance(args.set_is_unmessagable, str): - val = meshtastic.util.fromStr(args.set_is_unmessagable) - else: - val = args.set_is_unmessagable - interface.getNode(args.dest, **getNode_kwargs).setIsUnmessageable(is_unmessagable=val) + unmessageable = ( + args.set_is_unmessageable + if args.set_is_unmessageable is not None + else args.set_is_unmessagable + ) + set_is_unmessagable = ( + meshtastic.util.fromStr(unmessageable) + if isinstance(unmessageable, str) + else unmessageable + ) + if set_is_unmessagable is not None: + print(f"Setting device owner is_unmessageable to {set_is_unmessagable}") + interface.getNode( + args.dest, False, **getNode_kwargs).setOwner(long_name=args.set_owner, + short_name=args.set_owner_short, is_unmessagable=set_is_unmessagable + ) # TODO: add to export-config and configure if args.set_canned_message: diff --git a/meshtastic/node.py b/meshtastic/node.py index 40881a0e..13cb9732 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -298,7 +298,7 @@ def _getAdminChannelIndex(self): return c.index return 0 - def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None, is_licensed: bool=False): + def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None, is_licensed: bool=False, is_unmessagable: Optional[bool]=None): """Set device owner name""" logging.debug(f"in setOwner nodeNum:{self.nodeNum}") self.ensureSessionKey() @@ -315,27 +315,13 @@ def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None short_name = short_name[:nChars] print(f"Maximum is 4 characters, truncated to {short_name}") p.set_owner.short_name = short_name - + if is_unmessagable is not None: + p.set_owner.is_unmessagable = is_unmessagable + # Note: These debug lines are used in unit tests logging.debug(f"p.set_owner.long_name:{p.set_owner.long_name}:") logging.debug(f"p.set_owner.short_name:{p.set_owner.short_name}:") logging.debug(f"p.set_owner.is_licensed:{p.set_owner.is_licensed}") - # If sending to a remote node, wait for ACK/NAK - if self == self.iface.localNode: - onResponse = None - else: - onResponse = self.onAckNak - return self._sendAdmin(p, onResponse=onResponse) - - def setIsUnmessageable(self, is_unmessagable: Optional[bool]=False): - """Set if a device is messagable or not""" - self.ensureSessionKey() - p = admin_pb2.AdminMessage() - - if is_unmessagable is not None: - p.set_owner.is_unmessagable = is_unmessagable - - # Note: These debug lines are used in unit tests logging.debug(f"p.set_owner.is_unmessagable:{p.set_owner.is_unmessagable}:") # If sending to a remote node, wait for ACK/NAK if self == self.iface.localNode: diff --git a/meshtastic/tests/test_main.py b/meshtastic/tests/test_main.py index c75251e1..39bd599b 100644 --- a/meshtastic/tests/test_main.py +++ b/meshtastic/tests/test_main.py @@ -466,7 +466,7 @@ def test_main_set_is_unmessageable_to_true(capsys): main() out, err = capsys.readouterr() assert re.search(r"Connected to radio", out, re.MULTILINE) - assert re.search(r"Setting is_unmessagable to true", out, re.MULTILINE) + assert re.search(r"Setting device owner is_unmessageable to True", out, re.MULTILINE) assert err == "" mo.assert_called() @@ -482,7 +482,7 @@ def test_main_set_is_unmessagable_to_true(capsys): main() out, err = capsys.readouterr() assert re.search(r"Connected to radio", out, re.MULTILINE) - assert re.search(r"Setting is_unmessagable to true", out, re.MULTILINE) + assert re.search(r"Setting device owner is_unmessageable to True", out, re.MULTILINE) assert err == "" mo.assert_called() From 22b3062151d261ff34ef4360125872aa6190a004 Mon Sep 17 00:00:00 2001 From: pdxlocations Date: Mon, 23 Jun 2025 16:37:49 -0700 Subject: [PATCH 7/8] remove whitespace --- meshtastic/node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meshtastic/node.py b/meshtastic/node.py index 13cb9732..90eeba40 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -317,7 +317,7 @@ def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None p.set_owner.short_name = short_name if is_unmessagable is not None: p.set_owner.is_unmessagable = is_unmessagable - + # Note: These debug lines are used in unit tests logging.debug(f"p.set_owner.long_name:{p.set_owner.long_name}:") logging.debug(f"p.set_owner.short_name:{p.set_owner.short_name}:") From 9b5a889676128513bbfaca7ab8a7c2d6c0f2142e Mon Sep 17 00:00:00 2001 From: pdxlocations Date: Tue, 22 Jul 2025 10:17:40 -0700 Subject: [PATCH 8/8] combine arguments --- meshtastic/__main__.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 2ee6e001..a494e02b 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -1599,12 +1599,9 @@ def addConfigArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: ) group.add_argument( - "--set-is-unmessageable", help="Set if a node is messageable or not", action="store" + "--set-is-unmessageable", "--set-is-unmessagable", help="Set if a node is messageable or not", action="store" ) - group.add_argument( - "--set-is-unmessagable", help="Set if a node is messageable or not", action="store" -) group.add_argument( "--ch-set-url", "--seturl", help="Set all channels and set LoRa config from a supplied URL",