From 8fd00efe8b9ee0db08fd973b0e830d04937b1f73 Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Fri, 17 Dec 2021 10:19:48 -0800 Subject: [PATCH 1/3] working initial --- meshtastic/__main__.py | 43 +++++++++++++++++++++++++++++++++++ meshtastic/tests/test_util.py | 12 +++++++++- meshtastic/util.py | 8 +++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 22ec5d0..8a9dce5 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -335,6 +335,44 @@ def onConnected(interface): print("Writing modified preferences to device") getNode().writeConfig() + if args.configure_dump: + # dump out the configuration (the opposite of '--configure') + closeNow = True + owner = interface.getLongName() + channel_url = interface.localNode.getURL() + myinfo = interface.getMyNodeInfo() + pos = myinfo.get('position') + lat = None + lon = None + alt = None + if pos: + lat = pos.get('latitude') + lon = pos.get('longitude') + alt = pos.get('altitude') + + config = "# start of Meshtastic configure yaml\n" + if owner: + config += f"owner: {owner}\n\n" + if channel_url: + config += f"channel_url: {channel_url}\n\n" + if lat or lon or alt: + config += "location:\n" + if lat: + config += f" lat: {lat}\n" + if lon: + config += f" lon: {lon}\n" + if alt: + config += f" alt: {alt}\n" + config += "\n" + preferences = f'{interface.localNode.radioConfig.preferences}' + prefs = preferences.splitlines() + if prefs: + config += "user_prefs:\n" + for pref in prefs: + config += f" {meshtastic.util.quoteBooleans(pref)}\n" + + print(config) + if args.seturl: closeNow = True getNode().setURL(args.seturl) @@ -605,6 +643,11 @@ def initParser(): help="Specify a path to a yaml(.yml) file containing the desired settings for the connected device.", action='append') + parser.add_argument( + "--configure-dump", + help="Dump the configuration in yaml(.yml) format.", + action='store_true') + parser.add_argument( "--port", help="The port the Meshtastic device is connected to, i.e. /dev/ttyUSB0. If unspecified, we'll try to find it.", diff --git a/meshtastic/tests/test_util.py b/meshtastic/tests/test_util.py index b03501d..b3c6d39 100644 --- a/meshtastic/tests/test_util.py +++ b/meshtastic/tests/test_util.py @@ -4,7 +4,7 @@ import re import pytest -from meshtastic.util import fixme, stripnl, pskToString, our_exit, support_info, genPSK256, fromStr, fromPSK +from meshtastic.util import fixme, stripnl, pskToString, our_exit, support_info, genPSK256, fromStr, fromPSK, quoteBooleans @pytest.mark.unit @@ -35,6 +35,16 @@ def test_fromStr(): assert fromStr('abc') == 'abc' +@pytest.mark.unit +def test_quoteBooleans(): + """Test quoteBooleans""" + assert quoteBooleans('') == '' + assert quoteBooleans('foo') == 'foo' + assert quoteBooleans('true') == 'true' + assert quoteBooleans('false') == 'false' + assert quoteBooleans(': true') == ": 'true'" + assert quoteBooleans(': false') == ": 'false'" + @pytest.mark.unit def test_fromPSK(): """Test fromPSK""" diff --git a/meshtastic/util.py b/meshtastic/util.py index f9f95d7..b4376f9 100644 --- a/meshtastic/util.py +++ b/meshtastic/util.py @@ -16,6 +16,14 @@ import pkg_resources blacklistVids = dict.fromkeys([0x1366]) +def quoteBooleans(a_string): + """Quote booleans + given a string that contains ": true", replace with ": 'true'" (or false) + """ + tmp = a_string.replace(": true", ": 'true'") + tmp = tmp.replace(": false", ": 'false'") + return tmp + def genPSK256(): """Generate a random preshared key""" return os.urandom(32) From b47393d62dbbaed5cdc3782d8c051b5f120e2ba4 Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Fri, 17 Dec 2021 10:55:51 -0800 Subject: [PATCH 2/3] refactor and write unit test --- meshtastic/__main__.py | 73 +++++++++++++++++++---------------- meshtastic/tests/test_main.py | 35 ++++++++++++++++- 2 files changed, 73 insertions(+), 35 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 8a9dce5..8ad2503 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -338,40 +338,7 @@ def onConnected(interface): if args.configure_dump: # dump out the configuration (the opposite of '--configure') closeNow = True - owner = interface.getLongName() - channel_url = interface.localNode.getURL() - myinfo = interface.getMyNodeInfo() - pos = myinfo.get('position') - lat = None - lon = None - alt = None - if pos: - lat = pos.get('latitude') - lon = pos.get('longitude') - alt = pos.get('altitude') - - config = "# start of Meshtastic configure yaml\n" - if owner: - config += f"owner: {owner}\n\n" - if channel_url: - config += f"channel_url: {channel_url}\n\n" - if lat or lon or alt: - config += "location:\n" - if lat: - config += f" lat: {lat}\n" - if lon: - config += f" lon: {lon}\n" - if alt: - config += f" alt: {alt}\n" - config += "\n" - preferences = f'{interface.localNode.radioConfig.preferences}' - prefs = preferences.splitlines() - if prefs: - config += "user_prefs:\n" - for pref in prefs: - config += f" {meshtastic.util.quoteBooleans(pref)}\n" - - print(config) + configure_dump(interface) if args.seturl: closeNow = True @@ -553,6 +520,44 @@ def subscribe(): # pub.subscribe(onNode, "meshtastic.node") +def configure_dump(interface): + """Get info used in --configuration-dump""" + owner = interface.getLongName() + channel_url = interface.localNode.getURL() + myinfo = interface.getMyNodeInfo() + pos = myinfo.get('position') + lat = None + lon = None + alt = None + if pos: + lat = pos.get('latitude') + lon = pos.get('longitude') + alt = pos.get('altitude') + + config = "# start of Meshtastic configure yaml\n" + if owner: + config += f"owner: {owner}\n\n" + if channel_url: + config += f"channel_url: {channel_url}\n\n" + if lat or lon or alt: + config += "location:\n" + if lat: + config += f" lat: {lat}\n" + if lon: + config += f" lon: {lon}\n" + if alt: + config += f" alt: {alt}\n" + config += "\n" + preferences = f'{interface.localNode.radioConfig.preferences}' + prefs = preferences.splitlines() + if prefs: + config += "user_prefs:\n" + for pref in prefs: + config += f" {meshtastic.util.quoteBooleans(pref)}\n" + print(config) + return config + + def common(): """Shared code for all of our command line wrappers""" our_globals = Globals.getInstance() diff --git a/meshtastic/tests/test_main.py b/meshtastic/tests/test_main.py index 2559e3b..591f1df 100644 --- a/meshtastic/tests/test_main.py +++ b/meshtastic/tests/test_main.py @@ -1,4 +1,5 @@ """Meshtastic unit tests for __main__.py""" +# pylint: disable=C0302 import sys import os @@ -7,7 +8,7 @@ import re from unittest.mock import patch, MagicMock import pytest -from meshtastic.__main__ import initParser, main, Globals, onReceive, onConnection +from meshtastic.__main__ import initParser, main, Globals, onReceive, onConnection, configure_dump import meshtastic.radioconfig_pb2 from ..serial_interface import SerialInterface from ..tcp_interface import TCPInterface @@ -1196,3 +1197,35 @@ def test_main_onConnection(reset_globals, capsys): out, err = capsys.readouterr() assert re.search(r'Connection changed: foo', out, re.MULTILINE) assert err == '' + + +@pytest.mark.unit +def test_main_configure_dump(reset_globals, capsys): + """Test configure_dump""" + iface = MagicMock(autospec=SerialInterface) + with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: + mo.getLongName.return_value = 'foo' + mo.localNode.getURL.return_value = 'bar' + mo.getMyNodeInfo().get.return_value = { 'latitudeI': 1100000000, 'longitudeI': 1200000000, + 'altitude': 100, 'batteryLevel': 34, 'latitude': 110.0, + 'longitude': 120.0} + mo.localNode.radioConfig.preferences = """phone_timeout_secs: 900 +ls_secs: 300 +position_broadcast_smart: true +fixed_position: true +position_flags: 35""" + configure_dump(mo) + out, err = capsys.readouterr() + assert re.search(r'owner: foo', out, re.MULTILINE) + assert re.search(r'channel_url: bar', out, re.MULTILINE) + assert re.search(r'location:', out, re.MULTILINE) + assert re.search(r'lat: 110.0', out, re.MULTILINE) + assert re.search(r'lon: 120.0', out, re.MULTILINE) + assert re.search(r'alt: 100', out, re.MULTILINE) + assert re.search(r'user_prefs:', out, re.MULTILINE) + assert re.search(r'phone_timeout_secs: 900', out, re.MULTILINE) + assert re.search(r'ls_secs: 300', out, re.MULTILINE) + assert re.search(r"position_broadcast_smart: 'true'", out, re.MULTILINE) + assert re.search(r"fixed_position: 'true'", out, re.MULTILINE) + assert re.search(r"position_flags: 35", out, re.MULTILINE) + assert err == '' From c63dc09e5d936a5f0559436ccabdeb981aca184a Mon Sep 17 00:00:00 2001 From: Mike Kinney Date: Fri, 17 Dec 2021 12:47:27 -0800 Subject: [PATCH 3/3] rename to --export-config --- meshtastic/__main__.py | 14 +++++++------- meshtastic/tests/test_main.py | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/meshtastic/__main__.py b/meshtastic/__main__.py index 8ad2503..432e75c 100644 --- a/meshtastic/__main__.py +++ b/meshtastic/__main__.py @@ -335,10 +335,10 @@ def onConnected(interface): print("Writing modified preferences to device") getNode().writeConfig() - if args.configure_dump: - # dump out the configuration (the opposite of '--configure') + if args.export_config: + # export the configuration (the opposite of '--configure') closeNow = True - configure_dump(interface) + export_config(interface) if args.seturl: closeNow = True @@ -520,8 +520,8 @@ def subscribe(): # pub.subscribe(onNode, "meshtastic.node") -def configure_dump(interface): - """Get info used in --configuration-dump""" +def export_config(interface): + """used in--export-config""" owner = interface.getLongName() channel_url = interface.localNode.getURL() myinfo = interface.getMyNodeInfo() @@ -649,8 +649,8 @@ def initParser(): action='append') parser.add_argument( - "--configure-dump", - help="Dump the configuration in yaml(.yml) format.", + "--export-config", + help="Export the configuration in yaml(.yml) format.", action='store_true') parser.add_argument( diff --git a/meshtastic/tests/test_main.py b/meshtastic/tests/test_main.py index 591f1df..edb5848 100644 --- a/meshtastic/tests/test_main.py +++ b/meshtastic/tests/test_main.py @@ -8,7 +8,7 @@ import re from unittest.mock import patch, MagicMock import pytest -from meshtastic.__main__ import initParser, main, Globals, onReceive, onConnection, configure_dump +from meshtastic.__main__ import initParser, main, Globals, onReceive, onConnection, export_config import meshtastic.radioconfig_pb2 from ..serial_interface import SerialInterface from ..tcp_interface import TCPInterface @@ -1200,8 +1200,8 @@ def test_main_onConnection(reset_globals, capsys): @pytest.mark.unit -def test_main_configure_dump(reset_globals, capsys): - """Test configure_dump""" +def test_main_export_config(reset_globals, capsys): + """Test export_config""" iface = MagicMock(autospec=SerialInterface) with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: mo.getLongName.return_value = 'foo' @@ -1214,7 +1214,7 @@ ls_secs: 300 position_broadcast_smart: true fixed_position: true position_flags: 35""" - configure_dump(mo) + export_config(mo) out, err = capsys.readouterr() assert re.search(r'owner: foo', out, re.MULTILINE) assert re.search(r'channel_url: bar', out, re.MULTILINE)