Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add more info/checking on --sendtext and --ch-index; wrote helper met… #176

Merged
merged 2 commits into from
Dec 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 11 additions & 8 deletions meshtastic/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def onReceive(packet, interface):
args = our_globals.get_args()
try:
d = packet.get('decoded')
logging.debug(f'd:{d}')

# Exit once we receive a reply
if args and args.sendtext and packet["to"] == interface.myInfo.my_node_num and d["portnum"] == portnums_pb2.PortNum.TEXT_MESSAGE_APP:
Expand Down Expand Up @@ -243,8 +244,12 @@ def getNode():
channelIndex = 0
if args.ch_index is not None:
channelIndex = int(args.ch_index)
print(f"Sending text message {args.sendtext} to {args.destOrAll}")
interface.sendText(args.sendtext, args.destOrAll, wantAck=True, channelIndex=channelIndex)
ch = interface.getChannelByChannelIndex(channelIndex)
if ch and ch.role != channel_pb2.Channel.Role.DISABLED:
print(f"Sending text message {args.sendtext} to {args.destOrAll} on channelIndex:{channelIndex}")
interface.sendText(args.sendtext, args.destOrAll, wantAck=True, channelIndex=channelIndex)
else:
meshtastic.util.our_exit(f"Warning: {channelIndex} is not a valid channel. Channel must not be DISABLED.")

if args.sendping:
payload = str.encode("test string")
Expand Down Expand Up @@ -565,7 +570,8 @@ def common():
our_globals = Globals.getInstance()
args = our_globals.get_args()
parser = our_globals.get_parser()
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO)
logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO,
format='%(levelname)s file:%(filename)s %(funcName)s line:%(lineno)s %(message)s')

if len(sys.argv) == 1:
parser.print_help(sys.stderr)
Expand Down Expand Up @@ -688,7 +694,7 @@ def initParser():
"--seturl", help="Set a channel URL", action="store")

parser.add_argument(
"--ch-index", help="Set the specified channel index", action="store")
"--ch-index", help="Set the specified channel index. Channels start at 0 (0 is the PRIMARY channel).", action="store")

parser.add_argument(
"--ch-add", help="Add a secondary channel, you must specify a channel name", default=None)
Expand Down Expand Up @@ -738,17 +744,14 @@ def initParser():
"--dest", help="The destination node id for any sent commands, if not set '^all' or '^local' is assumed as appropriate", default=None)

parser.add_argument(
"--sendtext", help="Send a text message")
"--sendtext", help="Send a text message. Can specify a destination '--dest' and/or channel index '--ch-index'.")

parser.add_argument(
"--sendping", help="Send a ping message (which requests a reply)", action="store_true")

parser.add_argument(
"--reboot", help="Tell the destination node to reboot", action="store_true")

# parser.add_argument(
# "--repeat", help="Normally the send commands send only one message, use this option to request repeated sends")

parser.add_argument(
"--reply", help="Reply to received messages",
action="store_true")
Expand Down
10 changes: 10 additions & 0 deletions meshtastic/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ def writeChannel(self, channelIndex, adminIndex=0):
self._sendAdmin(p, adminIndex=adminIndex)
logging.debug(f"Wrote channel {channelIndex}")

def getChannelByChannelIndex(self, channelIndex):
"""Get channel by channelIndex
channelIndex: number, typically 0-7; based on max number channels
returns: None if there is no channel found
"""
ch = None
if self.channels and 0 <= channelIndex < len(self.channels):
ch = self.channels[channelIndex]
return ch

def deleteChannel(self, channelIndex):
"""Delete the specifed channelIndex and shift other channels up"""
ch = self.channels[channelIndex]
Expand Down
60 changes: 59 additions & 1 deletion meshtastic/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,48 @@ def mock_sendText(text, dest, wantAck, channelIndex):
mo.assert_called()


@pytest.mark.unit
def test_main_sendtext_with_channel(capsys, reset_globals):
"""Test --sendtext"""
sys.argv = ['', '--sendtext', 'hello', '--ch-index', '1']
Globals.getInstance().set_args(sys.argv)

iface = MagicMock(autospec=SerialInterface)
def mock_sendText(text, dest, wantAck, channelIndex):
print('inside mocked sendText')
iface.sendText.side_effect = mock_sendText

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'Sending text message', out, re.MULTILINE)
assert re.search(r'on channelIndex:1', out, re.MULTILINE)
assert re.search(r'inside mocked sendText', out, re.MULTILINE)
assert err == ''
mo.assert_called()


@pytest.mark.unit
def test_main_sendtext_with_invalid_channel(capsys, reset_globals):
"""Test --sendtext"""
sys.argv = ['', '--sendtext', 'hello', '--ch-index', '-1']
Globals.getInstance().set_args(sys.argv)

iface = MagicMock(autospec=SerialInterface)
iface.getChannelByChannelIndex.return_value = None
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
#mo.getChannelByChannelIndex.return_value = None
with pytest.raises(SystemExit) as pytest_wrapped_e:
main()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert re.search(r'is not a valid channel', out, re.MULTILINE)
assert err == ''
mo.assert_called()


