mirror of
https://github.com/meshtastic/python.git
synced 2026-01-04 05:47:57 -05:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4955ab8df2 | ||
|
|
de39c98e50 | ||
|
|
51378bb0eb | ||
|
|
aff3bdd78e | ||
|
|
e9a8e26e76 | ||
|
|
83439679c1 | ||
|
|
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
|
owner: Bob TBeam
|
||||||
|
|
||||||
channel_url: https://www.meshtastic.org/d/#CgUYAyIBAQ
|
channel_url: https://www.meshtastic.org/d/#CgUYAyIBAQ
|
||||||
@@ -13,3 +14,4 @@ user_prefs:
|
|||||||
send_owner_interval: 2
|
send_owner_interval: 2
|
||||||
screen_on_secs: 31536000
|
screen_on_secs: 31536000
|
||||||
wait_bluetooth_secs: 31536000
|
wait_bluetooth_secs: 31536000
|
||||||
|
location_share: 'LocEnabled'
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from meshtastic import portnums_pb2, channel_pb2, radioconfig_pb2
|
|||||||
from meshtastic.globals import Globals
|
from meshtastic.globals import Globals
|
||||||
from meshtastic.__init__ import BROADCAST_ADDR
|
from meshtastic.__init__ import BROADCAST_ADDR
|
||||||
|
|
||||||
|
|
||||||
def onReceive(packet, interface):
|
def onReceive(packet, interface):
|
||||||
"""Callback invoked when a packet arrives"""
|
"""Callback invoked when a packet arrives"""
|
||||||
our_globals = Globals.getInstance()
|
our_globals = Globals.getInstance()
|
||||||
@@ -56,48 +55,69 @@ def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=W0613
|
|||||||
def getPref(attributes, name):
|
def getPref(attributes, name):
|
||||||
"""Get a channel or preferences value"""
|
"""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
|
objDesc = attributes.DESCRIPTOR
|
||||||
field = objDesc.fields_by_name.get(name)
|
field = objDesc.fields_by_name.get(snake_name)
|
||||||
if not field:
|
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:")
|
print(f"Choices in sorted order are:")
|
||||||
names = []
|
names = []
|
||||||
for f in objDesc.fields:
|
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):
|
for temp_name in sorted(names):
|
||||||
print(f" {temp_name}")
|
print(f" {temp_name}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# okay - try to read the value
|
# read the value
|
||||||
try:
|
val = getattr(attributes, snake_name)
|
||||||
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)
|
|
||||||
|
|
||||||
# succeeded!
|
if Globals.getInstance().get_camel_case():
|
||||||
print(f"{name}: {str(val)}")
|
print(f"{camel_name}: {str(val)}")
|
||||||
except Exception as ex:
|
logging.debug(f"{camel_name}: {str(val)}")
|
||||||
print(f"Can't get {name} due to {ex}")
|
else:
|
||||||
|
print(f"{snake_name}: {str(val)}")
|
||||||
|
logging.debug(f"{snake_name}: {str(val)}")
|
||||||
|
|
||||||
|
|
||||||
def setPref(attributes, name, valStr):
|
def setPref(attributes, name, valStr):
|
||||||
"""Set a channel or preferences value"""
|
"""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
|
objDesc = attributes.DESCRIPTOR
|
||||||
field = objDesc.fields_by_name.get(name)
|
field = objDesc.fields_by_name.get(snake_name)
|
||||||
if not field:
|
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:")
|
print(f"Choices in sorted order are:")
|
||||||
names = []
|
names = []
|
||||||
for f in objDesc.fields:
|
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):
|
for temp_name in sorted(names):
|
||||||
print(f" {temp_name}")
|
print(f" {temp_name}")
|
||||||
return
|
return
|
||||||
|
|
||||||
val = meshtastic.util.fromStr(valStr)
|
val = meshtastic.util.fromStr(valStr)
|
||||||
|
logging.debug(f'valStr:{valStr} val:{val}')
|
||||||
|
|
||||||
enumType = field.enum_type
|
enumType = field.enum_type
|
||||||
# pylint: disable=C0123
|
# pylint: disable=C0123
|
||||||
@@ -107,27 +127,28 @@ def setPref(attributes, name, valStr):
|
|||||||
if e:
|
if e:
|
||||||
val = e.number
|
val = e.number
|
||||||
else:
|
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:")
|
print(f"Choices in sorted order are:")
|
||||||
names = []
|
names = []
|
||||||
for f in enumType.values:
|
for f in enumType.values:
|
||||||
|
# Note: We must use the value of the enum (regardless if camel or snake case)
|
||||||
names.append(f'{f.name}')
|
names.append(f'{f.name}')
|
||||||
for temp_name in sorted(names):
|
for temp_name in sorted(names):
|
||||||
print(f" {temp_name}")
|
print(f" {temp_name}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# okay - try to read the value
|
|
||||||
try:
|
try:
|
||||||
try:
|
setattr(attributes, snake_name, val)
|
||||||
setattr(attributes, name, val)
|
except TypeError:
|
||||||
except TypeError:
|
# The setter didn't like our arg type guess try again as a string
|
||||||
# The setter didn't like our arg type guess try again as a string
|
setattr(attributes, snake_name, valStr)
|
||||||
setattr(attributes, name, valStr)
|
|
||||||
|
|
||||||
# succeeded!
|
if Globals.getInstance().get_camel_case():
|
||||||
print(f"Set {name} to {valStr}")
|
print(f"Set {camel_name} to {valStr}")
|
||||||
except Exception as ex:
|
else:
|
||||||
print(f"Can't set {name} due to {ex}")
|
print(f"Set {snake_name} to {valStr}")
|
||||||
|
|
||||||
|
|
||||||
def onConnected(interface):
|
def onConnected(interface):
|
||||||
@@ -311,6 +332,10 @@ def onConnected(interface):
|
|||||||
print("Setting channel url to", configuration['channel_url'])
|
print("Setting channel url to", configuration['channel_url'])
|
||||||
interface.getNode(args.dest).setURL(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:
|
if 'location' in configuration:
|
||||||
alt = 0
|
alt = 0
|
||||||
lat = 0.0
|
lat = 0.0
|
||||||
@@ -340,6 +365,13 @@ def onConnected(interface):
|
|||||||
print("Writing modified preferences to device")
|
print("Writing modified preferences to device")
|
||||||
interface.getNode(args.dest).writeConfig()
|
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:
|
if args.export_config:
|
||||||
# export the configuration (the opposite of '--configure')
|
# export the configuration (the opposite of '--configure')
|
||||||
closeNow = True
|
closeNow = True
|
||||||
@@ -548,7 +580,10 @@ def export_config(interface):
|
|||||||
if owner:
|
if owner:
|
||||||
config += f"owner: {owner}\n\n"
|
config += f"owner: {owner}\n\n"
|
||||||
if channel_url:
|
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:
|
if lat or lon or alt:
|
||||||
config += "location:\n"
|
config += "location:\n"
|
||||||
if lat:
|
if lat:
|
||||||
@@ -561,9 +596,16 @@ def export_config(interface):
|
|||||||
preferences = f'{interface.localNode.radioConfig.preferences}'
|
preferences = f'{interface.localNode.radioConfig.preferences}'
|
||||||
prefs = preferences.splitlines()
|
prefs = preferences.splitlines()
|
||||||
if prefs:
|
if prefs:
|
||||||
config += "user_prefs:\n"
|
if Globals.getInstance().get_camel_case():
|
||||||
|
config += "userPrefs:\n"
|
||||||
|
else:
|
||||||
|
config += "user_prefs:\n"
|
||||||
for pref in prefs:
|
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)
|
print(config)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@@ -692,10 +734,12 @@ def initParser():
|
|||||||
action="store_true")
|
action="store_true")
|
||||||
|
|
||||||
parser.add_argument(
|
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(
|
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(
|
parser.add_argument(
|
||||||
"--seturl", help="Set a channel URL", action="store")
|
"--seturl", help="Set a channel URL", action="store")
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ class Globals:
|
|||||||
self.channel_index = None
|
self.channel_index = None
|
||||||
self.logfile = None
|
self.logfile = None
|
||||||
self.tunnelInstance = 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):
|
def reset(self):
|
||||||
"""Reset all of our globals. If you add a member, add it to this method, too."""
|
"""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.channel_index = None
|
||||||
self.logfile = None
|
self.logfile = None
|
||||||
self.tunnelInstance = None
|
self.tunnelInstance = None
|
||||||
|
# TODO: to migrate to camel_case for v1.3 change this value to True
|
||||||
|
self.camel_case = False
|
||||||
|
|
||||||
# setters
|
# setters
|
||||||
def set_args(self, args):
|
def set_args(self, args):
|
||||||
@@ -60,6 +64,10 @@ class Globals:
|
|||||||
"""Set the tunnelInstance"""
|
"""Set the tunnelInstance"""
|
||||||
self.tunnelInstance = tunnelInstance
|
self.tunnelInstance = tunnelInstance
|
||||||
|
|
||||||
|
def set_camel_case(self):
|
||||||
|
"""Force using camelCase for things like prefs/set/set"""
|
||||||
|
self.camel_case = True
|
||||||
|
|
||||||
# getters
|
# getters
|
||||||
def get_args(self):
|
def get_args(self):
|
||||||
"""Get args"""
|
"""Get args"""
|
||||||
@@ -80,3 +88,7 @@ class Globals:
|
|||||||
def get_tunnelInstance(self):
|
def get_tunnelInstance(self):
|
||||||
"""Get tunnelInstance"""
|
"""Get tunnelInstance"""
|
||||||
return self.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
|
from meshtastic.util import pskToString, stripnl, Timeout, our_exit, fromPSK
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
class Node:
|
||||||
"""A model of a (local or remote) node in the mesh
|
"""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 ..node import Node
|
||||||
from ..channel_pb2 import Channel
|
from ..channel_pb2 import Channel
|
||||||
from ..remote_hardware import onGPIOreceive
|
from ..remote_hardware import onGPIOreceive
|
||||||
|
from ..radioconfig_pb2 import RadioConfig
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@@ -366,6 +367,27 @@ def test_main_qr(capsys):
|
|||||||
mo.assert_called()
|
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.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
def test_main_nodes(capsys):
|
def test_main_nodes(capsys):
|
||||||
@@ -781,6 +803,49 @@ def test_main_set_valid(capsys):
|
|||||||
mo.assert_called()
|
mo.assert_called()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
|
def test_main_set_valid_wifi_passwd(capsys):
|
||||||
|
"""Test --set with valid field"""
|
||||||
|
sys.argv = ['', '--set', 'wifi_password', '123456789']
|
||||||
|
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'Set wifi_password to 123456789', out, re.MULTILINE)
|
||||||
|
assert err == ''
|
||||||
|
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.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
def test_main_set_with_invalid(capsys):
|
def test_main_set_with_invalid(capsys):
|
||||||
@@ -809,7 +874,7 @@ def test_main_set_with_invalid(capsys):
|
|||||||
# TODO: write some negative --configure tests
|
# TODO: write some negative --configure tests
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
def test_main_configure(capsys):
|
def test_main_configure_with_snake_case(capsys):
|
||||||
"""Test --configure with valid file"""
|
"""Test --configure with valid file"""
|
||||||
sys.argv = ['', '--configure', 'example_config.yaml']
|
sys.argv = ['', '--configure', 'example_config.yaml']
|
||||||
Globals.getInstance().set_args(sys.argv)
|
Globals.getInstance().set_args(sys.argv)
|
||||||
@@ -819,6 +884,33 @@ def test_main_configure(capsys):
|
|||||||
iface = MagicMock(autospec=SerialInterface)
|
iface = MagicMock(autospec=SerialInterface)
|
||||||
iface.getNode.return_value = mocked_node
|
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'Set location_share to LocEnabled', 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_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:
|
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo:
|
||||||
main()
|
main()
|
||||||
out, err = capsys.readouterr()
|
out, err = capsys.readouterr()
|
||||||
@@ -832,7 +924,6 @@ def test_main_configure(capsys):
|
|||||||
assert err == ''
|
assert err == ''
|
||||||
mo.assert_called()
|
mo.assert_called()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
def test_main_ch_add_valid(capsys):
|
def test_main_ch_add_valid(capsys):
|
||||||
@@ -1289,6 +1380,33 @@ def test_main_get_with_valid_values(capsys):
|
|||||||
assert err == ''
|
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.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
def test_main_get_with_invalid(capsys):
|
def test_main_get_with_invalid(capsys):
|
||||||
@@ -1492,6 +1610,45 @@ position_flags: 35"""
|
|||||||
assert err == ''
|
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.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
def test_main_export_config_called_from_main(capsys):
|
def test_main_export_config_called_from_main(capsys):
|
||||||
@@ -1625,6 +1782,22 @@ def test_main_getPref_valid_field(capsys):
|
|||||||
assert err == ''
|
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.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
def test_main_getPref_valid_field_string(capsys):
|
def test_main_getPref_valid_field_string(capsys):
|
||||||
@@ -1641,6 +1814,23 @@ def test_main_getPref_valid_field_string(capsys):
|
|||||||
assert err == ''
|
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.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
def test_main_getPref_valid_field_bool(capsys):
|
def test_main_getPref_valid_field_bool(capsys):
|
||||||
@@ -1657,6 +1847,23 @@ def test_main_getPref_valid_field_bool(capsys):
|
|||||||
assert err == ''
|
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.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
def test_main_getPref_invalid_field(capsys):
|
def test_main_getPref_invalid_field(capsys):
|
||||||
@@ -1691,7 +1898,40 @@ def test_main_getPref_invalid_field(capsys):
|
|||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@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"""
|
"""Test setPref() with a valid field"""
|
||||||
|
|
||||||
class Field:
|
class Field:
|
||||||
@@ -1712,6 +1952,95 @@ def test_main_setPref_valid_field_int(capsys):
|
|||||||
assert err == ''
|
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 re.search(r'Choices in sorted order are', out, re.MULTILINE)
|
||||||
|
assert re.search(r'MA100', out, re.MULTILINE)
|
||||||
|
assert re.search(r'MA280', out, re.MULTILINE)
|
||||||
|
assert err == ''
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
|
def test_main_setPref_valid_field_invalid_enum_where_enums_are_camel_cased_values(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, 'location_share', 'foo')
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
assert re.search(r'location_share does not have an enum called foo', out, re.MULTILINE)
|
||||||
|
assert re.search(r'Choices in sorted order are', out, re.MULTILINE)
|
||||||
|
assert re.search(r'LocDisabled', out, re.MULTILINE)
|
||||||
|
assert re.search(r'LocEnabled', 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.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
def test_main_setPref_invalid_field(capsys):
|
def test_main_setPref_invalid_field(capsys):
|
||||||
@@ -1744,6 +2073,38 @@ def test_main_setPref_invalid_field(capsys):
|
|||||||
assert err == ''
|
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.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_globals")
|
||||||
def test_main_ch_set_psk_no_ch_index(capsys):
|
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,
|
support_info, genPSK256, fromStr, fromPSK,
|
||||||
quoteBooleans, catchAndIgnore,
|
quoteBooleans, catchAndIgnore,
|
||||||
remove_keys_from_dict, Timeout, hexstr,
|
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
|
@pytest.mark.unit
|
||||||
@@ -39,6 +40,7 @@ def test_fromStr():
|
|||||||
assert fromStr('100.01') == 100.01
|
assert fromStr('100.01') == 100.01
|
||||||
assert fromStr('123') == 123
|
assert fromStr('123') == 123
|
||||||
assert fromStr('abc') == 'abc'
|
assert fromStr('abc') == 'abc'
|
||||||
|
assert fromStr('123456789') == 123456789
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unitslow
|
@pytest.mark.unitslow
|
||||||
@@ -251,3 +253,22 @@ def test_convert_mac_addr():
|
|||||||
assert convert_mac_addr('/c0gFyhb') == 'fd:cd:20:17:28:5b'
|
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('fd:cd:20:17:28:5b') == 'fd:cd:20:17:28:5b'
|
||||||
assert convert_mac_addr('') == ''
|
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)
|
val_as_bytes = base64.b64decode(val)
|
||||||
return hexstr(val_as_bytes)
|
return hexstr(val_as_bytes)
|
||||||
return val
|
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
|
# This call to setup() does all the work
|
||||||
setup(
|
setup(
|
||||||
name="meshtastic",
|
name="meshtastic",
|
||||||
version="1.2.54",
|
version="1.2.57",
|
||||||
description="Python API & client shell for talking to Meshtastic devices",
|
description="Python API & client shell for talking to Meshtastic devices",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
|
|||||||
Reference in New Issue
Block a user