mirror of
https://github.com/meshtastic/python.git
synced 2025-12-26 01:17:51 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
968027a439 | ||
|
|
cc24b6ebc5 | ||
|
|
db09b4718d | ||
|
|
e0edbc6288 | ||
|
|
ae9ae91af5 | ||
|
|
7921db007b | ||
|
|
e85af2f9e9 | ||
|
|
8ccc64f92e | ||
|
|
afb21c6dc3 | ||
|
|
3291bc7097 | ||
|
|
a7d56504be | ||
|
|
90e5b473d9 |
16
exampleConfig.yaml
Normal file
16
exampleConfig.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
# example config using camelCase keys
|
||||
owner: Bob TBeam
|
||||
|
||||
channelUrl: https://www.meshtastic.org/d/#CgUYAyIBAQ
|
||||
|
||||
location:
|
||||
lat: 35.88888
|
||||
lon: -93.88888
|
||||
alt: 304
|
||||
|
||||
userPrefs:
|
||||
region: 1
|
||||
isAlwaysPowered: 'true'
|
||||
sendOwnerInterval: 2
|
||||
screenOnSecs: 31536000
|
||||
waitBluetoothSecs: 31536000
|
||||
@@ -1,3 +1,4 @@
|
||||
# example configuration file with snake_case keys
|
||||
owner: Bob TBeam
|
||||
|
||||
channel_url: https://www.meshtastic.org/d/#CgUYAyIBAQ
|
||||
|
||||
@@ -20,7 +20,6 @@ from meshtastic import portnums_pb2, channel_pb2, radioconfig_pb2
|
||||
from meshtastic.globals import Globals
|
||||
from meshtastic.__init__ import BROADCAST_ADDR
|
||||
|
||||
|
||||
def onReceive(packet, interface):
|
||||
"""Callback invoked when a packet arrives"""
|
||||
our_globals = Globals.getInstance()
|
||||
@@ -56,43 +55,63 @@ def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=W0613
|
||||
def getPref(attributes, name):
|
||||
"""Get a channel or preferences value"""
|
||||
|
||||
camel_name = meshtastic.util.snake_to_camel(name)
|
||||
# Note: protobufs has the keys in snake_case, so snake internally
|
||||
snake_name = meshtastic.util.camel_to_snake(name)
|
||||
logging.debug(f'snake_name:{snake_name} camel_name:{camel_name}')
|
||||
logging.debug(f'use camel:{Globals.getInstance().get_camel_case()}')
|
||||
|
||||
objDesc = attributes.DESCRIPTOR
|
||||
field = objDesc.fields_by_name.get(name)
|
||||
field = objDesc.fields_by_name.get(snake_name)
|
||||
if not field:
|
||||
print(f"{attributes.__class__.__name__} does not have an attribute called {name}, so you can not get it.")
|
||||
if Globals.getInstance().get_camel_case():
|
||||
print(f"{attributes.__class__.__name__} does not have an attribute called {camel_name}, so you can not get it.")
|
||||
else:
|
||||
print(f"{attributes.__class__.__name__} does not have an attribute called {snake_name}, so you can not get it.")
|
||||
print(f"Choices in sorted order are:")
|
||||
names = []
|
||||
for f in objDesc.fields:
|
||||
names.append(f'{f.name}')
|
||||
tmp_name = f'{f.name}'
|
||||
if Globals.getInstance().get_camel_case():
|
||||
tmp_name = meshtastic.util.snake_to_camel(tmp_name)
|
||||
names.append(tmp_name)
|
||||
for temp_name in sorted(names):
|
||||
print(f" {temp_name}")
|
||||
return
|
||||
|
||||
# okay - try to read the value
|
||||
try:
|
||||
try:
|
||||
val = getattr(attributes, name)
|
||||
except TypeError:
|
||||
# The getter didn't like our arg type guess try again as a string
|
||||
val = getattr(attributes, name)
|
||||
# read the value
|
||||
val = getattr(attributes, snake_name)
|
||||
|
||||
# succeeded!
|
||||
print(f"{name}: {str(val)}")
|
||||
except Exception as ex:
|
||||
print(f"Can't get {name} due to {ex}")
|
||||
if Globals.getInstance().get_camel_case():
|
||||
print(f"{camel_name}: {str(val)}")
|
||||
logging.debug(f"{camel_name}: {str(val)}")
|
||||
else:
|
||||
print(f"{snake_name}: {str(val)}")
|
||||
logging.debug(f"{snake_name}: {str(val)}")
|
||||
|
||||
|
||||
def setPref(attributes, name, valStr):
|
||||
"""Set a channel or preferences value"""
|
||||
|
||||
snake_name = meshtastic.util.camel_to_snake(name)
|
||||
camel_name = meshtastic.util.snake_to_camel(name)
|
||||
logging.debug(f'snake_name:{snake_name}')
|
||||
logging.debug(f'camel_name:{camel_name}')
|
||||
|
||||
objDesc = attributes.DESCRIPTOR
|
||||
field = objDesc.fields_by_name.get(name)
|
||||
field = objDesc.fields_by_name.get(snake_name)
|
||||
if not field:
|
||||
print(f"{attributes.__class__.__name__} does not have an attribute called {name}, so you can not set it.")
|
||||
if Globals.getInstance().get_camel_case():
|
||||
print(f"{attributes.__class__.__name__} does not have an attribute called {camel_name}, so you can not set it.")
|
||||
else:
|
||||
print(f"{attributes.__class__.__name__} does not have an attribute called {snake_name}, so you can not set it.")
|
||||
print(f"Choices in sorted order are:")
|
||||
names = []
|
||||
for f in objDesc.fields:
|
||||
names.append(f'{f.name}')
|
||||
tmp_name = f'{f.name}'
|
||||
if Globals.getInstance().get_camel_case():
|
||||
tmp_name = meshtastic.util.snake_to_camel(tmp_name)
|
||||
names.append(tmp_name)
|
||||
for temp_name in sorted(names):
|
||||
print(f" {temp_name}")
|
||||
return
|
||||
@@ -107,27 +126,27 @@ def setPref(attributes, name, valStr):
|
||||
if e:
|
||||
val = e.number
|
||||
else:
|
||||
print(f"{name} does not have an enum called {val}, so you can not set it.")
|
||||
if Globals.getInstance().get_camel_case():
|
||||
print(f"{camel_name} does not have an enum called {val}, so you can not set it.")
|
||||
else:
|
||||
print(f"{snake_name} does not have an enum called {val}, so you can not set it.")
|
||||
print(f"Choices in sorted order are:")
|
||||
names = []
|
||||
for f in enumType.values:
|
||||
names.append(f'{f.name}')
|
||||
tmp_name = f'{f.name}'
|
||||
if Globals.getInstance().get_camel_case():
|
||||
tmp_name = meshtastic.util.snake_to_camel(tmp_name)
|
||||
names.append(name)
|
||||
for temp_name in sorted(names):
|
||||
print(f" {temp_name}")
|
||||
return
|
||||
|
||||
# okay - try to read the value
|
||||
try:
|
||||
try:
|
||||
setattr(attributes, name, val)
|
||||
except TypeError:
|
||||
# The setter didn't like our arg type guess try again as a string
|
||||
setattr(attributes, name, valStr)
|
||||
setattr(attributes, snake_name, val)
|
||||
|
||||
# succeeded!
|
||||
print(f"Set {name} to {valStr}")
|
||||
except Exception as ex:
|
||||
print(f"Can't set {name} due to {ex}")
|
||||
if Globals.getInstance().get_camel_case():
|
||||
print(f"Set {camel_name} to {valStr}")
|
||||
else:
|
||||
print(f"Set {snake_name} to {valStr}")
|
||||
|
||||
|
||||
def onConnected(interface):
|
||||
@@ -311,6 +330,10 @@ def onConnected(interface):
|
||||
print("Setting channel url to", configuration['channel_url'])
|
||||
interface.getNode(args.dest).setURL(configuration['channel_url'])
|
||||
|
||||
if 'channelUrl' in configuration:
|
||||
print("Setting channel url to", configuration['channelUrl'])
|
||||
interface.getNode(args.dest).setURL(configuration['channelUrl'])
|
||||
|
||||
if 'location' in configuration:
|
||||
alt = 0
|
||||
lat = 0.0
|
||||
@@ -340,6 +363,13 @@ def onConnected(interface):
|
||||
print("Writing modified preferences to device")
|
||||
interface.getNode(args.dest).writeConfig()
|
||||
|
||||
if 'userPrefs' in configuration:
|
||||
prefs = interface.getNode(args.dest).radioConfig.preferences
|
||||
for pref in configuration['userPrefs']:
|
||||
setPref(prefs, pref, str(configuration['userPrefs'][pref]))
|
||||
print("Writing modified preferences to device")
|
||||
interface.getNode(args.dest).writeConfig()
|
||||
|
||||
if args.export_config:
|
||||
# export the configuration (the opposite of '--configure')
|
||||
closeNow = True
|
||||
@@ -548,7 +578,10 @@ def export_config(interface):
|
||||
if owner:
|
||||
config += f"owner: {owner}\n\n"
|
||||
if channel_url:
|
||||
config += f"channel_url: {channel_url}\n\n"
|
||||
if Globals.getInstance().get_camel_case():
|
||||
config += f"channelUrl: {channel_url}\n\n"
|
||||
else:
|
||||
config += f"channel_url: {channel_url}\n\n"
|
||||
if lat or lon or alt:
|
||||
config += "location:\n"
|
||||
if lat:
|
||||
@@ -561,9 +594,16 @@ def export_config(interface):
|
||||
preferences = f'{interface.localNode.radioConfig.preferences}'
|
||||
prefs = preferences.splitlines()
|
||||
if prefs:
|
||||
config += "user_prefs:\n"
|
||||
if Globals.getInstance().get_camel_case():
|
||||
config += "userPrefs:\n"
|
||||
else:
|
||||
config += "user_prefs:\n"
|
||||
for pref in prefs:
|
||||
config += f" {meshtastic.util.quoteBooleans(pref)}\n"
|
||||
if Globals.getInstance().get_camel_case():
|
||||
# Note: This may not work if the value has '_'
|
||||
config += f" {meshtastic.util.snake_to_camel(meshtastic.util.quoteBooleans(pref))}\n"
|
||||
else:
|
||||
config += f" {meshtastic.util.quoteBooleans(pref)}\n"
|
||||
print(config)
|
||||
return config
|
||||
|
||||
@@ -692,10 +732,12 @@ def initParser():
|
||||
action="store_true")
|
||||
|
||||
parser.add_argument(
|
||||
"--get", help="Get a preferences field. Use an invalid field such as '0' to get a list of all fields.", nargs=1, action='append')
|
||||
"--get", help=("Get a preferences field. Use an invalid field such as '0' to get a list of all fields."
|
||||
" Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')"),
|
||||
nargs=1, action='append')
|
||||
|
||||
parser.add_argument(
|
||||
"--set", help="Set a preferences field", nargs=2, action='append')
|
||||
"--set", help="Set a preferences field. Can use either snake_case or camelCase format. (ex: 'ls_secs' or 'lsSecs')", nargs=2, action='append')
|
||||
|
||||
parser.add_argument(
|
||||
"--seturl", help="Set a channel URL", action="store")
|
||||
|
||||
@@ -30,6 +30,8 @@ class Globals:
|
||||
self.channel_index = None
|
||||
self.logfile = None
|
||||
self.tunnelInstance = None
|
||||
# TODO: to migrate to camel_case for v1.3 change this value to True
|
||||
self.camel_case = False
|
||||
|
||||
def reset(self):
|
||||
"""Reset all of our globals. If you add a member, add it to this method, too."""
|
||||
@@ -38,6 +40,8 @@ class Globals:
|
||||
self.channel_index = None
|
||||
self.logfile = None
|
||||
self.tunnelInstance = None
|
||||
# TODO: to migrate to camel_case for v1.3 change this value to True
|
||||
self.camel_case = False
|
||||
|
||||
# setters
|
||||
def set_args(self, args):
|
||||
@@ -60,6 +64,10 @@ class Globals:
|
||||
"""Set the tunnelInstance"""
|
||||
self.tunnelInstance = tunnelInstance
|
||||
|
||||
def set_camel_case(self):
|
||||
"""Force using camelCase for things like prefs/set/set"""
|
||||
self.camel_case = True
|
||||
|
||||
# getters
|
||||
def get_args(self):
|
||||
"""Get args"""
|
||||
@@ -80,3 +88,7 @@ class Globals:
|
||||
def get_tunnelInstance(self):
|
||||
"""Get tunnelInstance"""
|
||||
return self.tunnelInstance
|
||||
|
||||
def get_camel_case(self):
|
||||
"""Get whether or not to use camelCase"""
|
||||
return self.camel_case
|
||||
|
||||
@@ -8,8 +8,6 @@ from meshtastic import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
|
||||
from meshtastic.util import pskToString, stripnl, Timeout, our_exit, fromPSK
|
||||
|
||||
|
||||
|
||||
|
||||
class Node:
|
||||
"""A model of a (local or remote) node in the mesh
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ from ..tcp_interface import TCPInterface
|
||||
from ..node import Node
|
||||
from ..channel_pb2 import Channel
|
||||
from ..remote_hardware import onGPIOreceive
|
||||
from ..radioconfig_pb2 import RadioConfig
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@@ -366,6 +367,27 @@ def test_main_qr(capsys):
|
||||
mo.assert_called()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_onConnected_exception(capsys):
|
||||
"""Test the exception in onConnected"""
|
||||
sys.argv = ['', '--qr']
|
||||
Globals.getInstance().set_args(sys.argv)
|
||||
|
||||
def throw_an_exception(junk):
|
||||
raise Exception("Fake exception.")
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface):
|
||||
with patch('pyqrcode.create', side_effect=throw_an_exception):
|
||||
with pytest.raises(Exception) as pytest_wrapped_e:
|
||||
main()
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search('Aborting due to: Fake exception', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
assert pytest_wrapped_e.type == Exception
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_nodes(capsys):
|
||||
@@ -781,6 +803,28 @@ def test_main_set_valid(capsys):
|
||||
mo.assert_called()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_set_valid_camel_case(capsys):
|
||||
"""Test --set with valid field"""
|
||||
sys.argv = ['', '--set', 'wifi_ssid', 'foo']
|
||||
Globals.getInstance().set_args(sys.argv)
|
||||
Globals.getInstance().set_camel_case()
|
||||
|
||||
mocked_node = MagicMock(autospec=Node)
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
iface.getNode.return_value = mocked_node
|
||||
|
||||
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'Set wifiSsid to foo', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
mo.assert_called()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_set_with_invalid(capsys):
|
||||
@@ -809,7 +853,7 @@ def test_main_set_with_invalid(capsys):
|
||||
# TODO: write some negative --configure tests
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_configure(capsys):
|
||||
def test_main_configure_with_snake_case(capsys):
|
||||
"""Test --configure with valid file"""
|
||||
sys.argv = ['', '--configure', 'example_config.yaml']
|
||||
Globals.getInstance().set_args(sys.argv)
|
||||
@@ -833,6 +877,31 @@ def test_main_configure(capsys):
|
||||
mo.assert_called()
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_configure_with_camel_case_keys(capsys):
|
||||
"""Test --configure with valid file"""
|
||||
sys.argv = ['', '--configure', 'exampleConfig.yaml']
|
||||
Globals.getInstance().set_args(sys.argv)
|
||||
|
||||
mocked_node = MagicMock(autospec=Node)
|
||||
|
||||
iface = MagicMock(autospec=SerialInterface)
|
||||
iface.getNode.return_value = mocked_node
|
||||
|
||||
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 device owner', out, re.MULTILINE)
|
||||
assert re.search(r'Setting channel url', out, re.MULTILINE)
|
||||
assert re.search(r'Fixing altitude', out, re.MULTILINE)
|
||||
assert re.search(r'Fixing latitude', out, re.MULTILINE)
|
||||
assert re.search(r'Fixing longitude', out, re.MULTILINE)
|
||||
assert re.search(r'Writing modified preferences', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
mo.assert_called()
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_ch_add_valid(capsys):
|
||||
@@ -1289,6 +1358,33 @@ def test_main_get_with_valid_values(capsys):
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_get_with_valid_values_camel(capsys, caplog):
|
||||
"""Test --get with valid values (with string, number, boolean)"""
|
||||
sys.argv = ['', '--get', 'lsSecs', '--get', 'wifiSsid', '--get', 'fixedPosition']
|
||||
Globals.getInstance().set_args(sys.argv)
|
||||
Globals.getInstance().set_camel_case()
|
||||
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
with patch('meshtastic.serial_interface.SerialInterface') as mo:
|
||||
|
||||
mo().getNode().radioConfig.preferences.wifi_ssid = 'foo'
|
||||
mo().getNode().radioConfig.preferences.ls_secs = 300
|
||||
mo().getNode().radioConfig.preferences.fixed_position = False
|
||||
|
||||
main()
|
||||
|
||||
mo.assert_called()
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r'Connected to radio', out, re.MULTILINE)
|
||||
assert re.search(r'lsSecs: 300', out, re.MULTILINE)
|
||||
assert re.search(r'wifiSsid: foo', out, re.MULTILINE)
|
||||
assert re.search(r'fixedPosition: False', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_get_with_invalid(capsys):
|
||||
@@ -1492,6 +1588,45 @@ position_flags: 35"""
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_export_config_use_camel(capsys):
|
||||
"""Test export_config() function directly"""
|
||||
Globals.getInstance().set_camel_case()
|
||||
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"""
|
||||
export_config(mo)
|
||||
out, err = capsys.readouterr()
|
||||
|
||||
# ensure we do not output this line
|
||||
assert not re.search(r'Connected to radio', out, re.MULTILINE)
|
||||
|
||||
assert re.search(r'owner: foo', out, re.MULTILINE)
|
||||
assert re.search(r'channelUrl: 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'userPrefs:', out, re.MULTILINE)
|
||||
assert re.search(r'phoneTimeoutSecs: 900', out, re.MULTILINE)
|
||||
assert re.search(r'lsSecs: 300', out, re.MULTILINE)
|
||||
# TODO: should True be capitalized here?
|
||||
assert re.search(r"positionBroadcastSmart: 'True'", out, re.MULTILINE)
|
||||
assert re.search(r"fixedPosition: 'True'", out, re.MULTILINE)
|
||||
assert re.search(r"positionFlags: 35", out, re.MULTILINE)
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_export_config_called_from_main(capsys):
|
||||
@@ -1625,6 +1760,22 @@ def test_main_getPref_valid_field(capsys):
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_getPref_valid_field_camel(capsys):
|
||||
"""Test getPref() with a valid field"""
|
||||
Globals.getInstance().set_camel_case()
|
||||
prefs = MagicMock()
|
||||
prefs.DESCRIPTOR.fields_by_name.get.return_value = 'ls_secs'
|
||||
prefs.wifi_ssid = 'foo'
|
||||
prefs.ls_secs = 300
|
||||
prefs.fixed_position = False
|
||||
|
||||
getPref(prefs, 'ls_secs')
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r'lsSecs: 300', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_getPref_valid_field_string(capsys):
|
||||
@@ -1641,6 +1792,23 @@ def test_main_getPref_valid_field_string(capsys):
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_getPref_valid_field_string_camel(capsys):
|
||||
"""Test getPref() with a valid field and value as a string"""
|
||||
Globals.getInstance().set_camel_case()
|
||||
prefs = MagicMock()
|
||||
prefs.DESCRIPTOR.fields_by_name.get.return_value = 'wifi_ssid'
|
||||
prefs.wifi_ssid = 'foo'
|
||||
prefs.ls_secs = 300
|
||||
prefs.fixed_position = False
|
||||
|
||||
getPref(prefs, 'wifi_ssid')
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r'wifiSsid: foo', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_getPref_valid_field_bool(capsys):
|
||||
@@ -1657,6 +1825,23 @@ def test_main_getPref_valid_field_bool(capsys):
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_getPref_valid_field_bool_camel(capsys):
|
||||
"""Test getPref() with a valid field and value as a bool"""
|
||||
Globals.getInstance().set_camel_case()
|
||||
prefs = MagicMock()
|
||||
prefs.DESCRIPTOR.fields_by_name.get.return_value = 'fixed_position'
|
||||
prefs.wifi_ssid = 'foo'
|
||||
prefs.ls_secs = 300
|
||||
prefs.fixed_position = False
|
||||
|
||||
getPref(prefs, 'fixed_position')
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r'fixedPosition: False', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_getPref_invalid_field(capsys):
|
||||
@@ -1691,7 +1876,40 @@ def test_main_getPref_invalid_field(capsys):
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_setPref_valid_field_int(capsys):
|
||||
def test_main_getPref_invalid_field_camel(capsys):
|
||||
"""Test getPref() with an invalid field"""
|
||||
Globals.getInstance().set_camel_case()
|
||||
|
||||
class Field:
|
||||
"""Simple class for testing."""
|
||||
|
||||
def __init__(self, name):
|
||||
"""constructor"""
|
||||
self.name = name
|
||||
|
||||
prefs = MagicMock()
|
||||
prefs.DESCRIPTOR.fields_by_name.get.return_value = None
|
||||
|
||||
# Note: This is a subset of the real fields
|
||||
ls_secs_field = Field('ls_secs')
|
||||
is_router = Field('is_router')
|
||||
fixed_position = Field('fixed_position')
|
||||
|
||||
fields = [ ls_secs_field, is_router, fixed_position ]
|
||||
prefs.DESCRIPTOR.fields = fields
|
||||
|
||||
getPref(prefs, 'foo')
|
||||
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
|
||||
# ensure they are sorted
|
||||
assert re.search(r'fixedPosition\s+isRouter\s+lsSecs', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_setPref_valid_field_int_as_string(capsys):
|
||||
"""Test setPref() with a valid field"""
|
||||
|
||||
class Field:
|
||||
@@ -1712,6 +1930,74 @@ def test_main_setPref_valid_field_int(capsys):
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_setPref_valid_field_invalid_enum(capsys, caplog):
|
||||
"""Test setPref() with a valid field but invalid enum value"""
|
||||
|
||||
radioConfig = RadioConfig()
|
||||
prefs = radioConfig.preferences
|
||||
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
setPref(prefs, 'charge_current', 'foo')
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r'charge_current does not have an enum called foo', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_setPref_valid_field_invalid_enum_camel(capsys, caplog):
|
||||
"""Test setPref() with a valid field but invalid enum value"""
|
||||
Globals.getInstance().set_camel_case()
|
||||
|
||||
radioConfig = RadioConfig()
|
||||
prefs = radioConfig.preferences
|
||||
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
setPref(prefs, 'charge_current', 'foo')
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r'chargeCurrent does not have an enum called foo', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_setPref_valid_field_valid_enum(capsys, caplog):
|
||||
"""Test setPref() with a valid field and valid enum value"""
|
||||
|
||||
# charge_current
|
||||
# some valid values: MA100 MA1000 MA1080
|
||||
|
||||
radioConfig = RadioConfig()
|
||||
prefs = radioConfig.preferences
|
||||
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
setPref(prefs, 'charge_current', 'MA100')
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r'Set charge_current to MA100', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_setPref_valid_field_valid_enum_camel(capsys, caplog):
|
||||
"""Test setPref() with a valid field and valid enum value"""
|
||||
Globals.getInstance().set_camel_case()
|
||||
|
||||
# charge_current
|
||||
# some valid values: MA100 MA1000 MA1080
|
||||
|
||||
radioConfig = RadioConfig()
|
||||
prefs = radioConfig.preferences
|
||||
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
setPref(prefs, 'charge_current', 'MA100')
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r'Set chargeCurrent to MA100', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_setPref_invalid_field(capsys):
|
||||
@@ -1744,6 +2030,38 @@ def test_main_setPref_invalid_field(capsys):
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_setPref_invalid_field_camel(capsys):
|
||||
"""Test setPref() with a invalid field"""
|
||||
Globals.getInstance().set_camel_case()
|
||||
|
||||
class Field:
|
||||
"""Simple class for testing."""
|
||||
|
||||
def __init__(self, name):
|
||||
"""constructor"""
|
||||
self.name = name
|
||||
|
||||
prefs = MagicMock()
|
||||
prefs.DESCRIPTOR.fields_by_name.get.return_value = None
|
||||
|
||||
# Note: This is a subset of the real fields
|
||||
ls_secs_field = Field('ls_secs')
|
||||
is_router = Field('is_router')
|
||||
fixed_position = Field('fixed_position')
|
||||
|
||||
fields = [ ls_secs_field, is_router, fixed_position ]
|
||||
prefs.DESCRIPTOR.fields = fields
|
||||
|
||||
setPref(prefs, 'foo', '300')
|
||||
out, err = capsys.readouterr()
|
||||
assert re.search(r'does not have an attribute called foo', out, re.MULTILINE)
|
||||
# ensure they are sorted
|
||||
assert re.search(r'fixedPosition\s+isRouter\s+lsSecs', out, re.MULTILINE)
|
||||
assert err == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.usefixtures("reset_globals")
|
||||
def test_main_ch_set_psk_no_ch_index(capsys):
|
||||
|
||||
@@ -10,7 +10,8 @@ from meshtastic.util import (fixme, stripnl, pskToString, our_exit,
|
||||
support_info, genPSK256, fromStr, fromPSK,
|
||||
quoteBooleans, catchAndIgnore,
|
||||
remove_keys_from_dict, Timeout, hexstr,
|
||||
ipstr, readnet_u16, findPorts, convert_mac_addr)
|
||||
ipstr, readnet_u16, findPorts, convert_mac_addr,
|
||||
snake_to_camel, camel_to_snake)
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@@ -251,3 +252,22 @@ def test_convert_mac_addr():
|
||||
assert convert_mac_addr('/c0gFyhb') == 'fd:cd:20:17:28:5b'
|
||||
assert convert_mac_addr('fd:cd:20:17:28:5b') == 'fd:cd:20:17:28:5b'
|
||||
assert convert_mac_addr('') == ''
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_snake_to_camel():
|
||||
"""Test snake_to_camel"""
|
||||
assert snake_to_camel('') == ''
|
||||
assert snake_to_camel('foo') == 'foo'
|
||||
assert snake_to_camel('foo_bar') == 'fooBar'
|
||||
assert snake_to_camel('fooBar') == 'fooBar'
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
def test_camel_to_snake():
|
||||
"""Test camel_to_snake"""
|
||||
assert camel_to_snake('') == ''
|
||||
assert camel_to_snake('foo') == 'foo'
|
||||
assert camel_to_snake('Foo') == 'foo'
|
||||
assert camel_to_snake('fooBar') == 'foo_bar'
|
||||
assert camel_to_snake('fooBarBaz') == 'foo_bar_baz'
|
||||
|
||||
@@ -243,3 +243,17 @@ def convert_mac_addr(val):
|
||||
val_as_bytes = base64.b64decode(val)
|
||||
return hexstr(val_as_bytes)
|
||||
return val
|
||||
|
||||
|
||||
def snake_to_camel(a_string):
|
||||
"""convert snake_case to camelCase"""
|
||||
# split underscore using split
|
||||
temp = a_string.split('_')
|
||||
# joining result
|
||||
result = temp[0] + ''.join(ele.title() for ele in temp[1:])
|
||||
return result
|
||||
|
||||
|
||||
def camel_to_snake(a_string):
|
||||
"""convert camelCase to snake_case"""
|
||||
return ''.join(['_'+i.lower() if i.isupper() else i for i in a_string]).lstrip('_')
|
||||
|
||||
2
proto
2
proto
Submodule proto updated: 18fc4cdb52...d7b2791b7c
2
setup.py
2
setup.py
@@ -12,7 +12,7 @@ with open("README.md", "r") as fh:
|
||||
# This call to setup() does all the work
|
||||
setup(
|
||||
name="meshtastic",
|
||||
version="1.2.54",
|
||||
version="1.2.55",
|
||||
description="Python API & client shell for talking to Meshtastic devices",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
|
||||
Reference in New Issue
Block a user