@pytest.mark.unit
def test_main_sendtext_with_dest(capsys, reset_globals):
"""Test --sendtext with --dest"""
Expand Down Expand Up @@ -1201,7 +1243,7 @@ def getName(self):

@pytest.mark.unit
def test_main_export_config(reset_globals, capsys):
"""Test export_config"""
"""Test export_config() function directly"""
iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
mo.getLongName.return_value = 'foo'
Expand Down Expand Up @@ -1229,3 +1271,19 @@ def test_main_export_config(reset_globals, capsys):
assert re.search(r"fixed_position: 'true'", out, re.MULTILINE)
assert re.search(r"position_flags: 35", out, re.MULTILINE)
assert err == ''


@pytest.mark.unit
def test_main_export_config_called_from_main(capsys, reset_globals):
"""Test --export-config"""
sys.argv = ['', '--export-config']
Globals.getInstance().set_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'# start of Meshtastic configure yaml', out, re.MULTILINE)
assert err == ''
mo.assert_called()
30 changes: 29 additions & 1 deletion meshtastic/tests/test_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,35 @@ def test_showChannels(capsys):
assert err == ''


@pytest.mark.unit
def test_getChannelByChannelIndex():
"""Test getChannelByChannelIndex()"""
anode = Node('foo', 'bar')

channel1 = Channel(index=1, role=1) # primary channel
channel2 = Channel(index=2, role=2) # secondary channel
channel3 = Channel(index=3, role=0)
channel4 = Channel(index=4, role=0)
channel5 = Channel(index=5, role=0)
channel6 = Channel(index=6, role=0)
channel7 = Channel(index=7, role=0)
channel8 = Channel(index=8, role=0)

channels = [ channel1, channel2, channel3, channel4, channel5, channel6, channel7, channel8 ]

anode.channels = channels

# test primary
assert anode.getChannelByChannelIndex(0) is not None
# test secondary
assert anode.getChannelByChannelIndex(1) is not None
# test disabled
assert anode.getChannelByChannelIndex(2) is not None
# test invalid values
assert anode.getChannelByChannelIndex(-1) is None
assert anode.getChannelByChannelIndex(9) is None


@pytest.mark.unit
def test_deleteChannel_try_to_delete_primary_channel(capsys):
"""Try to delete primary channel."""
Expand Down Expand Up @@ -215,7 +244,6 @@ def test_deleteChannel_try_to_delete_primary_channel(capsys):
assert re.search(r'Warning: Only SECONDARY channels can be deleted', out, re.MULTILINE)
assert err == ''


@pytest.mark.unit
def test_deleteChannel_secondary():
"""Try to delete a secondary channel."""
Expand Down
2 changes: 1 addition & 1 deletion meshtastic/tests/test_stream_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

@pytest.mark.unit
def test_StreamInterface():
"""Test that we can instantiate a StreamInterface"""
"""Test that we cannot instantiate a StreamInterface"""
with pytest.raises(Exception) as pytest_wrapped_e:
StreamInterface(noProto=True)
assert pytest_wrapped_e.type == Exception
17 changes: 15 additions & 2 deletions meshtastic/tests/test_util.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""Meshtastic unit tests for util.py"""

import re
import logging

import pytest

from meshtastic.util import fixme, stripnl, pskToString, our_exit, support_info, genPSK256, fromStr, fromPSK, quoteBooleans
from meshtastic.util import (fixme, stripnl, pskToString, our_exit,
support_info, genPSK256, fromStr, fromPSK,
quoteBooleans, catchAndIgnore)


@pytest.mark.unit
Expand Down Expand Up @@ -120,7 +123,7 @@ def test_our_exit_non_zero_return_value():

@pytest.mark.unit
def test_fixme():
"""Test fixme"""
"""Test fixme()"""
with pytest.raises(Exception) as pytest_wrapped_e:
fixme("some exception")
assert pytest_wrapped_e.type == Exception
Expand All @@ -136,3 +139,13 @@ def test_support_info(capsys):
assert re.search(r'Machine', out, re.MULTILINE)
assert re.search(r'Executable', out, re.MULTILINE)
assert err == ''


@pytest.mark.unit
def test_catchAndIgnore(caplog):
"""Test catchAndIgnore() does not actually throw an exception, but just logs"""
def some_closure():
raise Exception('foo')
with caplog.at_level(logging.DEBUG):
catchAndIgnore("something", some_closure)
assert re.search(r'Exception thrown in something', caplog.text, re.MULTILINE)
2 changes: 1 addition & 1 deletion meshtastic/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def fixme(message):


def catchAndIgnore(reason, closure):
"""Call a closure but if it throws an excpetion print it and continue"""
"""Call a closure but if it throws an exception print it and continue"""
try:
closure()
except BaseException as ex:
Expand Down