diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 82763b7..af2b597 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -372,22 +372,43 @@ def onConnected(interface): closeNow = True channelIndex = our_globals.get_channel_index() - print(f"Deleting channel {channelIndex}") - ch = getNode().deleteChannel(channelIndex) + if channelIndex is None: + meshtastic.util.our_exit("Warning: Need to specify '--ch-index' for '--ch-del'.", 1) + else: + if channelIndex == 0: + meshtastic.util.our_exit("Warning: Cannot delete primary channel.", 1) + else: + print(f"Deleting channel {channelIndex}") + ch = getNode().deleteChannel(channelIndex) - if args.ch_set or args.ch_longslow or args.ch_longfast or args.ch_mediumslow or args.ch_mediumfast or args.ch_shortslow or args.ch_shortfast: + ch_changes = [args.ch_longslow, args.ch_longfast, args.ch_mediumslow, args.ch_mediumfast, args.ch_shortslow, args.ch_shortfast] + any_primary_channel_changes = any(x for x in ch_changes) + if args.ch_set or any_primary_channel_changes or args.ch_enable or args.ch_disable: closeNow = True channelIndex = our_globals.get_channel_index() + if channelIndex is None: + if any_primary_channel_changes: + # we assume that they want the primary channel if they're setting range values + channelIndex = 0 + else: + meshtastic.util.our_exit("Warning: Need to specify '--ch-index'.", 1) ch = getNode().channels[channelIndex] - enable = args.ch_enable # should we enable this channel? + if any_primary_channel_changes or args.ch_enable or args.ch_disable: + + if channelIndex == 0 and not any_primary_channel_changes: + meshtastic.util.our_exit("Warning: Cannot enable/disable PRIMARY channel.") - if args.ch_longslow or args.ch_longfast or args.ch_mediumslow or args.ch_mediumfast or args.ch_shortslow or args.ch_shortfast: if channelIndex != 0: - meshtastic.util.our_exit("Warning: Standard channel settings can only be applied to the PRIMARY channel") + if any_primary_channel_changes: + meshtastic.util.our_exit("Warning: Standard channel settings can only be applied to the PRIMARY channel") - enable = True # force enable + enable = True # default to enable + if args.ch_enable: + enable = True + if args.ch_disable: + enable = False def setSimpleChannel(modem_config): """Set one of the simple modem_config only based channels""" @@ -637,10 +658,11 @@ def initParser(): "--ch-del", help="Delete the ch-index channel", action='store_true') parser.add_argument( - "--ch-enable", help="Enable the specified channel", action="store_true", dest="ch_enable") + "--ch-enable", help="Enable the specified channel", action="store_true", dest="ch_enable", default=False) + # Note: We are doing a double negative here (Do we want to disable? If ch_disable==True, then disable.) parser.add_argument( - "--ch-disable", help="Disable the specified channel", action="store_false", dest="ch_enable") + "--ch-disable", help="Disable the specified channel", action="store_true", dest="ch_disable", default=False) parser.add_argument( "--ch-set", help="Set a channel parameter", nargs=2, action='append') diff --git a/meshtastic/globals.py b/meshtastic/globals.py index eec25f5..b00683c 100644 --- a/meshtastic/globals.py +++ b/meshtastic/globals.py @@ -28,7 +28,7 @@ class Globals: self.args = None self.parser = None self.target_node = None - self.channel_index = 0 + self.channel_index = None def set_args(self, args): """Set the args""" diff --git a/meshtastic/node.py b/meshtastic/node.py index 4b2bd4a..c611e25 100644 --- a/meshtastic/node.py +++ b/meshtastic/node.py @@ -28,8 +28,9 @@ class Node: print("Channels:") if self.channels: for c in self.channels: - if c.role != channel_pb2.Channel.Role.DISABLED: - cStr = stripnl(MessageToJson(c.settings)) + cStr = stripnl(MessageToJson(c.settings)) + # only show if there is no psk (meaning disabled channel) + if c.settings.psk: print(f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}") publicURL = self.getURL(includeAll=False) adminURL = self.getURL(includeAll=True) @@ -82,7 +83,7 @@ class Node: def deleteChannel(self, channelIndex): """Delete the specifed channelIndex and shift other channels up""" ch = self.channels[channelIndex] - if ch.role != channel_pb2.Channel.Role.SECONDARY: + if ch.role not in (channel_pb2.Channel.Role.SECONDARY, channel_pb2.Channel.Role.DISABLED): our_exit("Warning: Only SECONDARY channels can be deleted") # we are careful here because if we move the "admin" channel the channelIndex we need to use diff --git a/meshtastic/tests/test_main.py b/meshtastic/tests/test_main.py index 8bd8742..39a838c 100644 --- a/meshtastic/tests/test_main.py +++ b/meshtastic/tests/test_main.py @@ -114,7 +114,7 @@ def test_main_ch_index_no_devices(patched_find_ports, capsys): our_globals.set_parser(parser) our_globals.set_args(args) assert our_globals.get_target_node() is None - assert our_globals.get_channel_index() == 0 + assert our_globals.get_channel_index() is None with pytest.raises(SystemExit) as pytest_wrapped_e: main() assert our_globals.get_channel_index() == 1 diff --git a/meshtastic/tests/test_smoke1.py b/meshtastic/tests/test_smoke1.py index 767da1a..b7f39e8 100644 --- a/meshtastic/tests/test_smoke1.py +++ b/meshtastic/tests/test_smoke1.py @@ -276,6 +276,12 @@ def test_smoke1_ch_set_name(): time.sleep(PAUSE_AFTER_COMMAND) return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel') assert re.match(r'Connected to radio', out) + assert re.search(r'Warning: Need to specify', out, re.MULTILINE) + assert return_value == 1 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel --ch-index 0') + assert re.match(r'Connected to radio', out) assert re.search(r'^Set name to MyChannel', out, re.MULTILINE) assert return_value == 0 # pause for the radio @@ -290,6 +296,12 @@ def test_smoke1_ch_set_downlink_and_uplink(): """Test -ch-set downlink_enabled X and --ch-set uplink_enabled X""" return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false') assert re.match(r'Connected to radio', out) + assert re.search(r'Warning: Need to specify', out, re.MULTILINE) + assert return_value == 1 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0') + assert re.match(r'Connected to radio', out) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) @@ -299,7 +311,7 @@ def test_smoke1_ch_set_downlink_and_uplink(): assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled true --ch-set uplink_enabled true') + return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0') assert re.match(r'Connected to radio', out) assert re.search(r'^Set downlink_enabled to true', out, re.MULTILINE) assert re.search(r'^Set uplink_enabled to true', out, re.MULTILINE) @@ -340,15 +352,220 @@ def test_smoke1_ch_add_and_ch_del(): assert return_value == 0 +@pytest.mark.smoke1 +def test_smoke1_ch_enable_and_disable(): + """Test --ch-enable and --ch-disable""" + return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing') + assert re.search(r'Writing modified channels to device', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --info') + assert re.match(r'Connected to radio', out) + assert re.search(r'SECONDARY', out, re.MULTILINE) + assert re.search(r'testing', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + # ensure they need to specify a --ch-index + return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable') + assert return_value == 1 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 1') + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --info') + assert re.match(r'Connected to radio', out) + assert re.search(r'DISABLED', out, re.MULTILINE) + assert re.search(r'testing', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 1') + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --info') + assert re.match(r'Connected to radio', out) + assert re.search(r'SECONDARY', out, re.MULTILINE) + assert re.search(r'testing', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + + +@pytest.mark.smoke1 +def test_smoke1_ch_del_a_disabled_non_primary_channel(): + """Test --ch-del will work on a disabled non-primary channel.""" + return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing') + assert re.search(r'Writing modified channels to device', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --info') + assert re.match(r'Connected to radio', out) + assert re.search(r'SECONDARY', out, re.MULTILINE) + assert re.search(r'testing', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + # ensure they need to specify a --ch-index + return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable') + assert return_value == 1 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --info') + assert re.match(r'Connected to radio', out) + assert not re.search(r'DISABLED', out, re.MULTILINE) + assert not re.search(r'SECONDARY', out, re.MULTILINE) + assert not re.search(r'testing', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + + +@pytest.mark.smoke1 +def test_smoke1_attempt_to_delete_primary_channel(): + """Test that we cannot delete the PRIMARY channel.""" + return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 0') + assert re.search(r'Warning: Cannot delete primary channel', out, re.MULTILINE) + assert return_value == 1 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + + +@pytest.mark.smoke1 +def test_smoke1_attempt_to_disable_primary_channel(): + """Test that we cannot disable the PRIMARY channel.""" + return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 0') + assert re.search(r'Warning: Cannot enable', out, re.MULTILINE) + assert return_value == 1 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + + +@pytest.mark.smoke1 +def test_smoke1_attempt_to_enable_primary_channel(): + """Test that we cannot enable the PRIMARY channel.""" + return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 0') + assert re.search(r'Warning: Cannot enable', out, re.MULTILINE) + assert return_value == 1 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + + +@pytest.mark.smoke1 +def test_smoke1_ensure_ch_del_second_of_three_channels(): + """Test that when we delete the 2nd of 3 channels, that it deletes the correct channel.""" + return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1') + assert re.match(r'Connected to radio', out) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --info') + assert re.match(r'Connected to radio', out) + assert re.search(r'SECONDARY', out, re.MULTILINE) + assert re.search(r'testing1', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2') + assert re.match(r'Connected to radio', out) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --info') + assert re.match(r'Connected to radio', out) + assert re.search(r'testing2', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') + assert re.match(r'Connected to radio', out) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --info') + assert re.match(r'Connected to radio', out) + assert re.search(r'testing2', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') + assert re.match(r'Connected to radio', out) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + + +@pytest.mark.smoke1 +def test_smoke1_ensure_ch_del_third_of_three_channels(): + """Test that when we delete the 3rd of 3 channels, that it deletes the correct channel.""" + return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1') + assert re.match(r'Connected to radio', out) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --info') + assert re.match(r'Connected to radio', out) + assert re.search(r'SECONDARY', out, re.MULTILINE) + assert re.search(r'testing1', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2') + assert re.match(r'Connected to radio', out) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --info') + assert re.match(r'Connected to radio', out) + assert re.search(r'testing2', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 2') + assert re.match(r'Connected to radio', out) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --info') + assert re.match(r'Connected to radio', out) + assert re.search(r'testing1', out, re.MULTILINE) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') + assert re.match(r'Connected to radio', out) + assert return_value == 0 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) + + @pytest.mark.smoke1 def test_smoke1_ch_set_modem_config(): """Test --ch-set modem_config""" + return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config Bw31_25Cr48Sf512') + assert re.search(r'Warning: Need to specify', out, re.MULTILINE) + assert return_value == 1 + # pause for the radio + time.sleep(PAUSE_AFTER_COMMAND) return_value, out = subprocess.getstatusoutput('meshtastic --info') assert not re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE) assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND) - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config Bw31_25Cr48Sf512') + return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config Bw31_25Cr48Sf512 --ch-index 0') assert re.match(r'Connected to radio', out) assert re.search(r'^Set modem_config to Bw31_25Cr48Sf512', out, re.MULTILINE) assert return_value == 0 @@ -363,7 +580,7 @@ def test_smoke1_ch_set_modem_config(): def test_smoke1_seturl_default(): """Test --seturl with default value""" # set some channel value so we no longer have a default channel - return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name foo') + return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name foo --ch-index 0') assert return_value == 0 # pause for the radio time.sleep(PAUSE_AFTER_COMMAND)