mirror of
https://github.com/meshtastic/python.git
synced 2025-12-30 11:27:53 -05:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d6fa369e8 | ||
|
|
6812f508bc | ||
|
|
158cac6b0b | ||
|
|
4d10b6e1bd | ||
|
|
bb6e3b7a49 | ||
|
|
8b29dc683a | ||
|
|
d4156d78c6 | ||
|
|
c52977781e | ||
|
|
5ebc8e6f95 | ||
|
|
56d1dcafe5 | ||
|
|
393c765557 | ||
|
|
243b310eb2 | ||
|
|
cd1ecab4e0 | ||
|
|
c710953b85 | ||
|
|
70c5a30b77 | ||
|
|
9f0ba7aeae | ||
|
|
4226201423 | ||
|
|
bdf3a24be1 | ||
|
|
e8ba5581f6 | ||
|
|
948846e0f1 | ||
|
|
6c4dbb6fe6 | ||
|
|
afbabf9538 | ||
|
|
d8107122a2 | ||
|
|
03c1f08e45 | ||
|
|
760fcfcea7 | ||
|
|
a4830f5f62 | ||
|
|
2b1f337a41 | ||
|
|
ddad5f08b3 | ||
|
|
6e7933a3ce | ||
|
|
f449ff9506 | ||
|
|
a07e853f69 | ||
|
|
0d57449030 | ||
|
|
067cddd354 | ||
|
|
4af1b322da | ||
|
|
c580df15e1 | ||
|
|
b280d0ba23 | ||
|
|
439b1ade2e | ||
|
|
9f2b54eb98 | ||
|
|
278ca74a70 | ||
|
|
1c93b7bd52 | ||
|
|
2d4be347e9 | ||
|
|
26f024dc11 | ||
|
|
2b8348ea05 | ||
|
|
590b60fefb |
@@ -16,16 +16,15 @@ from pubsub import pub # type: ignore[import-untyped]
|
|||||||
|
|
||||||
import meshtastic.test
|
import meshtastic.test
|
||||||
import meshtastic.util
|
import meshtastic.util
|
||||||
|
from meshtastic import mt_config
|
||||||
from meshtastic import channel_pb2, config_pb2, portnums_pb2, remote_hardware, BROADCAST_ADDR
|
from meshtastic import channel_pb2, config_pb2, portnums_pb2, remote_hardware, BROADCAST_ADDR
|
||||||
from meshtastic.version import get_active_version
|
from meshtastic.version import get_active_version
|
||||||
from meshtastic.ble_interface import BLEInterface
|
from meshtastic.ble_interface import BLEInterface
|
||||||
from meshtastic.globals import Globals
|
from meshtastic.mesh_interface import MeshInterface
|
||||||
|
|
||||||
|
|
||||||
def onReceive(packet, interface):
|
def onReceive(packet, interface):
|
||||||
"""Callback invoked when a packet arrives"""
|
"""Callback invoked when a packet arrives"""
|
||||||
our_globals = Globals.getInstance()
|
args = mt_config.args
|
||||||
args = our_globals.get_args()
|
|
||||||
try:
|
try:
|
||||||
d = packet.get("decoded")
|
d = packet.get("decoded")
|
||||||
logging.debug(f"in onReceive() d:{d}")
|
logging.debug(f"in onReceive() d:{d}")
|
||||||
@@ -58,6 +57,11 @@ def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=W0613
|
|||||||
"""Callback invoked when we connect/disconnect from a radio"""
|
"""Callback invoked when we connect/disconnect from a radio"""
|
||||||
print(f"Connection changed: {topic.getName()}")
|
print(f"Connection changed: {topic.getName()}")
|
||||||
|
|
||||||
|
def checkChannel(interface: MeshInterface, channelIndex: int) -> bool:
|
||||||
|
"""Given an interface and channel index, return True if that channel is non-disabled on the local node"""
|
||||||
|
ch = interface.localNode.getChannelByChannelIndex(channelIndex)
|
||||||
|
logging.debug(f"ch:{ch}")
|
||||||
|
return (ch and ch.role != channel_pb2.Channel.Role.DISABLED)
|
||||||
|
|
||||||
def getPref(node, comp_name):
|
def getPref(node, comp_name):
|
||||||
"""Get a channel or preferences value"""
|
"""Get a channel or preferences value"""
|
||||||
@@ -69,7 +73,7 @@ def getPref(node, comp_name):
|
|||||||
# Note: protobufs has the keys in snake_case, so snake internally
|
# Note: protobufs has the keys in snake_case, so snake internally
|
||||||
snake_name = meshtastic.util.camel_to_snake(name[1])
|
snake_name = meshtastic.util.camel_to_snake(name[1])
|
||||||
logging.debug(f"snake_name:{snake_name} camel_name:{camel_name}")
|
logging.debug(f"snake_name:{snake_name} camel_name:{camel_name}")
|
||||||
logging.debug(f"use camel:{Globals.getInstance().get_camel_case()}")
|
logging.debug(f"use camel:{mt_config.camel_case}")
|
||||||
|
|
||||||
# First validate the input
|
# First validate the input
|
||||||
localConfig = node.localConfig
|
localConfig = node.localConfig
|
||||||
@@ -86,7 +90,7 @@ def getPref(node, comp_name):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
if Globals.getInstance().get_camel_case():
|
if mt_config.camel_case:
|
||||||
print(
|
print(
|
||||||
f"{localConfig.__class__.__name__} and {moduleConfig.__class__.__name__} do not have an attribute {snake_name}."
|
f"{localConfig.__class__.__name__} and {moduleConfig.__class__.__name__} do not have an attribute {snake_name}."
|
||||||
)
|
)
|
||||||
@@ -105,7 +109,7 @@ def getPref(node, comp_name):
|
|||||||
config_values = getattr(config, config_type.name)
|
config_values = getattr(config, config_type.name)
|
||||||
if not wholeField:
|
if not wholeField:
|
||||||
pref_value = getattr(config_values, pref.name)
|
pref_value = getattr(config_values, pref.name)
|
||||||
if Globals.getInstance().get_camel_case():
|
if mt_config.camel_case:
|
||||||
print(f"{str(config_type.name)}.{camel_name}: {str(pref_value)}")
|
print(f"{str(config_type.name)}.{camel_name}: {str(pref_value)}")
|
||||||
logging.debug(
|
logging.debug(
|
||||||
f"{str(config_type.name)}.{camel_name}: {str(pref_value)}"
|
f"{str(config_type.name)}.{camel_name}: {str(pref_value)}"
|
||||||
@@ -127,25 +131,46 @@ def getPref(node, comp_name):
|
|||||||
|
|
||||||
def splitCompoundName(comp_name):
|
def splitCompoundName(comp_name):
|
||||||
"""Split compound (dot separated) preference name into parts"""
|
"""Split compound (dot separated) preference name into parts"""
|
||||||
name = comp_name.split(".", 1)
|
name = comp_name.split(".")
|
||||||
if len(name) != 2:
|
if len(name) < 2:
|
||||||
name[0] = comp_name
|
name[0] = comp_name
|
||||||
name.append(comp_name)
|
name.append(comp_name)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
def traverseConfig(config_root, config, interface_config):
|
||||||
|
"""Iterate through current config level preferences and either traverse deeper if preference is a dict or set preference"""
|
||||||
|
snake_name = meshtastic.util.camel_to_snake(config_root)
|
||||||
|
for pref in config:
|
||||||
|
pref_name = f"{snake_name}.{pref}"
|
||||||
|
if isinstance(config[pref], dict):
|
||||||
|
traverseConfig(pref_name, config[pref], interface_config)
|
||||||
|
else:
|
||||||
|
setPref(
|
||||||
|
interface_config,
|
||||||
|
pref_name,
|
||||||
|
str(config[pref])
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def setPref(config, comp_name, valStr) -> bool:
|
def setPref(config, comp_name, valStr) -> bool:
|
||||||
"""Set a channel or preferences value"""
|
"""Set a channel or preferences value"""
|
||||||
|
|
||||||
name = splitCompoundName(comp_name)
|
name = splitCompoundName(comp_name)
|
||||||
|
|
||||||
snake_name = meshtastic.util.camel_to_snake(name[1])
|
snake_name = meshtastic.util.camel_to_snake(name[-1])
|
||||||
camel_name = meshtastic.util.snake_to_camel(name[1])
|
camel_name = meshtastic.util.snake_to_camel(name[-1])
|
||||||
logging.debug(f"snake_name:{snake_name}")
|
logging.debug(f"snake_name:{snake_name}")
|
||||||
logging.debug(f"camel_name:{camel_name}")
|
logging.debug(f"camel_name:{camel_name}")
|
||||||
|
|
||||||
objDesc = config.DESCRIPTOR
|
objDesc = config.DESCRIPTOR
|
||||||
|
config_part = config
|
||||||
config_type = objDesc.fields_by_name.get(name[0])
|
config_type = objDesc.fields_by_name.get(name[0])
|
||||||
|
if config_type and config_type.message_type is not None:
|
||||||
|
for name_part in name[1:-1]:
|
||||||
|
part_snake_name = meshtastic.util.camel_to_snake((name_part))
|
||||||
|
config_part = getattr(config, config_type.name)
|
||||||
|
config_type = config_type.message_type.fields_by_name.get(part_snake_name)
|
||||||
pref = None
|
pref = None
|
||||||
if config_type and config_type.message_type is not None:
|
if config_type and config_type.message_type is not None:
|
||||||
pref = config_type.message_type.fields_by_name.get(snake_name)
|
pref = config_type.message_type.fields_by_name.get(snake_name)
|
||||||
@@ -171,7 +196,7 @@ def setPref(config, comp_name, valStr) -> bool:
|
|||||||
if e:
|
if e:
|
||||||
val = e.number
|
val = e.number
|
||||||
else:
|
else:
|
||||||
if Globals.getInstance().get_camel_case():
|
if mt_config.camel_case:
|
||||||
print(
|
print(
|
||||||
f"{name[0]}.{camel_name} does not have an enum called {val}, so you can not set it."
|
f"{name[0]}.{camel_name} does not have an enum called {val}, so you can not set it."
|
||||||
)
|
)
|
||||||
@@ -192,13 +217,13 @@ def setPref(config, comp_name, valStr) -> bool:
|
|||||||
if snake_name != "ignore_incoming":
|
if snake_name != "ignore_incoming":
|
||||||
try:
|
try:
|
||||||
if config_type.message_type is not None:
|
if config_type.message_type is not None:
|
||||||
config_values = getattr(config, config_type.name)
|
config_values = getattr(config_part, config_type.name)
|
||||||
setattr(config_values, pref.name, val)
|
setattr(config_values, pref.name, val)
|
||||||
else:
|
else:
|
||||||
setattr(config, snake_name, val)
|
setattr(config_part, snake_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
|
||||||
config_values = getattr(config, config_type.name)
|
config_values = getattr(config_part, config_type.name)
|
||||||
setattr(config_values, pref.name, valStr)
|
setattr(config_values, pref.name, valStr)
|
||||||
else:
|
else:
|
||||||
if val == 0:
|
if val == 0:
|
||||||
@@ -209,8 +234,8 @@ def setPref(config, comp_name, valStr) -> bool:
|
|||||||
print(f"Adding '{val}' to the ignore_incoming list")
|
print(f"Adding '{val}' to the ignore_incoming list")
|
||||||
config_type.message_type.ignore_incoming.extend([val])
|
config_type.message_type.ignore_incoming.extend([val])
|
||||||
|
|
||||||
prefix = f"{name[0]}." if config_type.message_type is not None else ""
|
prefix = f"{'.'.join(name[0:-1])}." if config_type.message_type is not None else ""
|
||||||
if Globals.getInstance().get_camel_case():
|
if mt_config.camel_case:
|
||||||
print(f"Set {prefix}{camel_name} to {valStr}")
|
print(f"Set {prefix}{camel_name} to {valStr}")
|
||||||
else:
|
else:
|
||||||
print(f"Set {prefix}{snake_name} to {valStr}")
|
print(f"Set {prefix}{snake_name} to {valStr}")
|
||||||
@@ -225,14 +250,16 @@ def onConnected(interface):
|
|||||||
False # Should we wait for an acknowledgment if we send to a remote node?
|
False # Should we wait for an acknowledgment if we send to a remote node?
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
our_globals = Globals.getInstance()
|
args = mt_config.args
|
||||||
args = our_globals.get_args()
|
|
||||||
|
|
||||||
# do not print this line if we are exporting the config
|
# do not print this line if we are exporting the config
|
||||||
if not args.export_config:
|
if not args.export_config:
|
||||||
print("Connected to radio")
|
print("Connected to radio")
|
||||||
|
|
||||||
if args.setlat or args.setlon or args.setalt:
|
if args.setlat or args.setlon or args.setalt:
|
||||||
|
if args.dest != BROADCAST_ADDR:
|
||||||
|
print("Setting latitude, longitude, and altitude of remote nodes is not supported.")
|
||||||
|
return
|
||||||
closeNow = True
|
closeNow = True
|
||||||
|
|
||||||
alt = 0
|
alt = 0
|
||||||
@@ -259,7 +286,12 @@ def onConnected(interface):
|
|||||||
interface.localNode.writeConfig("position")
|
interface.localNode.writeConfig("position")
|
||||||
elif not args.no_time:
|
elif not args.no_time:
|
||||||
# We normally provide a current time to the mesh when we connect
|
# We normally provide a current time to the mesh when we connect
|
||||||
interface.sendPosition()
|
if interface.localNode.nodeNum in interface.nodesByNum and "position" in interface.nodesByNum[interface.localNode.nodeNum]:
|
||||||
|
# send the same position the node already knows, just to update time
|
||||||
|
position = interface.nodesByNum[interface.localNode.nodeNum]["position"]
|
||||||
|
interface.sendPosition(position.get("latitude", 0.0), position.get("longitude", 0.0), position.get("altitude", 0.0))
|
||||||
|
else:
|
||||||
|
interface.sendPosition()
|
||||||
|
|
||||||
if args.set_owner:
|
if args.set_owner:
|
||||||
closeNow = True
|
closeNow = True
|
||||||
@@ -343,6 +375,11 @@ def onConnected(interface):
|
|||||||
waitForAckNak = True
|
waitForAckNak = True
|
||||||
interface.getNode(args.dest, False).rebootOTA()
|
interface.getNode(args.dest, False).rebootOTA()
|
||||||
|
|
||||||
|
if args.enter_dfu:
|
||||||
|
closeNow = True
|
||||||
|
waitForAckNak = True
|
||||||
|
interface.getNode(args.dest, False).enterDFUMode()
|
||||||
|
|
||||||
if args.shutdown:
|
if args.shutdown:
|
||||||
closeNow = True
|
closeNow = True
|
||||||
waitForAckNak = True
|
waitForAckNak = True
|
||||||
@@ -365,6 +402,11 @@ def onConnected(interface):
|
|||||||
waitForAckNak = True
|
waitForAckNak = True
|
||||||
interface.getNode(args.dest, False).factoryReset()
|
interface.getNode(args.dest, False).factoryReset()
|
||||||
|
|
||||||
|
if args.remove_node:
|
||||||
|
closeNow = True
|
||||||
|
waitForAckNak = True
|
||||||
|
interface.getNode(args.dest, False).removeNode(args.remove_node)
|
||||||
|
|
||||||
if args.reset_nodedb:
|
if args.reset_nodedb:
|
||||||
closeNow = True
|
closeNow = True
|
||||||
waitForAckNak = True
|
waitForAckNak = True
|
||||||
@@ -372,12 +414,8 @@ def onConnected(interface):
|
|||||||
|
|
||||||
if args.sendtext:
|
if args.sendtext:
|
||||||
closeNow = True
|
closeNow = True
|
||||||
channelIndex = 0
|
channelIndex = mt_config.channel_index or 0
|
||||||
if args.ch_index is not None:
|
if checkChannel(interface, channelIndex):
|
||||||
channelIndex = int(args.ch_index)
|
|
||||||
ch = interface.localNode.getChannelByChannelIndex(channelIndex)
|
|
||||||
logging.debug(f"ch:{ch}")
|
|
||||||
if ch and ch.role != channel_pb2.Channel.Role.DISABLED:
|
|
||||||
print(
|
print(
|
||||||
f"Sending text message {args.sendtext} to {args.dest} on channelIndex:{channelIndex}"
|
f"Sending text message {args.sendtext} to {args.dest} on channelIndex:{channelIndex}"
|
||||||
)
|
)
|
||||||
@@ -397,15 +435,28 @@ def onConnected(interface):
|
|||||||
loraConfig = getattr(interface.localNode.localConfig, "lora")
|
loraConfig = getattr(interface.localNode.localConfig, "lora")
|
||||||
hopLimit = getattr(loraConfig, "hop_limit")
|
hopLimit = getattr(loraConfig, "hop_limit")
|
||||||
dest = str(args.traceroute)
|
dest = str(args.traceroute)
|
||||||
print(f"Sending traceroute request to {dest} (this could take a while)")
|
channelIndex = mt_config.channel_index or 0
|
||||||
interface.sendTraceRoute(dest, hopLimit)
|
if checkChannel(interface, channelIndex):
|
||||||
|
print(f"Sending traceroute request to {dest} on channelIndex:{channelIndex} (this could take a while)")
|
||||||
|
interface.sendTraceRoute(dest, hopLimit, channelIndex=channelIndex)
|
||||||
|
|
||||||
if args.request_telemetry:
|
if args.request_telemetry:
|
||||||
if args.dest == BROADCAST_ADDR:
|
if args.dest == BROADCAST_ADDR:
|
||||||
meshtastic.util.our_exit("Warning: Must use a destination node ID.")
|
meshtastic.util.our_exit("Warning: Must use a destination node ID.")
|
||||||
else:
|
else:
|
||||||
print(f"Sending telemetry request to {args.dest} (this could take a while)")
|
channelIndex = mt_config.channel_index or 0
|
||||||
interface.sendTelemetry(destinationId=args.dest, wantResponse=True)
|
if checkChannel(interface, channelIndex):
|
||||||
|
print(f"Sending telemetry request to {args.dest} on channelIndex:{channelIndex} (this could take a while)")
|
||||||
|
interface.sendTelemetry(destinationId=args.dest, wantResponse=True, channelIndex=channelIndex)
|
||||||
|
|
||||||
|
if args.request_position:
|
||||||
|
if args.dest == BROADCAST_ADDR:
|
||||||
|
meshtastic.util.our_exit("Warning: Must use a destination node ID.")
|
||||||
|
else:
|
||||||
|
channelIndex = mt_config.channel_index or 0
|
||||||
|
if checkChannel(interface, channelIndex):
|
||||||
|
print(f"Sending position request to {args.dest} on channelIndex:{channelIndex} (this could take a while)")
|
||||||
|
interface.sendPosition(destinationId=args.dest, wantResponse=True, channelIndex=channelIndex)
|
||||||
|
|
||||||
if args.gpio_wrb or args.gpio_rd or args.gpio_watch:
|
if args.gpio_wrb or args.gpio_rd or args.gpio_watch:
|
||||||
if args.dest == BROADCAST_ADDR:
|
if args.dest == BROADCAST_ADDR:
|
||||||
@@ -472,7 +523,7 @@ def onConnected(interface):
|
|||||||
print("Writing modified preferences to device")
|
print("Writing modified preferences to device")
|
||||||
node.writeConfig(field)
|
node.writeConfig(field)
|
||||||
else:
|
else:
|
||||||
if Globals.getInstance().get_camel_case():
|
if mt_config.camel_case:
|
||||||
print(
|
print(
|
||||||
f"{node.localConfig.__class__.__name__} and {node.moduleConfig.__class__.__name__} do not have an attribute {pref[0]}."
|
f"{node.localConfig.__class__.__name__} and {node.moduleConfig.__class__.__name__} do not have an attribute {pref[0]}."
|
||||||
)
|
)
|
||||||
@@ -529,15 +580,15 @@ def onConnected(interface):
|
|||||||
localConfig = interface.localNode.localConfig
|
localConfig = interface.localNode.localConfig
|
||||||
|
|
||||||
if "alt" in configuration["location"]:
|
if "alt" in configuration["location"]:
|
||||||
alt = int(configuration["location"]["alt"])
|
alt = int(configuration["location"]["alt"] or 0)
|
||||||
localConfig.position.fixed_position = True
|
localConfig.position.fixed_position = True
|
||||||
print(f"Fixing altitude at {alt} meters")
|
print(f"Fixing altitude at {alt} meters")
|
||||||
if "lat" in configuration["location"]:
|
if "lat" in configuration["location"]:
|
||||||
lat = float(configuration["location"]["lat"])
|
lat = float(configuration["location"]["lat"] or 0)
|
||||||
localConfig.position.fixed_position = True
|
localConfig.position.fixed_position = True
|
||||||
print(f"Fixing latitude at {lat} degrees")
|
print(f"Fixing latitude at {lat} degrees")
|
||||||
if "lon" in configuration["location"]:
|
if "lon" in configuration["location"]:
|
||||||
lon = float(configuration["location"]["lon"])
|
lon = float(configuration["location"]["lon"] or 0)
|
||||||
localConfig.position.fixed_position = True
|
localConfig.position.fixed_position = True
|
||||||
print(f"Fixing longitude at {lon} degrees")
|
print(f"Fixing longitude at {lon} degrees")
|
||||||
print("Setting device position")
|
print("Setting device position")
|
||||||
@@ -547,12 +598,7 @@ def onConnected(interface):
|
|||||||
if "config" in configuration:
|
if "config" in configuration:
|
||||||
localConfig = interface.getNode(args.dest).localConfig
|
localConfig = interface.getNode(args.dest).localConfig
|
||||||
for section in configuration["config"]:
|
for section in configuration["config"]:
|
||||||
for pref in configuration["config"][section]:
|
traverseConfig(section, configuration["config"][section], localConfig)
|
||||||
setPref(
|
|
||||||
localConfig,
|
|
||||||
f"{meshtastic.util.camel_to_snake(section)}.{pref}",
|
|
||||||
str(configuration["config"][section][pref]),
|
|
||||||
)
|
|
||||||
interface.getNode(args.dest).writeConfig(
|
interface.getNode(args.dest).writeConfig(
|
||||||
meshtastic.util.camel_to_snake(section)
|
meshtastic.util.camel_to_snake(section)
|
||||||
)
|
)
|
||||||
@@ -560,12 +606,7 @@ def onConnected(interface):
|
|||||||
if "module_config" in configuration:
|
if "module_config" in configuration:
|
||||||
moduleConfig = interface.getNode(args.dest).moduleConfig
|
moduleConfig = interface.getNode(args.dest).moduleConfig
|
||||||
for section in configuration["module_config"]:
|
for section in configuration["module_config"]:
|
||||||
for pref in configuration["module_config"][section]:
|
traverseConfig(section, configuration["module_config"][section], moduleConfig)
|
||||||
setPref(
|
|
||||||
moduleConfig,
|
|
||||||
f"{meshtastic.util.camel_to_snake(section)}.{pref}",
|
|
||||||
str(configuration["module_config"][section][pref]),
|
|
||||||
)
|
|
||||||
interface.getNode(args.dest).writeConfig(
|
interface.getNode(args.dest).writeConfig(
|
||||||
meshtastic.util.camel_to_snake(section)
|
meshtastic.util.camel_to_snake(section)
|
||||||
)
|
)
|
||||||
@@ -574,6 +615,9 @@ def onConnected(interface):
|
|||||||
print("Writing modified configuration to device")
|
print("Writing modified configuration to device")
|
||||||
|
|
||||||
if args.export_config:
|
if args.export_config:
|
||||||
|
if args.dest != BROADCAST_ADDR:
|
||||||
|
print("Exporting configuration of remote nodes is not supported.")
|
||||||
|
return
|
||||||
# export the configuration (the opposite of '--configure')
|
# export the configuration (the opposite of '--configure')
|
||||||
closeNow = True
|
closeNow = True
|
||||||
export_config(interface)
|
export_config(interface)
|
||||||
@@ -585,7 +629,7 @@ def onConnected(interface):
|
|||||||
# handle changing channels
|
# handle changing channels
|
||||||
|
|
||||||
if args.ch_add:
|
if args.ch_add:
|
||||||
channelIndex = our_globals.get_channel_index()
|
channelIndex = mt_config.channel_index
|
||||||
if channelIndex is not None:
|
if channelIndex is not None:
|
||||||
# Since we set the channel index after adding a channel, don't allow --ch-index
|
# Since we set the channel index after adding a channel, don't allow --ch-index
|
||||||
meshtastic.util.our_exit(
|
meshtastic.util.our_exit(
|
||||||
@@ -616,12 +660,12 @@ def onConnected(interface):
|
|||||||
n.writeChannel(ch.index)
|
n.writeChannel(ch.index)
|
||||||
if channelIndex is None:
|
if channelIndex is None:
|
||||||
print(f"Setting newly-added channel's {ch.index} as '--ch-index' for further modifications")
|
print(f"Setting newly-added channel's {ch.index} as '--ch-index' for further modifications")
|
||||||
our_globals.set_channel_index(ch.index)
|
mt_config.channel_index = ch.index
|
||||||
|
|
||||||
if args.ch_del:
|
if args.ch_del:
|
||||||
closeNow = True
|
closeNow = True
|
||||||
|
|
||||||
channelIndex = our_globals.get_channel_index()
|
channelIndex = mt_config.channel_index
|
||||||
if channelIndex is None:
|
if channelIndex is None:
|
||||||
meshtastic.util.our_exit(
|
meshtastic.util.our_exit(
|
||||||
"Warning: Need to specify '--ch-index' for '--ch-del'.", 1
|
"Warning: Need to specify '--ch-index' for '--ch-del'.", 1
|
||||||
@@ -637,7 +681,7 @@ def onConnected(interface):
|
|||||||
|
|
||||||
def setSimpleConfig(modem_preset):
|
def setSimpleConfig(modem_preset):
|
||||||
"""Set one of the simple modem_config"""
|
"""Set one of the simple modem_config"""
|
||||||
channelIndex = our_globals.get_channel_index()
|
channelIndex = mt_config.channel_index
|
||||||
if channelIndex is not None and channelIndex > 0:
|
if channelIndex is not None and channelIndex > 0:
|
||||||
meshtastic.util.our_exit(
|
meshtastic.util.our_exit(
|
||||||
"Warning: Cannot set modem preset for non-primary channel", 1
|
"Warning: Cannot set modem preset for non-primary channel", 1
|
||||||
@@ -672,7 +716,7 @@ def onConnected(interface):
|
|||||||
if args.ch_set or args.ch_enable or args.ch_disable:
|
if args.ch_set or args.ch_enable or args.ch_disable:
|
||||||
closeNow = True
|
closeNow = True
|
||||||
|
|
||||||
channelIndex = our_globals.get_channel_index()
|
channelIndex = mt_config.channel_index
|
||||||
if channelIndex is None:
|
if channelIndex is None:
|
||||||
meshtastic.util.our_exit("Warning: Need to specify '--ch-index'.", 1)
|
meshtastic.util.our_exit("Warning: Need to specify '--ch-index'.", 1)
|
||||||
ch = interface.getNode(args.dest).channels[channelIndex]
|
ch = interface.getNode(args.dest).channels[channelIndex]
|
||||||
@@ -776,10 +820,14 @@ def onConnected(interface):
|
|||||||
return
|
return
|
||||||
interface.showNodes()
|
interface.showNodes()
|
||||||
|
|
||||||
if args.qr:
|
if args.qr or args.qr_all:
|
||||||
closeNow = True
|
closeNow = True
|
||||||
url = interface.localNode.getURL(includeAll=False)
|
url = interface.getNode(args.dest, True).getURL(includeAll=args.qr_all)
|
||||||
print(f"Primary channel URL {url}")
|
if args.qr_all:
|
||||||
|
urldesc = "Complete URL (includes all channels)"
|
||||||
|
else:
|
||||||
|
urldesc = "Primary channel URL"
|
||||||
|
print(f"{urldesc}: {url}")
|
||||||
qr = pyqrcode.create(url)
|
qr = pyqrcode.create(url)
|
||||||
print(qr.terminal())
|
print(qr.terminal())
|
||||||
|
|
||||||
@@ -788,6 +836,9 @@ def onConnected(interface):
|
|||||||
|
|
||||||
have_tunnel = platform.system() == "Linux"
|
have_tunnel = platform.system() == "Linux"
|
||||||
if have_tunnel and args.tunnel:
|
if have_tunnel and args.tunnel:
|
||||||
|
if args.dest != BROADCAST_ADDR:
|
||||||
|
print("A tunnel can only be created using the local node.")
|
||||||
|
return
|
||||||
# pylint: disable=C0415
|
# pylint: disable=C0415
|
||||||
from . import tunnel
|
from . import tunnel
|
||||||
|
|
||||||
@@ -827,7 +878,7 @@ def printConfig(config):
|
|||||||
names = []
|
names = []
|
||||||
for field in config.message_type.fields:
|
for field in config.message_type.fields:
|
||||||
tmp_name = f"{config_section.name}.{field.name}"
|
tmp_name = f"{config_section.name}.{field.name}"
|
||||||
if Globals.getInstance().get_camel_case():
|
if mt_config.camel_case:
|
||||||
tmp_name = meshtastic.util.snake_to_camel(tmp_name)
|
tmp_name = meshtastic.util.snake_to_camel(tmp_name)
|
||||||
names.append(tmp_name)
|
names.append(tmp_name)
|
||||||
for temp_name in sorted(names):
|
for temp_name in sorted(names):
|
||||||
@@ -872,23 +923,26 @@ def export_config(interface):
|
|||||||
if owner_short:
|
if owner_short:
|
||||||
configObj["owner_short"] = owner_short
|
configObj["owner_short"] = owner_short
|
||||||
if channel_url:
|
if channel_url:
|
||||||
if Globals.getInstance().get_camel_case():
|
if mt_config.camel_case:
|
||||||
configObj["channelUrl"] = channel_url
|
configObj["channelUrl"] = channel_url
|
||||||
else:
|
else:
|
||||||
configObj["channel_url"] = channel_url
|
configObj["channel_url"] = channel_url
|
||||||
if lat or lon or alt:
|
# lat and lon don't make much sense without the other (so fill with 0s), and alt isn't meaningful without both
|
||||||
configObj["location"] = {"lat": lat, "lon": lon, "alt": alt}
|
if lat or lon:
|
||||||
|
configObj["location"] = {"lat": lat or float(0), "lon": lon or float(0)}
|
||||||
|
if alt:
|
||||||
|
configObj["location"]["alt"] = alt
|
||||||
|
|
||||||
config = MessageToDict(interface.localNode.localConfig)
|
config = MessageToDict(interface.localNode.localConfig)
|
||||||
if config:
|
if config:
|
||||||
# Convert inner keys to correct snake/camelCase
|
# Convert inner keys to correct snake/camelCase
|
||||||
prefs = {}
|
prefs = {}
|
||||||
for pref in config:
|
for pref in config:
|
||||||
if Globals.getInstance().get_camel_case():
|
if mt_config.camel_case:
|
||||||
prefs[meshtastic.util.snake_to_camel(pref)] = config[pref]
|
prefs[meshtastic.util.snake_to_camel(pref)] = config[pref]
|
||||||
else:
|
else:
|
||||||
prefs[pref] = config[pref]
|
prefs[pref] = config[pref]
|
||||||
if Globals.getInstance().get_camel_case():
|
if mt_config.camel_case:
|
||||||
configObj["config"] = config
|
configObj["config"] = config
|
||||||
else:
|
else:
|
||||||
configObj["config"] = config
|
configObj["config"] = config
|
||||||
@@ -900,7 +954,7 @@ def export_config(interface):
|
|||||||
for pref in module_config:
|
for pref in module_config:
|
||||||
if len(module_config[pref]) > 0:
|
if len(module_config[pref]) > 0:
|
||||||
prefs[pref] = module_config[pref]
|
prefs[pref] = module_config[pref]
|
||||||
if Globals.getInstance().get_camel_case():
|
if mt_config.camel_case:
|
||||||
configObj["module_config"] = prefs
|
configObj["module_config"] = prefs
|
||||||
else:
|
else:
|
||||||
configObj["module_config"] = prefs
|
configObj["module_config"] = prefs
|
||||||
@@ -914,9 +968,8 @@ def export_config(interface):
|
|||||||
def common():
|
def common():
|
||||||
"""Shared code for all of our command line wrappers"""
|
"""Shared code for all of our command line wrappers"""
|
||||||
logfile = None
|
logfile = None
|
||||||
our_globals = Globals.getInstance()
|
args = mt_config.args
|
||||||
args = our_globals.get_args()
|
parser = mt_config.parser
|
||||||
parser = our_globals.get_parser()
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.DEBUG if (args.debug or args.listen) else logging.INFO,
|
level=logging.DEBUG if (args.debug or args.listen) else logging.INFO,
|
||||||
format="%(levelname)s file:%(filename)s %(funcName)s line:%(lineno)s %(message)s",
|
format="%(levelname)s file:%(filename)s %(funcName)s line:%(lineno)s %(message)s",
|
||||||
@@ -932,7 +985,7 @@ def common():
|
|||||||
|
|
||||||
if args.ch_index is not None:
|
if args.ch_index is not None:
|
||||||
channelIndex = int(args.ch_index)
|
channelIndex = int(args.ch_index)
|
||||||
our_globals.set_channel_index(channelIndex)
|
mt_config.channel_index = channelIndex
|
||||||
|
|
||||||
if not args.dest:
|
if not args.dest:
|
||||||
args.dest = BROADCAST_ADDR
|
args.dest = BROADCAST_ADDR
|
||||||
@@ -967,7 +1020,7 @@ def common():
|
|||||||
# Note: using "line buffering"
|
# Note: using "line buffering"
|
||||||
# pylint: disable=R1732
|
# pylint: disable=R1732
|
||||||
logfile = open(args.seriallog, "w+", buffering=1, encoding="utf8")
|
logfile = open(args.seriallog, "w+", buffering=1, encoding="utf8")
|
||||||
our_globals.set_logfile(logfile)
|
mt_config.logfile = logfile
|
||||||
|
|
||||||
subscribe()
|
subscribe()
|
||||||
if args.ble_scan:
|
if args.ble_scan:
|
||||||
@@ -1058,9 +1111,8 @@ def addConnectionArgs(parser: argparse.ArgumentParser) -> argparse.ArgumentParse
|
|||||||
|
|
||||||
def initParser():
|
def initParser():
|
||||||
"""Initialize the command line argument parsing."""
|
"""Initialize the command line argument parsing."""
|
||||||
our_globals = Globals.getInstance()
|
parser = mt_config.parser
|
||||||
parser = our_globals.get_parser()
|
args = mt_config.args
|
||||||
args = our_globals.get_args()
|
|
||||||
|
|
||||||
# The "Help" group includes the help option and other informational stuff about the CLI itself
|
# The "Help" group includes the help option and other informational stuff about the CLI itself
|
||||||
outerHelpGroup = parser.add_argument_group('Help')
|
outerHelpGroup = parser.add_argument_group('Help')
|
||||||
@@ -1128,7 +1180,16 @@ def initParser():
|
|||||||
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--qr",
|
"--qr",
|
||||||
help="Display the QR code that corresponds to the current channel",
|
help=(
|
||||||
|
"Display a QR code for the node's primary channel (or all channels with --qr-all). "
|
||||||
|
"Also shows the shareable channel URL."
|
||||||
|
),
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--qr-all",
|
||||||
|
help="Display a QR code and URL for all of the node's channels.",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1281,9 +1342,17 @@ def initParser():
|
|||||||
)
|
)
|
||||||
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--request-telemetry",
|
"--request-telemetry",
|
||||||
help="Request telemetry from a node. "
|
help="Request telemetry from a node. "
|
||||||
"You need pass the destination ID as argument with '--dest'. "
|
"You need to pass the destination ID as argument with '--dest'. "
|
||||||
|
"For repeaters, the nodeNum is required.",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--request-position",
|
||||||
|
help="Request the position from a nade. "
|
||||||
|
"You need to pass the destination ID as an argument with '--dest'. "
|
||||||
"For repeaters, the nodeNum is required.",
|
"For repeaters, the nodeNum is required.",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
@@ -1300,7 +1369,13 @@ def initParser():
|
|||||||
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--reboot-ota",
|
"--reboot-ota",
|
||||||
help="Tell the destination node to reboot into factory firmware",
|
help="Tell the destination node to reboot into factory firmware (ESP32)",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--enter-dfu",
|
||||||
|
help="Tell the destination node to enter DFU mode (NRF52)",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1332,9 +1407,13 @@ def initParser():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--remove-node",
|
||||||
|
help="Tell the destination node to remove a specific node from its DB, by node number or ID"
|
||||||
|
)
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--reset-nodedb",
|
"--reset-nodedb",
|
||||||
help="Tell the destination node clear its list of nodes",
|
help="Tell the destination node to clear its list of nodes",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1422,34 +1501,32 @@ def initParser():
|
|||||||
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
our_globals.set_args(args)
|
mt_config.args = args
|
||||||
our_globals.set_parser(parser)
|
mt_config.parser = parser
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Perform command line meshtastic operations"""
|
"""Perform command line meshtastic operations"""
|
||||||
our_globals = Globals.getInstance()
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
add_help=False,
|
add_help=False,
|
||||||
epilog="If no connection arguments are specified, we search for a compatible serial device, "
|
epilog="If no connection arguments are specified, we search for a compatible serial device, "
|
||||||
"and if none is found, then attempt a TCP connection to localhost.")
|
"and if none is found, then attempt a TCP connection to localhost.")
|
||||||
our_globals.set_parser(parser)
|
mt_config.parser = parser
|
||||||
initParser()
|
initParser()
|
||||||
common()
|
common()
|
||||||
logfile = our_globals.get_logfile()
|
logfile = mt_config.logfile
|
||||||
if logfile:
|
if logfile:
|
||||||
logfile.close()
|
logfile.close()
|
||||||
|
|
||||||
|
|
||||||
def tunnelMain():
|
def tunnelMain():
|
||||||
"""Run a meshtastic IP tunnel"""
|
"""Run a meshtastic IP tunnel"""
|
||||||
our_globals = Globals.getInstance()
|
|
||||||
parser = argparse.ArgumentParser(add_help=False)
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
our_globals.set_parser(parser)
|
mt_config.parser = parser
|
||||||
initParser()
|
initParser()
|
||||||
args = our_globals.get_args()
|
args = mt_config.args
|
||||||
args.tunnel = True
|
args.tunnel = True
|
||||||
our_globals.set_args(args)
|
mt_config.args = args
|
||||||
common()
|
common()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -245,6 +245,7 @@ class Config(google.protobuf.message.Message):
|
|||||||
IS_MANAGED_FIELD_NUMBER: builtins.int
|
IS_MANAGED_FIELD_NUMBER: builtins.int
|
||||||
DISABLE_TRIPLE_CLICK_FIELD_NUMBER: builtins.int
|
DISABLE_TRIPLE_CLICK_FIELD_NUMBER: builtins.int
|
||||||
TZDEF_FIELD_NUMBER: builtins.int
|
TZDEF_FIELD_NUMBER: builtins.int
|
||||||
|
LED_HEARTBEAT_DISABLED_FIELD_NUMBER: builtins.int
|
||||||
role: global___Config.DeviceConfig.Role.ValueType
|
role: global___Config.DeviceConfig.Role.ValueType
|
||||||
"""
|
"""
|
||||||
Sets the role of node
|
Sets the role of node
|
||||||
@@ -294,6 +295,10 @@ class Config(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
POSIX Timezone definition string from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv.
|
POSIX Timezone definition string from https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv.
|
||||||
"""
|
"""
|
||||||
|
led_heartbeat_disabled: builtins.bool
|
||||||
|
"""
|
||||||
|
If true, disable the default blinking LED (LED_PIN) behavior on the device
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -308,8 +313,9 @@ class Config(google.protobuf.message.Message):
|
|||||||
is_managed: builtins.bool = ...,
|
is_managed: builtins.bool = ...,
|
||||||
disable_triple_click: builtins.bool = ...,
|
disable_triple_click: builtins.bool = ...,
|
||||||
tzdef: builtins.str = ...,
|
tzdef: builtins.str = ...,
|
||||||
|
led_heartbeat_disabled: builtins.bool = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing_extensions.Literal["button_gpio", b"button_gpio", "buzzer_gpio", b"buzzer_gpio", "debug_log_enabled", b"debug_log_enabled", "disable_triple_click", b"disable_triple_click", "double_tap_as_button_press", b"double_tap_as_button_press", "is_managed", b"is_managed", "node_info_broadcast_secs", b"node_info_broadcast_secs", "rebroadcast_mode", b"rebroadcast_mode", "role", b"role", "serial_enabled", b"serial_enabled", "tzdef", b"tzdef"]) -> None: ...
|
def ClearField(self, field_name: typing_extensions.Literal["button_gpio", b"button_gpio", "buzzer_gpio", b"buzzer_gpio", "debug_log_enabled", b"debug_log_enabled", "disable_triple_click", b"disable_triple_click", "double_tap_as_button_press", b"double_tap_as_button_press", "is_managed", b"is_managed", "led_heartbeat_disabled", b"led_heartbeat_disabled", "node_info_broadcast_secs", b"node_info_broadcast_secs", "rebroadcast_mode", b"rebroadcast_mode", "role", b"role", "serial_enabled", b"serial_enabled", "tzdef", b"tzdef"]) -> None: ...
|
||||||
|
|
||||||
@typing_extensions.final
|
@typing_extensions.final
|
||||||
class PositionConfig(google.protobuf.message.Message):
|
class PositionConfig(google.protobuf.message.Message):
|
||||||
@@ -575,27 +581,25 @@ class Config(google.protobuf.message.Message):
|
|||||||
DEVICE_BATTERY_INA_ADDRESS_FIELD_NUMBER: builtins.int
|
DEVICE_BATTERY_INA_ADDRESS_FIELD_NUMBER: builtins.int
|
||||||
is_power_saving: builtins.bool
|
is_power_saving: builtins.bool
|
||||||
"""
|
"""
|
||||||
If set, we are powered from a low-current source (i.e. solar), so even if it looks like we have power flowing in
|
Description: Will sleep everything as much as possible, for the tracker and sensor role this will also include the lora radio.
|
||||||
we should try to minimize power consumption as much as possible.
|
Don't use this setting if you want to use your device with the phone apps or are using a device without a user button.
|
||||||
YOU DO NOT NEED TO SET THIS IF YOU'VE set is_router (it is implied in that case).
|
Technical Details: Works for ESP32 devices and NRF52 devices in the Sensor or Tracker roles
|
||||||
Advanced Option
|
|
||||||
"""
|
"""
|
||||||
on_battery_shutdown_after_secs: builtins.int
|
on_battery_shutdown_after_secs: builtins.int
|
||||||
"""
|
"""
|
||||||
If non-zero, the device will fully power off this many seconds after external power is removed.
|
Description: If non-zero, the device will fully power off this many seconds after external power is removed.
|
||||||
"""
|
"""
|
||||||
adc_multiplier_override: builtins.float
|
adc_multiplier_override: builtins.float
|
||||||
"""
|
"""
|
||||||
Ratio of voltage divider for battery pin eg. 3.20 (R1=100k, R2=220k)
|
Ratio of voltage divider for battery pin eg. 3.20 (R1=100k, R2=220k)
|
||||||
Overrides the ADC_MULTIPLIER defined in variant for battery voltage calculation.
|
Overrides the ADC_MULTIPLIER defined in variant for battery voltage calculation.
|
||||||
Should be set to floating point value between 2 and 4
|
https://meshtastic.org/docs/configuration/radio/power/#adc-multiplier-override
|
||||||
Fixes issues on Heltec v2
|
Should be set to floating point value between 2 and 6
|
||||||
"""
|
"""
|
||||||
wait_bluetooth_secs: builtins.int
|
wait_bluetooth_secs: builtins.int
|
||||||
"""
|
"""
|
||||||
Wait Bluetooth Seconds
|
Description: The number of seconds for to wait before turning off BLE in No Bluetooth states
|
||||||
The number of seconds for to wait before turning off BLE in No Bluetooth states
|
Technical Details: ESP32 Only 0 for default of 1 minute
|
||||||
0 for default of 1 minute
|
|
||||||
"""
|
"""
|
||||||
sds_secs: builtins.int
|
sds_secs: builtins.int
|
||||||
"""
|
"""
|
||||||
@@ -606,16 +610,13 @@ class Config(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
ls_secs: builtins.int
|
ls_secs: builtins.int
|
||||||
"""
|
"""
|
||||||
Light Sleep Seconds
|
Description: In light sleep the CPU is suspended, LoRa radio is on, BLE is off an GPS is on
|
||||||
In light sleep the CPU is suspended, LoRa radio is on, BLE is off an GPS is on
|
Technical Details: ESP32 Only 0 for default of 300
|
||||||
ESP32 Only
|
|
||||||
0 for default of 300
|
|
||||||
"""
|
"""
|
||||||
min_wake_secs: builtins.int
|
min_wake_secs: builtins.int
|
||||||
"""
|
"""
|
||||||
Minimum Wake Seconds
|
Description: While in light sleep when we receive packets on the LoRa radio we will wake and handle them and stay awake in no BLE mode for this value
|
||||||
While in light sleep when we receive packets on the LoRa radio we will wake and handle them and stay awake in no BLE mode for this value
|
Technical Details: ESP32 Only 0 for default of 10 seconds
|
||||||
0 for default of 10 seconds
|
|
||||||
"""
|
"""
|
||||||
device_battery_ina_address: builtins.int
|
device_battery_ina_address: builtins.int
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
"""Globals singleton class.
|
|
||||||
|
|
||||||
Instead of using a global, stuff your variables in this "trash can".
|
|
||||||
This is not much better than using python's globals, but it allows
|
|
||||||
us to better test meshtastic. Plus, there are some weird python
|
|
||||||
global issues/gotcha that we can hopefully avoid by using this
|
|
||||||
class instead.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Globals:
|
|
||||||
"""Globals class is a Singleton."""
|
|
||||||
|
|
||||||
__instance = None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def getInstance():
|
|
||||||
"""Get an instance of the Globals class."""
|
|
||||||
if Globals.__instance is None:
|
|
||||||
Globals()
|
|
||||||
return Globals.__instance
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Constructor for the Globals CLass"""
|
|
||||||
if Globals.__instance is not None:
|
|
||||||
raise Exception("This class is a singleton") # pylint: disable=W0719
|
|
||||||
else:
|
|
||||||
Globals.__instance = self
|
|
||||||
self.args = None
|
|
||||||
self.parser = None
|
|
||||||
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."""
|
|
||||||
self.args = None
|
|
||||||
self.parser = None
|
|
||||||
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):
|
|
||||||
"""Set the args"""
|
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def set_parser(self, parser):
|
|
||||||
"""Set the parser"""
|
|
||||||
self.parser = parser
|
|
||||||
|
|
||||||
def set_channel_index(self, channel_index):
|
|
||||||
"""Set the channel_index"""
|
|
||||||
self.channel_index = channel_index
|
|
||||||
|
|
||||||
def set_logfile(self, logfile):
|
|
||||||
"""Set the logfile"""
|
|
||||||
self.logfile = logfile
|
|
||||||
|
|
||||||
def set_tunnelInstance(self, tunnelInstance):
|
|
||||||
"""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"""
|
|
||||||
return self.args
|
|
||||||
|
|
||||||
def get_parser(self):
|
|
||||||
"""Get parser"""
|
|
||||||
return self.parser
|
|
||||||
|
|
||||||
def get_channel_index(self):
|
|
||||||
"""Get channel_index"""
|
|
||||||
return self.channel_index
|
|
||||||
|
|
||||||
def get_logfile(self):
|
|
||||||
"""Get logfile"""
|
|
||||||
return self.logfile
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -10,6 +10,8 @@ import threading
|
|||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Union
|
||||||
|
|
||||||
import google.protobuf.json_format
|
import google.protobuf.json_format
|
||||||
import timeago # type: ignore[import-untyped]
|
import timeago # type: ignore[import-untyped]
|
||||||
from pubsub import pub # type: ignore[import-untyped]
|
from pubsub import pub # type: ignore[import-untyped]
|
||||||
@@ -54,7 +56,7 @@ class MeshInterface:
|
|||||||
self.message = message
|
self.message = message
|
||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
||||||
def __init__(self, debugOut=None, noProto=False):
|
def __init__(self, debugOut=None, noProto: bool=False) -> None:
|
||||||
"""Constructor
|
"""Constructor
|
||||||
|
|
||||||
Keyword Arguments:
|
Keyword Arguments:
|
||||||
@@ -62,27 +64,27 @@ class MeshInterface:
|
|||||||
link - just be a dumb serial client.
|
link - just be a dumb serial client.
|
||||||
"""
|
"""
|
||||||
self.debugOut = debugOut
|
self.debugOut = debugOut
|
||||||
self.nodes = None # FIXME
|
self.nodes: Optional[Dict[str,Dict]] = None # FIXME
|
||||||
self.isConnected = threading.Event()
|
self.isConnected: threading.Event = threading.Event()
|
||||||
self.noProto = noProto
|
self.noProto: bool = noProto
|
||||||
self.localNode = meshtastic.node.Node(self, -1) # We fixup nodenum later
|
self.localNode: meshtastic.node.Node = meshtastic.node.Node(self, -1) # We fixup nodenum later
|
||||||
self.myInfo = None # We don't have device info yet
|
self.myInfo: Optional[mesh_pb2.MyNodeInfo] = None # We don't have device info yet
|
||||||
self.metadata = None # We don't have device metadata yet
|
self.metadata: Optional[mesh_pb2.DeviceMetadata] = None # We don't have device metadata yet
|
||||||
self.responseHandlers = {} # A map from request ID to the handler
|
self.responseHandlers: Dict[int,ResponseHandler] = {} # A map from request ID to the handler
|
||||||
self.failure = (
|
self.failure = (
|
||||||
None # If we've encountered a fatal exception it will be kept here
|
None # If we've encountered a fatal exception it will be kept here
|
||||||
)
|
)
|
||||||
self._timeout = Timeout()
|
self._timeout: Timeout = Timeout()
|
||||||
self._acknowledgment = Acknowledgment()
|
self._acknowledgment: Acknowledgment = Acknowledgment()
|
||||||
self.heartbeatTimer = None
|
self.heartbeatTimer: Optional[threading.Timer] = None
|
||||||
random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it
|
random.seed() # FIXME, we should not clobber the random seedval here, instead tell user they must call it
|
||||||
self.currentPacketId = random.randint(0, 0xFFFFFFFF)
|
self.currentPacketId: int = random.randint(0, 0xFFFFFFFF)
|
||||||
self.nodesByNum = None
|
self.nodesByNum: Optional[Dict[int, Dict]] = None
|
||||||
self.configId = None
|
self.configId: Optional[int] = None
|
||||||
self.gotResponse = False # used in gpio read
|
self.gotResponse: bool = False # used in gpio read
|
||||||
self.mask = None # used in gpio read and gpio watch
|
self.mask: Optional[int] = None # used in gpio read and gpio watch
|
||||||
self.queueStatus = None
|
self.queueStatus: Optional[mesh_pb2.QueueStatus] = None
|
||||||
self.queue = collections.OrderedDict()
|
self.queue: collections.OrderedDict = collections.OrderedDict()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Shutdown this interface"""
|
"""Shutdown this interface"""
|
||||||
@@ -103,7 +105,7 @@ class MeshInterface:
|
|||||||
logging.error(f"Traceback: {traceback}")
|
logging.error(f"Traceback: {traceback}")
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def showInfo(self, file=sys.stdout): # pylint: disable=W0613
|
def showInfo(self, file=sys.stdout) -> str: # pylint: disable=W0613
|
||||||
"""Show human readable summary about this object"""
|
"""Show human readable summary about this object"""
|
||||||
owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
|
owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
|
||||||
myinfo = ""
|
myinfo = ""
|
||||||
@@ -135,20 +137,20 @@ class MeshInterface:
|
|||||||
print(infos)
|
print(infos)
|
||||||
return infos
|
return infos
|
||||||
|
|
||||||
def showNodes(self, includeSelf=True, file=sys.stdout): # pylint: disable=W0613
|
def showNodes(self, includeSelf: bool=True, file=sys.stdout) -> str: # pylint: disable=W0613
|
||||||
"""Show table summary of nodes in mesh"""
|
"""Show table summary of nodes in mesh"""
|
||||||
|
|
||||||
def formatFloat(value, precision=2, unit=""):
|
def formatFloat(value, precision=2, unit="") -> Optional[str]:
|
||||||
"""Format a float value with precision."""
|
"""Format a float value with precision."""
|
||||||
return f"{value:.{precision}f}{unit}" if value else None
|
return f"{value:.{precision}f}{unit}" if value else None
|
||||||
|
|
||||||
def getLH(ts):
|
def getLH(ts) -> Optional[str]:
|
||||||
"""Format last heard"""
|
"""Format last heard"""
|
||||||
return (
|
return (
|
||||||
datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") if ts else None
|
datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S") if ts else None
|
||||||
)
|
)
|
||||||
|
|
||||||
def getTimeAgo(ts):
|
def getTimeAgo(ts) -> Optional[str]:
|
||||||
"""Format how long ago have we heard from this node (aka timeago)."""
|
"""Format how long ago have we heard from this node (aka timeago)."""
|
||||||
return (
|
return (
|
||||||
timeago.format(datetime.fromtimestamp(ts), datetime.now())
|
timeago.format(datetime.fromtimestamp(ts), datetime.now())
|
||||||
@@ -156,7 +158,7 @@ class MeshInterface:
|
|||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
rows = []
|
rows: List[Dict[str, Any]] = []
|
||||||
if self.nodesByNum:
|
if self.nodesByNum:
|
||||||
logging.debug(f"self.nodes:{self.nodes}")
|
logging.debug(f"self.nodes:{self.nodes}")
|
||||||
for node in self.nodesByNum.values():
|
for node in self.nodesByNum.values():
|
||||||
@@ -208,6 +210,7 @@ class MeshInterface:
|
|||||||
row.update(
|
row.update(
|
||||||
{
|
{
|
||||||
"SNR": formatFloat(node.get("snr"), 2, " dB"),
|
"SNR": formatFloat(node.get("snr"), 2, " dB"),
|
||||||
|
"Hops Away": node.get("hopsAway", "unknown"),
|
||||||
"Channel": node.get("channel"),
|
"Channel": node.get("channel"),
|
||||||
"LastHeard": getLH(node.get("lastHeard")),
|
"LastHeard": getLH(node.get("lastHeard")),
|
||||||
"Since": getTimeAgo(node.get("lastHeard")),
|
"Since": getTimeAgo(node.get("lastHeard")),
|
||||||
@@ -224,7 +227,7 @@ class MeshInterface:
|
|||||||
print(table)
|
print(table)
|
||||||
return table
|
return table
|
||||||
|
|
||||||
def getNode(self, nodeId, requestChannels=True):
|
def getNode(self, nodeId: str, requestChannels: bool=True) -> meshtastic.node.Node:
|
||||||
"""Return a node object which contains device settings and channel info"""
|
"""Return a node object which contains device settings and channel info"""
|
||||||
if nodeId in (LOCAL_ADDR, BROADCAST_ADDR):
|
if nodeId in (LOCAL_ADDR, BROADCAST_ADDR):
|
||||||
return self.localNode
|
return self.localNode
|
||||||
@@ -241,11 +244,11 @@ class MeshInterface:
|
|||||||
def sendText(
|
def sendText(
|
||||||
self,
|
self,
|
||||||
text: str,
|
text: str,
|
||||||
destinationId=BROADCAST_ADDR,
|
destinationId: Union[int, str]=BROADCAST_ADDR,
|
||||||
wantAck=False,
|
wantAck: bool=False,
|
||||||
wantResponse=False,
|
wantResponse: bool=False,
|
||||||
onResponse=None,
|
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None,
|
||||||
channelIndex=0,
|
channelIndex: int=0,
|
||||||
):
|
):
|
||||||
"""Send a utf8 string to some other node, if the node has a display it
|
"""Send a utf8 string to some other node, if the node has a display it
|
||||||
will also be shown on the device.
|
will also be shown on the device.
|
||||||
@@ -280,12 +283,12 @@ class MeshInterface:
|
|||||||
def sendData(
|
def sendData(
|
||||||
self,
|
self,
|
||||||
data,
|
data,
|
||||||
destinationId=BROADCAST_ADDR,
|
destinationId: Union[int, str]=BROADCAST_ADDR,
|
||||||
portNum=portnums_pb2.PortNum.PRIVATE_APP,
|
portNum: portnums_pb2.PortNum.ValueType=portnums_pb2.PortNum.PRIVATE_APP,
|
||||||
wantAck=False,
|
wantAck: bool=False,
|
||||||
wantResponse=False,
|
wantResponse: bool=False,
|
||||||
onResponse=None,
|
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None,
|
||||||
channelIndex=0,
|
channelIndex: int=0,
|
||||||
):
|
):
|
||||||
"""Send a data packet to some other node
|
"""Send a data packet to some other node
|
||||||
|
|
||||||
@@ -334,19 +337,21 @@ class MeshInterface:
|
|||||||
meshPacket.id = self._generatePacketId()
|
meshPacket.id = self._generatePacketId()
|
||||||
|
|
||||||
if onResponse is not None:
|
if onResponse is not None:
|
||||||
|
logging.debug(f"Setting a response handler for requestId {meshPacket.id}")
|
||||||
self._addResponseHandler(meshPacket.id, onResponse)
|
self._addResponseHandler(meshPacket.id, onResponse)
|
||||||
p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck)
|
p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck)
|
||||||
return p
|
return p
|
||||||
|
|
||||||
def sendPosition(
|
def sendPosition(
|
||||||
self,
|
self,
|
||||||
latitude=0.0,
|
latitude: float=0.0,
|
||||||
longitude=0.0,
|
longitude: float=0.0,
|
||||||
altitude=0,
|
altitude: int=0,
|
||||||
timeSec=0,
|
timeSec: int=0,
|
||||||
destinationId=BROADCAST_ADDR,
|
destinationId: Union[int, str]=BROADCAST_ADDR,
|
||||||
wantAck=False,
|
wantAck: bool=False,
|
||||||
wantResponse=False,
|
wantResponse: bool=False,
|
||||||
|
channelIndex: int=0,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Send a position packet to some other node (normally a broadcast)
|
Send a position packet to some other node (normally a broadcast)
|
||||||
@@ -373,19 +378,57 @@ class MeshInterface:
|
|||||||
logging.debug(f"p.altitude:{p.altitude}")
|
logging.debug(f"p.altitude:{p.altitude}")
|
||||||
|
|
||||||
if timeSec == 0:
|
if timeSec == 0:
|
||||||
timeSec = time.time() # returns unix timestamp in seconds
|
timeSec = int(time.time()) # returns unix timestamp in seconds
|
||||||
p.time = int(timeSec)
|
p.time = timeSec
|
||||||
logging.debug(f"p.time:{p.time}")
|
logging.debug(f"p.time:{p.time}")
|
||||||
|
|
||||||
return self.sendData(
|
if wantResponse:
|
||||||
|
onResponse = self.onResponsePosition
|
||||||
|
else:
|
||||||
|
onResponse = None
|
||||||
|
|
||||||
|
d = self.sendData(
|
||||||
p,
|
p,
|
||||||
destinationId,
|
destinationId,
|
||||||
portNum=portnums_pb2.PortNum.POSITION_APP,
|
portNum=portnums_pb2.PortNum.POSITION_APP,
|
||||||
wantAck=wantAck,
|
wantAck=wantAck,
|
||||||
wantResponse=wantResponse,
|
wantResponse=wantResponse,
|
||||||
|
onResponse=onResponse,
|
||||||
|
channelIndex=channelIndex,
|
||||||
)
|
)
|
||||||
|
if wantResponse:
|
||||||
|
self.waitForPosition()
|
||||||
|
return d
|
||||||
|
|
||||||
def sendTraceRoute(self, dest, hopLimit):
|
def onResponsePosition(self, p):
|
||||||
|
"""on response for position"""
|
||||||
|
if p["decoded"]["portnum"] == 'POSITION_APP':
|
||||||
|
self._acknowledgment.receivedPosition = True
|
||||||
|
position = mesh_pb2.Position()
|
||||||
|
position.ParseFromString(p["decoded"]["payload"])
|
||||||
|
|
||||||
|
ret = "Position received: "
|
||||||
|
if position.latitude_i != 0 and position.longitude_i != 0:
|
||||||
|
ret += f"({position.latitude_i * 10**-7}, {position.longitude_i * 10**-7})"
|
||||||
|
else:
|
||||||
|
ret += "(unknown)"
|
||||||
|
if position.altitude != 0:
|
||||||
|
ret += f" {position.altitude}m"
|
||||||
|
|
||||||
|
if position.precision_bits not in [0,32]:
|
||||||
|
ret += f" precision:{position.precision_bits}"
|
||||||
|
elif position.precision_bits == 32:
|
||||||
|
ret += " full precision"
|
||||||
|
elif position.precision_bits == 0:
|
||||||
|
ret += " position disabled"
|
||||||
|
|
||||||
|
print(ret)
|
||||||
|
|
||||||
|
elif p["decoded"]["portnum"] == 'ROUTING_APP':
|
||||||
|
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
|
||||||
|
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
|
||||||
|
|
||||||
|
def sendTraceRoute(self, dest: Union[int, str], hopLimit: int, channelIndex: int=0):
|
||||||
"""Send the trace route"""
|
"""Send the trace route"""
|
||||||
r = mesh_pb2.RouteDiscovery()
|
r = mesh_pb2.RouteDiscovery()
|
||||||
self.sendData(
|
self.sendData(
|
||||||
@@ -394,9 +437,10 @@ class MeshInterface:
|
|||||||
portNum=portnums_pb2.PortNum.TRACEROUTE_APP,
|
portNum=portnums_pb2.PortNum.TRACEROUTE_APP,
|
||||||
wantResponse=True,
|
wantResponse=True,
|
||||||
onResponse=self.onResponseTraceRoute,
|
onResponse=self.onResponseTraceRoute,
|
||||||
|
channelIndex=channelIndex,
|
||||||
)
|
)
|
||||||
# extend timeout based on number of nodes, limit by configured hopLimit
|
# extend timeout based on number of nodes, limit by configured hopLimit
|
||||||
waitFactor = min(len(self.nodes) - 1, hopLimit)
|
waitFactor = min(len(self.nodes) - 1 if self.nodes else 0, hopLimit)
|
||||||
self.waitForTraceRoute(waitFactor)
|
self.waitForTraceRoute(waitFactor)
|
||||||
|
|
||||||
def onResponseTraceRoute(self, p):
|
def onResponseTraceRoute(self, p):
|
||||||
@@ -415,43 +459,40 @@ class MeshInterface:
|
|||||||
|
|
||||||
self._acknowledgment.receivedTraceRoute = True
|
self._acknowledgment.receivedTraceRoute = True
|
||||||
|
|
||||||
def sendTelemetry(self, destinationId=BROADCAST_ADDR, wantResponse=False):
|
def sendTelemetry(self, destinationId: Union[int,str]=BROADCAST_ADDR, wantResponse: bool=False, channelIndex: int=0):
|
||||||
"""Send telemetry and optionally ask for a response"""
|
"""Send telemetry and optionally ask for a response"""
|
||||||
r = telemetry_pb2.Telemetry()
|
r = telemetry_pb2.Telemetry()
|
||||||
|
|
||||||
node = next(n for n in self.nodes.values() if n["num"] == self.localNode.nodeNum)
|
if self.nodes is not None:
|
||||||
if node is not None:
|
node = next(n for n in self.nodes.values() if n["num"] == self.localNode.nodeNum)
|
||||||
metrics = node.get("deviceMetrics")
|
if node is not None:
|
||||||
if metrics:
|
metrics = node.get("deviceMetrics")
|
||||||
batteryLevel = metrics.get("batteryLevel")
|
if metrics:
|
||||||
if batteryLevel is not None:
|
batteryLevel = metrics.get("batteryLevel")
|
||||||
r.device_metrics.battery_level = batteryLevel
|
if batteryLevel is not None:
|
||||||
voltage = metrics.get("voltage")
|
r.device_metrics.battery_level = batteryLevel
|
||||||
if voltage is not None:
|
voltage = metrics.get("voltage")
|
||||||
r.device_metrics.voltage = voltage
|
if voltage is not None:
|
||||||
channel_utilization = metrics.get("channelUtilization")
|
r.device_metrics.voltage = voltage
|
||||||
if channel_utilization is not None:
|
channel_utilization = metrics.get("channelUtilization")
|
||||||
r.device_metrics.channel_utilization = channel_utilization
|
if channel_utilization is not None:
|
||||||
air_util_tx = metrics.get("airUtilTx")
|
r.device_metrics.channel_utilization = channel_utilization
|
||||||
if air_util_tx is not None:
|
air_util_tx = metrics.get("airUtilTx")
|
||||||
r.device_metrics.air_util_tx = air_util_tx
|
if air_util_tx is not None:
|
||||||
|
r.device_metrics.air_util_tx = air_util_tx
|
||||||
|
|
||||||
if wantResponse:
|
if wantResponse:
|
||||||
onResponse = self.onResponseTelemetry
|
onResponse = self.onResponseTelemetry
|
||||||
else:
|
else:
|
||||||
onResponse = None
|
onResponse = None
|
||||||
|
|
||||||
if destinationId.startswith("!"):
|
|
||||||
destinationId = int(destinationId[1:], 16)
|
|
||||||
else:
|
|
||||||
destinationId = int(destinationId)
|
|
||||||
|
|
||||||
self.sendData(
|
self.sendData(
|
||||||
r,
|
r,
|
||||||
destinationId=destinationId,
|
destinationId=destinationId,
|
||||||
portNum=portnums_pb2.PortNum.TELEMETRY_APP,
|
portNum=portnums_pb2.PortNum.TELEMETRY_APP,
|
||||||
wantResponse=wantResponse,
|
wantResponse=wantResponse,
|
||||||
onResponse=onResponse,
|
onResponse=onResponse,
|
||||||
|
channelIndex=channelIndex,
|
||||||
)
|
)
|
||||||
if wantResponse:
|
if wantResponse:
|
||||||
self.waitForTelemetry()
|
self.waitForTelemetry()
|
||||||
@@ -479,10 +520,10 @@ class MeshInterface:
|
|||||||
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
|
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
|
||||||
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
|
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
|
||||||
|
|
||||||
def _addResponseHandler(self, requestId, callback):
|
def _addResponseHandler(self, requestId: int, callback: Callable):
|
||||||
self.responseHandlers[requestId] = ResponseHandler(callback)
|
self.responseHandlers[requestId] = ResponseHandler(callback)
|
||||||
|
|
||||||
def _sendPacket(self, meshPacket, destinationId=BROADCAST_ADDR, wantAck=False):
|
def _sendPacket(self, meshPacket: mesh_pb2.MeshPacket, destinationId: Union[int,str]=BROADCAST_ADDR, wantAck: bool=False):
|
||||||
"""Send a MeshPacket to the specified node (or if unspecified, broadcast).
|
"""Send a MeshPacket to the specified node (or if unspecified, broadcast).
|
||||||
You probably don't want this - use sendData instead.
|
You probably don't want this - use sendData instead.
|
||||||
|
|
||||||
@@ -496,7 +537,7 @@ class MeshInterface:
|
|||||||
|
|
||||||
toRadio = mesh_pb2.ToRadio()
|
toRadio = mesh_pb2.ToRadio()
|
||||||
|
|
||||||
nodeNum = 0
|
nodeNum: int = 0
|
||||||
if destinationId is None:
|
if destinationId is None:
|
||||||
our_exit("Warning: destinationId must not be None")
|
our_exit("Warning: destinationId must not be None")
|
||||||
elif isinstance(destinationId, int):
|
elif isinstance(destinationId, int):
|
||||||
@@ -514,9 +555,10 @@ class MeshInterface:
|
|||||||
else:
|
else:
|
||||||
if self.nodes:
|
if self.nodes:
|
||||||
node = self.nodes.get(destinationId)
|
node = self.nodes.get(destinationId)
|
||||||
if not node:
|
if node is None:
|
||||||
our_exit(f"Warning: NodeId {destinationId} not found in DB")
|
our_exit(f"Warning: NodeId {destinationId} not found in DB")
|
||||||
nodeNum = node["num"]
|
else:
|
||||||
|
nodeNum = node["num"]
|
||||||
else:
|
else:
|
||||||
logging.warning("Warning: There were no self.nodes.")
|
logging.warning("Warning: There were no self.nodes.")
|
||||||
|
|
||||||
@@ -568,9 +610,15 @@ class MeshInterface:
|
|||||||
if not success:
|
if not success:
|
||||||
raise MeshInterface.MeshInterfaceError("Timed out waiting for telemetry")
|
raise MeshInterface.MeshInterfaceError("Timed out waiting for telemetry")
|
||||||
|
|
||||||
def getMyNodeInfo(self):
|
def waitForPosition(self):
|
||||||
|
"""Wait for position"""
|
||||||
|
success = self._timeout.waitForPosition(self._acknowledgment)
|
||||||
|
if not success:
|
||||||
|
raise MeshInterface.MeshInterfaceError("Timed out waiting for position")
|
||||||
|
|
||||||
|
def getMyNodeInfo(self) -> Optional[Dict]:
|
||||||
"""Get info about my node."""
|
"""Get info about my node."""
|
||||||
if self.myInfo is None:
|
if self.myInfo is None or self.nodesByNum is None:
|
||||||
return None
|
return None
|
||||||
logging.debug(f"self.nodesByNum:{self.nodesByNum}")
|
logging.debug(f"self.nodesByNum:{self.nodesByNum}")
|
||||||
return self.nodesByNum.get(self.myInfo.my_node_num)
|
return self.nodesByNum.get(self.myInfo.my_node_num)
|
||||||
@@ -607,7 +655,7 @@ class MeshInterface:
|
|||||||
if self.failure:
|
if self.failure:
|
||||||
raise self.failure
|
raise self.failure
|
||||||
|
|
||||||
def _generatePacketId(self):
|
def _generatePacketId(self) -> int:
|
||||||
"""Get a new unique packet ID"""
|
"""Get a new unique packet ID"""
|
||||||
if self.currentPacketId is None:
|
if self.currentPacketId is None:
|
||||||
raise MeshInterface.MeshInterfaceError("Not connected yet, can not generate packet")
|
raise MeshInterface.MeshInterfaceError("Not connected yet, can not generate packet")
|
||||||
@@ -669,18 +717,18 @@ class MeshInterface:
|
|||||||
m.disconnect = True
|
m.disconnect = True
|
||||||
self._sendToRadio(m)
|
self._sendToRadio(m)
|
||||||
|
|
||||||
def _queueHasFreeSpace(self):
|
def _queueHasFreeSpace(self) -> bool:
|
||||||
# We never got queueStatus, maybe the firmware is old
|
# We never got queueStatus, maybe the firmware is old
|
||||||
if self.queueStatus is None:
|
if self.queueStatus is None:
|
||||||
return True
|
return True
|
||||||
return self.queueStatus.free > 0
|
return self.queueStatus.free > 0
|
||||||
|
|
||||||
def _queueClaim(self):
|
def _queueClaim(self) -> None:
|
||||||
if self.queueStatus is None:
|
if self.queueStatus is None:
|
||||||
return
|
return
|
||||||
self.queueStatus.free -= 1
|
self.queueStatus.free -= 1
|
||||||
|
|
||||||
def _sendToRadio(self, toRadio):
|
def _sendToRadio(self, toRadio: mesh_pb2.ToRadio) -> None:
|
||||||
"""Send a ToRadio protobuf to the device"""
|
"""Send a ToRadio protobuf to the device"""
|
||||||
if self.noProto:
|
if self.noProto:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
@@ -729,18 +777,18 @@ class MeshInterface:
|
|||||||
self.queue[packetId] = packet
|
self.queue[packetId] = packet
|
||||||
# logging.warn("queue + resentQueue: " + " ".join(f'{k:08x}' for k in self.queue))
|
# logging.warn("queue + resentQueue: " + " ".join(f'{k:08x}' for k in self.queue))
|
||||||
|
|
||||||
def _sendToRadioImpl(self, toRadio):
|
def _sendToRadioImpl(self, toRadio: mesh_pb2.ToRadio) -> None:
|
||||||
"""Send a ToRadio protobuf to the device"""
|
"""Send a ToRadio protobuf to the device"""
|
||||||
logging.error(f"Subclass must provide toradio: {toRadio}")
|
logging.error(f"Subclass must provide toradio: {toRadio}")
|
||||||
|
|
||||||
def _handleConfigComplete(self):
|
def _handleConfigComplete(self) -> None:
|
||||||
"""
|
"""
|
||||||
Done with initial config messages, now send regular MeshPackets
|
Done with initial config messages, now send regular MeshPackets
|
||||||
to ask for settings and channels
|
to ask for settings and channels
|
||||||
"""
|
"""
|
||||||
self.localNode.requestChannels()
|
self.localNode.requestChannels()
|
||||||
|
|
||||||
def _handleQueueStatusFromRadio(self, queueStatus):
|
def _handleQueueStatusFromRadio(self, queueStatus) -> None:
|
||||||
self.queueStatus = queueStatus
|
self.queueStatus = queueStatus
|
||||||
logging.debug(
|
logging.debug(
|
||||||
f"TX QUEUE free {queueStatus.free} of {queueStatus.maxlen}, res = {queueStatus.res}, id = {queueStatus.mesh_packet_id:08x} "
|
f"TX QUEUE free {queueStatus.free} of {queueStatus.maxlen}, res = {queueStatus.res}, id = {queueStatus.mesh_packet_id:08x} "
|
||||||
@@ -853,6 +901,10 @@ class MeshInterface:
|
|||||||
self.localNode.moduleConfig.external_notification.CopyFrom(
|
self.localNode.moduleConfig.external_notification.CopyFrom(
|
||||||
fromRadio.moduleConfig.external_notification
|
fromRadio.moduleConfig.external_notification
|
||||||
)
|
)
|
||||||
|
elif fromRadio.moduleConfig.HasField("store_forward"):
|
||||||
|
self.localNode.moduleConfig.store_forward.CopyFrom(
|
||||||
|
fromRadio.moduleConfig.store_forward
|
||||||
|
)
|
||||||
elif fromRadio.moduleConfig.HasField("range_test"):
|
elif fromRadio.moduleConfig.HasField("range_test"):
|
||||||
self.localNode.moduleConfig.range_test.CopyFrom(
|
self.localNode.moduleConfig.range_test.CopyFrom(
|
||||||
fromRadio.moduleConfig.range_test
|
fromRadio.moduleConfig.range_test
|
||||||
@@ -891,7 +943,7 @@ class MeshInterface:
|
|||||||
else:
|
else:
|
||||||
logging.debug("Unexpected FromRadio payload")
|
logging.debug("Unexpected FromRadio payload")
|
||||||
|
|
||||||
def _fixupPosition(self, position):
|
def _fixupPosition(self, position: Dict) -> Dict:
|
||||||
"""Convert integer lat/lon into floats
|
"""Convert integer lat/lon into floats
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -1030,14 +1082,16 @@ class MeshInterface:
|
|||||||
# Is this message in response to a request, if so, look for a handler
|
# Is this message in response to a request, if so, look for a handler
|
||||||
requestId = decoded.get("requestId")
|
requestId = decoded.get("requestId")
|
||||||
if requestId is not None:
|
if requestId is not None:
|
||||||
|
logging.debug(f"Got a response for requestId {requestId}")
|
||||||
# We ignore ACK packets, but send NAKs and data responses to the handlers
|
# We ignore ACK packets, but send NAKs and data responses to the handlers
|
||||||
routing = decoded.get("routing")
|
routing = decoded.get("routing")
|
||||||
isAck = routing is not None and ("errorReason" not in routing)
|
isAck = routing is not None and ("errorReason" not in routing or routing["errorReason"] == "NONE")
|
||||||
if not isAck:
|
if not isAck:
|
||||||
# we keep the responseHandler in dict until we get a non ack
|
# we keep the responseHandler in dict until we get a non ack
|
||||||
handler = self.responseHandlers.pop(requestId, None)
|
handler = self.responseHandlers.pop(requestId, None)
|
||||||
if handler is not None:
|
if handler is not None:
|
||||||
if not isAck or (isAck and handler.__name__ == "onAckNak"):
|
if not isAck or (isAck and handler.__name__ == "onAckNak"):
|
||||||
|
logging.debug(f"Calling response handler for requestId {requestId}")
|
||||||
handler.callback(asDict)
|
handler.callback(asDict)
|
||||||
|
|
||||||
logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
|
logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -267,6 +267,15 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
|||||||
Teledatics TD-LORAC NRF52840 based M.2 LoRA module
|
Teledatics TD-LORAC NRF52840 based M.2 LoRA module
|
||||||
Compatible with the TD-WRLS development board
|
Compatible with the TD-WRLS development board
|
||||||
"""
|
"""
|
||||||
|
CDEBYTE_EORA_S3: _HardwareModel.ValueType # 61
|
||||||
|
"""
|
||||||
|
CDEBYTE EoRa-S3 board using their own MM modules, clone of LILYGO T3S3
|
||||||
|
"""
|
||||||
|
TWC_MESH_V4: _HardwareModel.ValueType # 62
|
||||||
|
"""
|
||||||
|
TWC_MESH_V4
|
||||||
|
Adafruit NRF52840 feather express with SX1262, SSD1306 OLED and NEO6M GPS
|
||||||
|
"""
|
||||||
PRIVATE_HW: _HardwareModel.ValueType # 255
|
PRIVATE_HW: _HardwareModel.ValueType # 255
|
||||||
"""
|
"""
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -519,6 +528,15 @@ TD_LORAC: HardwareModel.ValueType # 60
|
|||||||
Teledatics TD-LORAC NRF52840 based M.2 LoRA module
|
Teledatics TD-LORAC NRF52840 based M.2 LoRA module
|
||||||
Compatible with the TD-WRLS development board
|
Compatible with the TD-WRLS development board
|
||||||
"""
|
"""
|
||||||
|
CDEBYTE_EORA_S3: HardwareModel.ValueType # 61
|
||||||
|
"""
|
||||||
|
CDEBYTE EoRa-S3 board using their own MM modules, clone of LILYGO T3S3
|
||||||
|
"""
|
||||||
|
TWC_MESH_V4: HardwareModel.ValueType # 62
|
||||||
|
"""
|
||||||
|
TWC_MESH_V4
|
||||||
|
Adafruit NRF52840 feather express with SX1262, SSD1306 OLED and NEO6M GPS
|
||||||
|
"""
|
||||||
PRIVATE_HW: HardwareModel.ValueType # 255
|
PRIVATE_HW: HardwareModel.ValueType # 255
|
||||||
"""
|
"""
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
37
meshtastic/mt_config.py
Normal file
37
meshtastic/mt_config.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"""
|
||||||
|
Globals singleton class.
|
||||||
|
|
||||||
|
The Global object is gone, as are all its setters and getters. Instead the
|
||||||
|
module itself is the singleton namespace, which can be imported into
|
||||||
|
whichever module is used. The associated tests have also been removed,
|
||||||
|
since we now rely on built in Python mechanisms.
|
||||||
|
|
||||||
|
This is intended to make the Python read more naturally, and to make the
|
||||||
|
intention of the code clearer and more compact. It is merely a sticking
|
||||||
|
plaster over the use of shared mt_config, but the coupling issues wil be dealt
|
||||||
|
with rather more easily once the code is simplified by this change.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def reset():
|
||||||
|
"""
|
||||||
|
Restore the namespace to pristine condition.
|
||||||
|
"""
|
||||||
|
# pylint: disable=W0603
|
||||||
|
global args, parser, channel_index, logfile, tunnelInstance, camel_case
|
||||||
|
args = None
|
||||||
|
parser = None
|
||||||
|
channel_index = None
|
||||||
|
logfile = None
|
||||||
|
tunnelInstance = None
|
||||||
|
# TODO: to migrate to camel_case for v1.3 change this value to True
|
||||||
|
camel_case = False
|
||||||
|
|
||||||
|
# These assignments are used instead of calling reset()
|
||||||
|
# purely to shut pylint up.
|
||||||
|
args = None
|
||||||
|
parser = None
|
||||||
|
channel_index = None
|
||||||
|
logfile = None
|
||||||
|
tunnelInstance = None
|
||||||
|
camel_case = False
|
||||||
@@ -5,6 +5,8 @@ import base64
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from meshtastic import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, portnums_pb2
|
from meshtastic import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, portnums_pb2
|
||||||
from meshtastic.util import (
|
from meshtastic.util import (
|
||||||
Timeout,
|
Timeout,
|
||||||
@@ -311,6 +313,8 @@ class Node:
|
|||||||
):
|
):
|
||||||
channelSet.settings.append(c.settings)
|
channelSet.settings.append(c.settings)
|
||||||
|
|
||||||
|
if len(self.localConfig.ListFields()) == 0:
|
||||||
|
self.requestConfig(self.localConfig.DESCRIPTOR.fields_by_name.get('lora'))
|
||||||
channelSet.lora_config.CopyFrom(self.localConfig.lora)
|
channelSet.lora_config.CopyFrom(self.localConfig.lora)
|
||||||
some_bytes = channelSet.SerializeToString()
|
some_bytes = channelSet.SerializeToString()
|
||||||
s = base64.urlsafe_b64encode(some_bytes).decode("ascii")
|
s = base64.urlsafe_b64encode(some_bytes).decode("ascii")
|
||||||
@@ -567,6 +571,19 @@ class Node:
|
|||||||
onResponse = self.onAckNak
|
onResponse = self.onAckNak
|
||||||
return self._sendAdmin(p, onResponse=onResponse)
|
return self._sendAdmin(p, onResponse=onResponse)
|
||||||
|
|
||||||
|
def enterDFUMode(self):
|
||||||
|
"""Tell the node to enter DFU mode (NRF52)."""
|
||||||
|
p = admin_pb2.AdminMessage()
|
||||||
|
p.enter_dfu_mode_request = True
|
||||||
|
logging.info(f"Telling node to enable DFU mode")
|
||||||
|
|
||||||
|
# If sending to a remote node, wait for ACK/NAK
|
||||||
|
if self == self.iface.localNode:
|
||||||
|
onResponse = None
|
||||||
|
else:
|
||||||
|
onResponse = self.onAckNak
|
||||||
|
return self._sendAdmin(p, onResponse=onResponse)
|
||||||
|
|
||||||
def shutdown(self, secs: int = 10):
|
def shutdown(self, secs: int = 10):
|
||||||
"""Tell the node to shutdown."""
|
"""Tell the node to shutdown."""
|
||||||
p = admin_pb2.AdminMessage()
|
p = admin_pb2.AdminMessage()
|
||||||
@@ -603,6 +620,23 @@ class Node:
|
|||||||
onResponse = self.onAckNak
|
onResponse = self.onAckNak
|
||||||
return self._sendAdmin(p, onResponse=onResponse)
|
return self._sendAdmin(p, onResponse=onResponse)
|
||||||
|
|
||||||
|
def removeNode(self, nodeId: Union[int, str]):
|
||||||
|
"""Tell the node to remove a specific node by ID"""
|
||||||
|
if isinstance(nodeId, str):
|
||||||
|
if nodeId.startswith("!"):
|
||||||
|
nodeId = int(nodeId[1:], 16)
|
||||||
|
else:
|
||||||
|
nodeId = int(nodeId)
|
||||||
|
|
||||||
|
p = admin_pb2.AdminMessage()
|
||||||
|
p.remove_by_nodenum = nodeId
|
||||||
|
|
||||||
|
if self == self.iface.localNode:
|
||||||
|
onResponse = None
|
||||||
|
else:
|
||||||
|
onResponse = self.onAckNak
|
||||||
|
return self._sendAdmin(p, onResponse=onResponse)
|
||||||
|
|
||||||
def resetNodeDb(self):
|
def resetNodeDb(self):
|
||||||
"""Tell the node to reset its list of nodes."""
|
"""Tell the node to reset its list of nodes."""
|
||||||
p = admin_pb2.AdminMessage()
|
p = admin_pb2.AdminMessage()
|
||||||
@@ -740,9 +774,9 @@ class Node:
|
|||||||
def _sendAdmin(
|
def _sendAdmin(
|
||||||
self,
|
self,
|
||||||
p: admin_pb2.AdminMessage,
|
p: admin_pb2.AdminMessage,
|
||||||
wantResponse=True,
|
wantResponse: bool=True,
|
||||||
onResponse=None,
|
onResponse=None,
|
||||||
adminIndex=0,
|
adminIndex: int=0,
|
||||||
):
|
):
|
||||||
"""Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
|
"""Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/telemetry.proto\x12\nmeshtastic\"i\n\rDeviceMetrics\x12\x15\n\rbattery_level\x18\x01 \x01(\r\x12\x0f\n\x07voltage\x18\x02 \x01(\x02\x12\x1b\n\x13\x63hannel_utilization\x18\x03 \x01(\x02\x12\x13\n\x0b\x61ir_util_tx\x18\x04 \x01(\x02\"\x9b\x01\n\x12\x45nvironmentMetrics\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x12\x16\n\x0egas_resistance\x18\x04 \x01(\x02\x12\x0f\n\x07voltage\x18\x05 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x02\"\x8c\x01\n\x0cPowerMetrics\x12\x13\n\x0b\x63h1_voltage\x18\x01 \x01(\x02\x12\x13\n\x0b\x63h1_current\x18\x02 \x01(\x02\x12\x13\n\x0b\x63h2_voltage\x18\x03 \x01(\x02\x12\x13\n\x0b\x63h2_current\x18\x04 \x01(\x02\x12\x13\n\x0b\x63h3_voltage\x18\x05 \x01(\x02\x12\x13\n\x0b\x63h3_current\x18\x06 \x01(\x02\"\xbf\x02\n\x11\x41irQualityMetrics\x12\x15\n\rpm10_standard\x18\x01 \x01(\r\x12\x15\n\rpm25_standard\x18\x02 \x01(\r\x12\x16\n\x0epm100_standard\x18\x03 \x01(\r\x12\x1a\n\x12pm10_environmental\x18\x04 \x01(\r\x12\x1a\n\x12pm25_environmental\x18\x05 \x01(\r\x12\x1b\n\x13pm100_environmental\x18\x06 \x01(\r\x12\x16\n\x0eparticles_03um\x18\x07 \x01(\r\x12\x16\n\x0eparticles_05um\x18\x08 \x01(\r\x12\x16\n\x0eparticles_10um\x18\t \x01(\r\x12\x16\n\x0eparticles_25um\x18\n \x01(\r\x12\x16\n\x0eparticles_50um\x18\x0b \x01(\r\x12\x17\n\x0fparticles_100um\x18\x0c \x01(\r\"\x89\x02\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12\x33\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\x19.meshtastic.DeviceMetricsH\x00\x12=\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\x1e.meshtastic.EnvironmentMetricsH\x00\x12<\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32\x1d.meshtastic.AirQualityMetricsH\x00\x12\x31\n\rpower_metrics\x18\x05 \x01(\x0b\x32\x18.meshtastic.PowerMetricsH\x00\x42\t\n\x07variant*\xe0\x01\n\x13TelemetrySensorType\x12\x10\n\x0cSENSOR_UNSET\x10\x00\x12\n\n\x06\x42ME280\x10\x01\x12\n\n\x06\x42ME680\x10\x02\x12\x0b\n\x07MCP9808\x10\x03\x12\n\n\x06INA260\x10\x04\x12\n\n\x06INA219\x10\x05\x12\n\n\x06\x42MP280\x10\x06\x12\t\n\x05SHTC3\x10\x07\x12\t\n\x05LPS22\x10\x08\x12\x0b\n\x07QMC6310\x10\t\x12\x0b\n\x07QMI8658\x10\n\x12\x0c\n\x08QMC5883L\x10\x0b\x12\t\n\x05SHT31\x10\x0c\x12\x0c\n\x08PMSA003I\x10\r\x12\x0b\n\x07INA3221\x10\x0e\x12\n\n\x06\x42MP085\x10\x0f\x42\x64\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/telemetry.proto\x12\nmeshtastic\"\x81\x01\n\rDeviceMetrics\x12\x15\n\rbattery_level\x18\x01 \x01(\r\x12\x0f\n\x07voltage\x18\x02 \x01(\x02\x12\x1b\n\x13\x63hannel_utilization\x18\x03 \x01(\x02\x12\x13\n\x0b\x61ir_util_tx\x18\x04 \x01(\x02\x12\x16\n\x0euptime_seconds\x18\x05 \x01(\r\"\xba\x01\n\x12\x45nvironmentMetrics\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x12\x16\n\x0egas_resistance\x18\x04 \x01(\x02\x12\x0f\n\x07voltage\x18\x05 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x02\x12\x0b\n\x03iaq\x18\x07 \x01(\r\x12\x10\n\x08\x64istance\x18\x08 \x01(\x02\"\x8c\x01\n\x0cPowerMetrics\x12\x13\n\x0b\x63h1_voltage\x18\x01 \x01(\x02\x12\x13\n\x0b\x63h1_current\x18\x02 \x01(\x02\x12\x13\n\x0b\x63h2_voltage\x18\x03 \x01(\x02\x12\x13\n\x0b\x63h2_current\x18\x04 \x01(\x02\x12\x13\n\x0b\x63h3_voltage\x18\x05 \x01(\x02\x12\x13\n\x0b\x63h3_current\x18\x06 \x01(\x02\"\xbf\x02\n\x11\x41irQualityMetrics\x12\x15\n\rpm10_standard\x18\x01 \x01(\r\x12\x15\n\rpm25_standard\x18\x02 \x01(\r\x12\x16\n\x0epm100_standard\x18\x03 \x01(\r\x12\x1a\n\x12pm10_environmental\x18\x04 \x01(\r\x12\x1a\n\x12pm25_environmental\x18\x05 \x01(\r\x12\x1b\n\x13pm100_environmental\x18\x06 \x01(\r\x12\x16\n\x0eparticles_03um\x18\x07 \x01(\r\x12\x16\n\x0eparticles_05um\x18\x08 \x01(\r\x12\x16\n\x0eparticles_10um\x18\t \x01(\r\x12\x16\n\x0eparticles_25um\x18\n \x01(\r\x12\x16\n\x0eparticles_50um\x18\x0b \x01(\r\x12\x17\n\x0fparticles_100um\x18\x0c \x01(\r\"\x89\x02\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12\x33\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\x19.meshtastic.DeviceMetricsH\x00\x12=\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\x1e.meshtastic.EnvironmentMetricsH\x00\x12<\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32\x1d.meshtastic.AirQualityMetricsH\x00\x12\x31\n\rpower_metrics\x18\x05 \x01(\x0b\x32\x18.meshtastic.PowerMetricsH\x00\x42\t\n\x07variant*\xee\x01\n\x13TelemetrySensorType\x12\x10\n\x0cSENSOR_UNSET\x10\x00\x12\n\n\x06\x42ME280\x10\x01\x12\n\n\x06\x42ME680\x10\x02\x12\x0b\n\x07MCP9808\x10\x03\x12\n\n\x06INA260\x10\x04\x12\n\n\x06INA219\x10\x05\x12\n\n\x06\x42MP280\x10\x06\x12\t\n\x05SHTC3\x10\x07\x12\t\n\x05LPS22\x10\x08\x12\x0b\n\x07QMC6310\x10\t\x12\x0b\n\x07QMI8658\x10\n\x12\x0c\n\x08QMC5883L\x10\x0b\x12\t\n\x05SHT31\x10\x0c\x12\x0c\n\x08PMSA003I\x10\r\x12\x0b\n\x07INA3221\x10\x0e\x12\n\n\x06\x42MP085\x10\x0f\x12\x0c\n\x08RCWL9620\x10\x10\x42\x64\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
||||||
|
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.telemetry_pb2', globals())
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.telemetry_pb2', globals())
|
||||||
@@ -21,16 +21,16 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|||||||
|
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017TelemetryProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017TelemetryProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_TELEMETRYSENSORTYPE._serialized_start=1041
|
_TELEMETRYSENSORTYPE._serialized_start=1097
|
||||||
_TELEMETRYSENSORTYPE._serialized_end=1265
|
_TELEMETRYSENSORTYPE._serialized_end=1335
|
||||||
_DEVICEMETRICS._serialized_start=42
|
_DEVICEMETRICS._serialized_start=43
|
||||||
_DEVICEMETRICS._serialized_end=147
|
_DEVICEMETRICS._serialized_end=172
|
||||||
_ENVIRONMENTMETRICS._serialized_start=150
|
_ENVIRONMENTMETRICS._serialized_start=175
|
||||||
_ENVIRONMENTMETRICS._serialized_end=305
|
_ENVIRONMENTMETRICS._serialized_end=361
|
||||||
_POWERMETRICS._serialized_start=308
|
_POWERMETRICS._serialized_start=364
|
||||||
_POWERMETRICS._serialized_end=448
|
_POWERMETRICS._serialized_end=504
|
||||||
_AIRQUALITYMETRICS._serialized_start=451
|
_AIRQUALITYMETRICS._serialized_start=507
|
||||||
_AIRQUALITYMETRICS._serialized_end=770
|
_AIRQUALITYMETRICS._serialized_end=826
|
||||||
_TELEMETRY._serialized_start=773
|
_TELEMETRY._serialized_start=829
|
||||||
_TELEMETRY._serialized_end=1038
|
_TELEMETRY._serialized_end=1094
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
|
|||||||
"""
|
"""
|
||||||
BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280)
|
BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280)
|
||||||
"""
|
"""
|
||||||
|
RCWL9620: _TelemetrySensorType.ValueType # 16
|
||||||
|
"""
|
||||||
|
RCWL-9620 Doppler Radar Distance Sensor, used for water level detection
|
||||||
|
"""
|
||||||
|
|
||||||
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -156,6 +160,10 @@ BMP085: TelemetrySensorType.ValueType # 15
|
|||||||
"""
|
"""
|
||||||
BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280)
|
BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280)
|
||||||
"""
|
"""
|
||||||
|
RCWL9620: TelemetrySensorType.ValueType # 16
|
||||||
|
"""
|
||||||
|
RCWL-9620 Doppler Radar Distance Sensor, used for water level detection
|
||||||
|
"""
|
||||||
global___TelemetrySensorType = TelemetrySensorType
|
global___TelemetrySensorType = TelemetrySensorType
|
||||||
|
|
||||||
@typing_extensions.final
|
@typing_extensions.final
|
||||||
@@ -170,6 +178,7 @@ class DeviceMetrics(google.protobuf.message.Message):
|
|||||||
VOLTAGE_FIELD_NUMBER: builtins.int
|
VOLTAGE_FIELD_NUMBER: builtins.int
|
||||||
CHANNEL_UTILIZATION_FIELD_NUMBER: builtins.int
|
CHANNEL_UTILIZATION_FIELD_NUMBER: builtins.int
|
||||||
AIR_UTIL_TX_FIELD_NUMBER: builtins.int
|
AIR_UTIL_TX_FIELD_NUMBER: builtins.int
|
||||||
|
UPTIME_SECONDS_FIELD_NUMBER: builtins.int
|
||||||
battery_level: builtins.int
|
battery_level: builtins.int
|
||||||
"""
|
"""
|
||||||
0-100 (>100 means powered)
|
0-100 (>100 means powered)
|
||||||
@@ -186,6 +195,10 @@ class DeviceMetrics(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Percent of airtime for transmission used within the last hour.
|
Percent of airtime for transmission used within the last hour.
|
||||||
"""
|
"""
|
||||||
|
uptime_seconds: builtins.int
|
||||||
|
"""
|
||||||
|
How long the device has been running since the last reboot (in seconds)
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -193,8 +206,9 @@ class DeviceMetrics(google.protobuf.message.Message):
|
|||||||
voltage: builtins.float = ...,
|
voltage: builtins.float = ...,
|
||||||
channel_utilization: builtins.float = ...,
|
channel_utilization: builtins.float = ...,
|
||||||
air_util_tx: builtins.float = ...,
|
air_util_tx: builtins.float = ...,
|
||||||
|
uptime_seconds: builtins.int = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing_extensions.Literal["air_util_tx", b"air_util_tx", "battery_level", b"battery_level", "channel_utilization", b"channel_utilization", "voltage", b"voltage"]) -> None: ...
|
def ClearField(self, field_name: typing_extensions.Literal["air_util_tx", b"air_util_tx", "battery_level", b"battery_level", "channel_utilization", b"channel_utilization", "uptime_seconds", b"uptime_seconds", "voltage", b"voltage"]) -> None: ...
|
||||||
|
|
||||||
global___DeviceMetrics = DeviceMetrics
|
global___DeviceMetrics = DeviceMetrics
|
||||||
|
|
||||||
@@ -212,6 +226,8 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
GAS_RESISTANCE_FIELD_NUMBER: builtins.int
|
GAS_RESISTANCE_FIELD_NUMBER: builtins.int
|
||||||
VOLTAGE_FIELD_NUMBER: builtins.int
|
VOLTAGE_FIELD_NUMBER: builtins.int
|
||||||
CURRENT_FIELD_NUMBER: builtins.int
|
CURRENT_FIELD_NUMBER: builtins.int
|
||||||
|
IAQ_FIELD_NUMBER: builtins.int
|
||||||
|
DISTANCE_FIELD_NUMBER: builtins.int
|
||||||
temperature: builtins.float
|
temperature: builtins.float
|
||||||
"""
|
"""
|
||||||
Temperature measured
|
Temperature measured
|
||||||
@@ -236,6 +252,15 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x)
|
Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x)
|
||||||
"""
|
"""
|
||||||
|
iaq: builtins.int
|
||||||
|
"""
|
||||||
|
relative scale IAQ value as measured by Bosch BME680 . value 0-500.
|
||||||
|
Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here.
|
||||||
|
"""
|
||||||
|
distance: builtins.float
|
||||||
|
"""
|
||||||
|
RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm.
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -245,8 +270,10 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
gas_resistance: builtins.float = ...,
|
gas_resistance: builtins.float = ...,
|
||||||
voltage: builtins.float = ...,
|
voltage: builtins.float = ...,
|
||||||
current: builtins.float = ...,
|
current: builtins.float = ...,
|
||||||
|
iaq: builtins.int = ...,
|
||||||
|
distance: builtins.float = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing_extensions.Literal["barometric_pressure", b"barometric_pressure", "current", b"current", "gas_resistance", b"gas_resistance", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "voltage", b"voltage"]) -> None: ...
|
def ClearField(self, field_name: typing_extensions.Literal["barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "voltage", b"voltage"]) -> None: ...
|
||||||
|
|
||||||
global___EnvironmentMetrics = EnvironmentMetrics
|
global___EnvironmentMetrics = EnvironmentMetrics
|
||||||
|
|
||||||
|
|||||||
@@ -5,18 +5,18 @@ from unittest.mock import MagicMock
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from meshtastic.__main__ import Globals
|
from meshtastic import mt_config
|
||||||
|
|
||||||
from ..mesh_interface import MeshInterface
|
from ..mesh_interface import MeshInterface
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def reset_globals():
|
def reset_mt_config():
|
||||||
"""Fixture to reset globals."""
|
"""Fixture to reset mt_config."""
|
||||||
parser = None
|
parser = None
|
||||||
parser = argparse.ArgumentParser(add_help=False)
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
Globals.getInstance().reset()
|
mt_config.reset()
|
||||||
Globals.getInstance().set_parser(parser)
|
mt_config.parser = parser
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
"""Meshtastic unit tests for globals.py
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from ..globals import Globals
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
|
||||||
def test_globals_get_instaance():
|
|
||||||
"""Test that we can instantiate a Globals instance"""
|
|
||||||
ourglobals = Globals.getInstance()
|
|
||||||
ourglobals2 = Globals.getInstance()
|
|
||||||
assert ourglobals == ourglobals2
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
|
||||||
def test_globals_there_can_be_only_one():
|
|
||||||
"""Test that we can cannot create two Globals instances"""
|
|
||||||
# if we have an instance, delete it
|
|
||||||
Globals.getInstance()
|
|
||||||
with pytest.raises(Exception) as pytest_wrapped_e:
|
|
||||||
# try to create another instance
|
|
||||||
Globals()
|
|
||||||
assert pytest_wrapped_e.type == Exception
|
|
||||||
@@ -6,9 +6,8 @@ from unittest.mock import MagicMock
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from meshtastic import _onNodeInfoReceive, _onPositionReceive, _onTextReceive
|
from meshtastic import _onNodeInfoReceive, _onPositionReceive, _onTextReceive, mt_config
|
||||||
|
|
||||||
from ..globals import Globals
|
|
||||||
from ..serial_interface import SerialInterface
|
from ..serial_interface import SerialInterface
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ from ..serial_interface import SerialInterface
|
|||||||
def test_init_onTextReceive_with_exception(caplog):
|
def test_init_onTextReceive_with_exception(caplog):
|
||||||
"""Test _onTextReceive"""
|
"""Test _onTextReceive"""
|
||||||
args = MagicMock()
|
args = MagicMock()
|
||||||
Globals.getInstance().set_args(args)
|
mt_config.args = args
|
||||||
iface = MagicMock(autospec=SerialInterface)
|
iface = MagicMock(autospec=SerialInterface)
|
||||||
packet = {}
|
packet = {}
|
||||||
with caplog.at_level(logging.DEBUG):
|
with caplog.at_level(logging.DEBUG):
|
||||||
@@ -29,7 +28,7 @@ def test_init_onTextReceive_with_exception(caplog):
|
|||||||
def test_init_onPositionReceive(caplog):
|
def test_init_onPositionReceive(caplog):
|
||||||
"""Test _onPositionReceive"""
|
"""Test _onPositionReceive"""
|
||||||
args = MagicMock()
|
args = MagicMock()
|
||||||
Globals.getInstance().set_args(args)
|
mt_config.args = args
|
||||||
iface = MagicMock(autospec=SerialInterface)
|
iface = MagicMock(autospec=SerialInterface)
|
||||||
packet = {"from": "foo", "decoded": {"position": {}}}
|
packet = {"from": "foo", "decoded": {"position": {}}}
|
||||||
with caplog.at_level(logging.DEBUG):
|
with caplog.at_level(logging.DEBUG):
|
||||||
@@ -41,7 +40,7 @@ def test_init_onPositionReceive(caplog):
|
|||||||
def test_init_onNodeInfoReceive(caplog, iface_with_nodes):
|
def test_init_onNodeInfoReceive(caplog, iface_with_nodes):
|
||||||
"""Test _onNodeInfoReceive"""
|
"""Test _onNodeInfoReceive"""
|
||||||
args = MagicMock()
|
args = MagicMock()
|
||||||
Globals.getInstance().set_args(args)
|
mt_config.args = args
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
iface.myInfo.my_node_num = 2475227164
|
iface.myInfo.my_node_num = 2475227164
|
||||||
packet = {
|
packet = {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from .. import mesh_pb2, BROADCAST_ADDR, LOCAL_ADDR
|
from .. import mesh_pb2, config_pb2, BROADCAST_ADDR, LOCAL_ADDR
|
||||||
from ..mesh_interface import MeshInterface
|
from ..mesh_interface import MeshInterface
|
||||||
from ..node import Node
|
from ..node import Node
|
||||||
|
|
||||||
@@ -16,16 +16,17 @@ from ..util import Timeout
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_MeshInterface(capsys):
|
def test_MeshInterface(capsys):
|
||||||
"""Test that we can instantiate a MeshInterface"""
|
"""Test that we can instantiate a MeshInterface"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
|
|
||||||
nodes = {
|
NODE_ID = "!9388f81c"
|
||||||
"!9388f81c": {
|
NODE_NUM = 2475227164
|
||||||
"num": 2475227164,
|
node = {
|
||||||
|
"num": NODE_NUM,
|
||||||
"user": {
|
"user": {
|
||||||
"id": "!9388f81c",
|
"id": NODE_ID,
|
||||||
"longName": "Unknown f81c",
|
"longName": "Unknown f81c",
|
||||||
"shortName": "?1C",
|
"shortName": "?1C",
|
||||||
"macaddr": "RBeTiPgc",
|
"macaddr": "RBeTiPgc",
|
||||||
@@ -34,14 +35,16 @@ def test_MeshInterface(capsys):
|
|||||||
"position": {},
|
"position": {},
|
||||||
"lastHeard": 1640204888,
|
"lastHeard": 1640204888,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
iface.nodesByNum = {2475227164: nodes["!9388f81c"]}
|
|
||||||
iface.nodes = nodes
|
iface.nodes = {NODE_ID: node}
|
||||||
|
iface.nodesByNum = {NODE_NUM: node}
|
||||||
|
|
||||||
myInfo = MagicMock()
|
myInfo = MagicMock()
|
||||||
iface.myInfo = myInfo
|
iface.myInfo = myInfo
|
||||||
|
|
||||||
|
iface.localNode.localConfig.lora.CopyFrom(config_pb2.Config.LoRaConfig())
|
||||||
|
|
||||||
iface.showInfo()
|
iface.showInfo()
|
||||||
iface.localNode.showInfo()
|
iface.localNode.showInfo()
|
||||||
iface.showNodes()
|
iface.showNodes()
|
||||||
@@ -57,7 +60,7 @@ def test_MeshInterface(capsys):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_getMyUser(iface_with_nodes):
|
def test_getMyUser(iface_with_nodes):
|
||||||
"""Test getMyUser()"""
|
"""Test getMyUser()"""
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
@@ -68,7 +71,7 @@ def test_getMyUser(iface_with_nodes):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_getLongName(iface_with_nodes):
|
def test_getLongName(iface_with_nodes):
|
||||||
"""Test getLongName()"""
|
"""Test getLongName()"""
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
@@ -78,7 +81,7 @@ def test_getLongName(iface_with_nodes):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_getShortName(iface_with_nodes):
|
def test_getShortName(iface_with_nodes):
|
||||||
"""Test getShortName()."""
|
"""Test getShortName()."""
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
@@ -88,7 +91,7 @@ def test_getShortName(iface_with_nodes):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_handlePacketFromRadio_no_from(capsys):
|
def test_handlePacketFromRadio_no_from(capsys):
|
||||||
"""Test _handlePacketFromRadio with no 'from' in the mesh packet."""
|
"""Test _handlePacketFromRadio with no 'from' in the mesh packet."""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -100,7 +103,7 @@ def test_handlePacketFromRadio_no_from(capsys):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_handlePacketFromRadio_with_a_portnum(caplog):
|
def test_handlePacketFromRadio_with_a_portnum(caplog):
|
||||||
"""Test _handlePacketFromRadio with a portnum
|
"""Test _handlePacketFromRadio with a portnum
|
||||||
Since we have an attribute called 'from', we cannot simply 'set' it.
|
Since we have an attribute called 'from', we cannot simply 'set' it.
|
||||||
@@ -116,7 +119,7 @@ def test_handlePacketFromRadio_with_a_portnum(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_handlePacketFromRadio_no_portnum(caplog):
|
def test_handlePacketFromRadio_no_portnum(caplog):
|
||||||
"""Test _handlePacketFromRadio without a portnum"""
|
"""Test _handlePacketFromRadio without a portnum"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -128,7 +131,7 @@ def test_handlePacketFromRadio_no_portnum(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_getNode_with_local():
|
def test_getNode_with_local():
|
||||||
"""Test getNode"""
|
"""Test getNode"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -137,7 +140,7 @@ def test_getNode_with_local():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_getNode_not_local(caplog):
|
def test_getNode_not_local(caplog):
|
||||||
"""Test getNode not local"""
|
"""Test getNode not local"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -150,7 +153,7 @@ def test_getNode_not_local(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_getNode_not_local_timeout(capsys):
|
def test_getNode_not_local_timeout(capsys):
|
||||||
"""Test getNode not local, simulate timeout"""
|
"""Test getNode not local, simulate timeout"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -167,7 +170,7 @@ def test_getNode_not_local_timeout(capsys):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendPosition(caplog):
|
def test_sendPosition(caplog):
|
||||||
"""Test sendPosition"""
|
"""Test sendPosition"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -179,7 +182,7 @@ def test_sendPosition(caplog):
|
|||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# @pytest.mark.unit
|
# @pytest.mark.unit
|
||||||
# @pytest.mark.usefixtures("reset_globals")
|
# @pytest.mark.usefixtures("reset_mt_config")
|
||||||
# def test_close_with_heartbeatTimer(caplog):
|
# def test_close_with_heartbeatTimer(caplog):
|
||||||
# """Test close() with heartbeatTimer"""
|
# """Test close() with heartbeatTimer"""
|
||||||
# iface = MeshInterface(noProto=True)
|
# iface = MeshInterface(noProto=True)
|
||||||
@@ -197,7 +200,7 @@ def test_sendPosition(caplog):
|
|||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# @pytest.mark.unit
|
# @pytest.mark.unit
|
||||||
# @pytest.mark.usefixtures("reset_globals")
|
# @pytest.mark.usefixtures("reset_mt_config")
|
||||||
# def test_handleFromRadio_empty_payload(caplog):
|
# def test_handleFromRadio_empty_payload(caplog):
|
||||||
# """Test _handleFromRadio"""
|
# """Test _handleFromRadio"""
|
||||||
# iface = MeshInterface(noProto=True)
|
# iface = MeshInterface(noProto=True)
|
||||||
@@ -208,7 +211,7 @@ def test_sendPosition(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_handleFromRadio_with_my_info(caplog):
|
def test_handleFromRadio_with_my_info(caplog):
|
||||||
"""Test _handleFromRadio with my_info"""
|
"""Test _handleFromRadio with my_info"""
|
||||||
# Note: I captured the '--debug --info' for the bytes below.
|
# Note: I captured the '--debug --info' for the bytes below.
|
||||||
@@ -233,7 +236,7 @@ def test_handleFromRadio_with_my_info(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_handleFromRadio_with_node_info(caplog, capsys):
|
def test_handleFromRadio_with_node_info(caplog, capsys):
|
||||||
"""Test _handleFromRadio with node_info"""
|
"""Test _handleFromRadio with node_info"""
|
||||||
# Note: I captured the '--debug --info' for the bytes below.
|
# Note: I captured the '--debug --info' for the bytes below.
|
||||||
@@ -269,7 +272,7 @@ def test_handleFromRadio_with_node_info(caplog, capsys):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_handleFromRadio_with_node_info_tbeam1(caplog, capsys):
|
def test_handleFromRadio_with_node_info_tbeam1(caplog, capsys):
|
||||||
"""Test _handleFromRadio with node_info"""
|
"""Test _handleFromRadio with node_info"""
|
||||||
# Note: Captured the '--debug --info' for the bytes below.
|
# Note: Captured the '--debug --info' for the bytes below.
|
||||||
@@ -293,7 +296,7 @@ def test_handleFromRadio_with_node_info_tbeam1(caplog, capsys):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_handleFromRadio_with_node_info_tbeam_with_bad_data(caplog):
|
def test_handleFromRadio_with_node_info_tbeam_with_bad_data(caplog):
|
||||||
"""Test _handleFromRadio with node_info with some bad data (issue#172) - ensure we do not throw exception"""
|
"""Test _handleFromRadio with node_info with some bad data (issue#172) - ensure we do not throw exception"""
|
||||||
# Note: Captured the '--debug --info' for the bytes below.
|
# Note: Captured the '--debug --info' for the bytes below.
|
||||||
@@ -305,7 +308,7 @@ def test_handleFromRadio_with_node_info_tbeam_with_bad_data(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_MeshInterface_sendToRadioImpl(caplog):
|
def test_MeshInterface_sendToRadioImpl(caplog):
|
||||||
"""Test _sendToRadioImp()"""
|
"""Test _sendToRadioImp()"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -316,7 +319,7 @@ def test_MeshInterface_sendToRadioImpl(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_MeshInterface_sendToRadio_no_proto(caplog):
|
def test_MeshInterface_sendToRadio_no_proto(caplog):
|
||||||
"""Test sendToRadio()"""
|
"""Test sendToRadio()"""
|
||||||
iface = MeshInterface()
|
iface = MeshInterface()
|
||||||
@@ -327,7 +330,7 @@ def test_MeshInterface_sendToRadio_no_proto(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendData_too_long(caplog):
|
def test_sendData_too_long(caplog):
|
||||||
"""Test when data payload is too big"""
|
"""Test when data payload is too big"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -352,7 +355,7 @@ def test_sendData_too_long(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendData_unknown_app(capsys):
|
def test_sendData_unknown_app(capsys):
|
||||||
"""Test sendData when unknown app"""
|
"""Test sendData when unknown app"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -366,7 +369,7 @@ def test_sendData_unknown_app(capsys):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendPosition_with_a_position(caplog):
|
def test_sendPosition_with_a_position(caplog):
|
||||||
"""Test sendPosition when lat/long/alt"""
|
"""Test sendPosition when lat/long/alt"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -378,7 +381,7 @@ def test_sendPosition_with_a_position(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendPacket_with_no_destination(capsys):
|
def test_sendPacket_with_no_destination(capsys):
|
||||||
"""Test _sendPacket()"""
|
"""Test _sendPacket()"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -392,7 +395,7 @@ def test_sendPacket_with_no_destination(capsys):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendPacket_with_destination_as_int(caplog):
|
def test_sendPacket_with_destination_as_int(caplog):
|
||||||
"""Test _sendPacket() with int as a destination"""
|
"""Test _sendPacket() with int as a destination"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -403,7 +406,7 @@ def test_sendPacket_with_destination_as_int(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendPacket_with_destination_starting_with_a_bang(caplog):
|
def test_sendPacket_with_destination_starting_with_a_bang(caplog):
|
||||||
"""Test _sendPacket() with int as a destination"""
|
"""Test _sendPacket() with int as a destination"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -414,7 +417,7 @@ def test_sendPacket_with_destination_starting_with_a_bang(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog):
|
def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog):
|
||||||
"""Test _sendPacket() with BROADCAST_ADDR as a destination"""
|
"""Test _sendPacket() with BROADCAST_ADDR as a destination"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -425,7 +428,7 @@ def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys):
|
def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys):
|
||||||
"""Test _sendPacket() with LOCAL_ADDR as a destination with no myInfo"""
|
"""Test _sendPacket() with LOCAL_ADDR as a destination with no myInfo"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -440,7 +443,7 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog):
|
def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog):
|
||||||
"""Test _sendPacket() with LOCAL_ADDR as a destination with myInfo"""
|
"""Test _sendPacket() with LOCAL_ADDR as a destination with myInfo"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -454,7 +457,7 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendPacket_with_destination_is_blank_with_nodes(capsys, iface_with_nodes):
|
def test_sendPacket_with_destination_is_blank_with_nodes(capsys, iface_with_nodes):
|
||||||
"""Test _sendPacket() with '' as a destination with myInfo"""
|
"""Test _sendPacket() with '' as a destination with myInfo"""
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
@@ -469,7 +472,7 @@ def test_sendPacket_with_destination_is_blank_with_nodes(capsys, iface_with_node
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_sendPacket_with_destination_is_blank_without_nodes(caplog, iface_with_nodes):
|
def test_sendPacket_with_destination_is_blank_without_nodes(caplog, iface_with_nodes):
|
||||||
"""Test _sendPacket() with '' as a destination with myInfo"""
|
"""Test _sendPacket() with '' as a destination with myInfo"""
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
@@ -481,7 +484,7 @@ def test_sendPacket_with_destination_is_blank_without_nodes(caplog, iface_with_n
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_getMyNodeInfo():
|
def test_getMyNodeInfo():
|
||||||
"""Test getMyNodeInfo()"""
|
"""Test getMyNodeInfo()"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -496,7 +499,7 @@ def test_getMyNodeInfo():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_generatePacketId(capsys):
|
def test_generatePacketId(capsys):
|
||||||
"""Test _generatePacketId() when no currentPacketId (not connected)"""
|
"""Test _generatePacketId() when no currentPacketId (not connected)"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -514,7 +517,7 @@ def test_generatePacketId(capsys):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_fixupPosition_empty_pos():
|
def test_fixupPosition_empty_pos():
|
||||||
"""Test _fixupPosition()"""
|
"""Test _fixupPosition()"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -524,7 +527,7 @@ def test_fixupPosition_empty_pos():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_fixupPosition_no_changes_needed():
|
def test_fixupPosition_no_changes_needed():
|
||||||
"""Test _fixupPosition()"""
|
"""Test _fixupPosition()"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -534,7 +537,7 @@ def test_fixupPosition_no_changes_needed():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_fixupPosition():
|
def test_fixupPosition():
|
||||||
"""Test _fixupPosition()"""
|
"""Test _fixupPosition()"""
|
||||||
iface = MeshInterface(noProto=True)
|
iface = MeshInterface(noProto=True)
|
||||||
@@ -549,7 +552,7 @@ def test_fixupPosition():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_nodeNumToId(iface_with_nodes):
|
def test_nodeNumToId(iface_with_nodes):
|
||||||
"""Test _nodeNumToId()"""
|
"""Test _nodeNumToId()"""
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
@@ -559,7 +562,7 @@ def test_nodeNumToId(iface_with_nodes):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_nodeNumToId_not_found(iface_with_nodes):
|
def test_nodeNumToId_not_found(iface_with_nodes):
|
||||||
"""Test _nodeNumToId()"""
|
"""Test _nodeNumToId()"""
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
@@ -569,7 +572,7 @@ def test_nodeNumToId_not_found(iface_with_nodes):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_nodeNumToId_to_all(iface_with_nodes):
|
def test_nodeNumToId_to_all(iface_with_nodes):
|
||||||
"""Test _nodeNumToId()"""
|
"""Test _nodeNumToId()"""
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
@@ -579,7 +582,7 @@ def test_nodeNumToId_to_all(iface_with_nodes):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_getOrCreateByNum_minimal(iface_with_nodes):
|
def test_getOrCreateByNum_minimal(iface_with_nodes):
|
||||||
"""Test _getOrCreateByNum()"""
|
"""Test _getOrCreateByNum()"""
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
@@ -589,7 +592,7 @@ def test_getOrCreateByNum_minimal(iface_with_nodes):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_getOrCreateByNum_not_found(iface_with_nodes):
|
def test_getOrCreateByNum_not_found(iface_with_nodes):
|
||||||
"""Test _getOrCreateByNum()"""
|
"""Test _getOrCreateByNum()"""
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
@@ -600,7 +603,7 @@ def test_getOrCreateByNum_not_found(iface_with_nodes):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_getOrCreateByNum(iface_with_nodes):
|
def test_getOrCreateByNum(iface_with_nodes):
|
||||||
"""Test _getOrCreateByNum()"""
|
"""Test _getOrCreateByNum()"""
|
||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
# from ..admin_pb2 import AdminMessage
|
from .. import localonly_pb2, config_pb2
|
||||||
from ..channel_pb2 import Channel # pylint: disable=E0611
|
from ..channel_pb2 import Channel # pylint: disable=E0611
|
||||||
from ..node import Node
|
from ..node import Node
|
||||||
from ..serial_interface import SerialInterface
|
from ..serial_interface import SerialInterface
|
||||||
@@ -19,21 +19,26 @@ from ..mesh_interface import MeshInterface
|
|||||||
# from ..util import Timeout
|
# from ..util import Timeout
|
||||||
|
|
||||||
|
|
||||||
# TODO
|
@pytest.mark.unit
|
||||||
# @pytest.mark.unit
|
def test_node(capsys):
|
||||||
# def test_node(capsys):
|
"""Test that we can instantiate a Node"""
|
||||||
# """Test that we can instantiate a Node"""
|
iface = MagicMock(autospec=SerialInterface)
|
||||||
# anode = Node('foo', 'bar')
|
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||||
# radioConfig = RadioConfig()
|
mo.localNode.getChannelByName.return_value = None
|
||||||
# anode.radioConfig = radioConfig
|
mo.myInfo.max_channels = 8
|
||||||
# anode.showChannels()
|
anode = Node(mo, "bar", noProto=True)
|
||||||
# anode.showInfo()
|
lc = localonly_pb2.LocalConfig()
|
||||||
# out, err = capsys.readouterr()
|
anode.localConfig = lc
|
||||||
# assert re.search(r'Preferences', out)
|
lc.lora.CopyFrom(config_pb2.Config.LoRaConfig())
|
||||||
# assert re.search(r'Channels', out)
|
anode.moduleConfig = localonly_pb2.LocalModuleConfig()
|
||||||
# assert re.search(r'Primary channel URL', out)
|
anode.showInfo()
|
||||||
# assert err == ''
|
out, err = capsys.readouterr()
|
||||||
|
assert re.search(r'Preferences', out)
|
||||||
|
assert re.search(r'Module preferences', out)
|
||||||
|
assert re.search(r'Channels', out)
|
||||||
|
assert re.search(r'Primary channel URL', out)
|
||||||
|
assert not re.search(r'remote node', out)
|
||||||
|
assert err == ''
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# @pytest.mark.unit
|
# @pytest.mark.unit
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from unittest.mock import mock_open, patch
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ..serial_interface import SerialInterface
|
from ..serial_interface import SerialInterface
|
||||||
|
from .. import config_pb2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@@ -20,6 +21,7 @@ def test_SerialInterface_single_port(
|
|||||||
):
|
):
|
||||||
"""Test that we can instantiate a SerialInterface with a single port"""
|
"""Test that we can instantiate a SerialInterface with a single port"""
|
||||||
iface = SerialInterface(noProto=True)
|
iface = SerialInterface(noProto=True)
|
||||||
|
iface.localNode.localConfig.lora.CopyFrom(config_pb2.Config.LoRaConfig())
|
||||||
iface.showInfo()
|
iface.showInfo()
|
||||||
iface.localNode.showInfo()
|
iface.localNode.showInfo()
|
||||||
iface.close()
|
iface.close()
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ def test_StreamInterface():
|
|||||||
|
|
||||||
# Note: This takes a bit, so moving from unit to slow
|
# Note: This takes a bit, so moving from unit to slow
|
||||||
@pytest.mark.unitslow
|
@pytest.mark.unitslow
|
||||||
@pytest.mark.usefixtures("reset_globals")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_StreamInterface_with_noProto(caplog):
|
def test_StreamInterface_with_noProto(caplog):
|
||||||
"""Test that we can instantiate a StreamInterface based on nonProto
|
"""Test that we can instantiate a StreamInterface based on nonProto
|
||||||
and we can read/write bytes from a mocked stream
|
and we can read/write bytes from a mocked stream
|
||||||
@@ -41,7 +41,7 @@ def test_StreamInterface_with_noProto(caplog):
|
|||||||
### Tip: If you want to see the print output, run with '-s' flag:
|
### Tip: If you want to see the print output, run with '-s' flag:
|
||||||
### pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl
|
### pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl
|
||||||
# @pytest.mark.unitslow
|
# @pytest.mark.unitslow
|
||||||
# @pytest.mark.usefixtures("reset_globals")
|
# @pytest.mark.usefixtures("reset_mt_config")
|
||||||
# def test_sendToRadioImpl(caplog):
|
# def test_sendToRadioImpl(caplog):
|
||||||
# """Test _sendToRadioImpl()"""
|
# """Test _sendToRadioImpl()"""
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from .. import config_pb2
|
||||||
from ..tcp_interface import TCPInterface
|
from ..tcp_interface import TCPInterface
|
||||||
|
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ def test_TCPInterface(capsys):
|
|||||||
"""Test that we can instantiate a TCPInterface"""
|
"""Test that we can instantiate a TCPInterface"""
|
||||||
with patch("socket.socket") as mock_socket:
|
with patch("socket.socket") as mock_socket:
|
||||||
iface = TCPInterface(hostname="localhost", noProto=True)
|
iface = TCPInterface(hostname="localhost", noProto=True)
|
||||||
|
iface.localNode.localConfig.lora.CopyFrom(config_pb2.Config.LoRaConfig())
|
||||||
iface.myConnect()
|
iface.myConnect()
|
||||||
iface.showInfo()
|
iface.showInfo()
|
||||||
iface.localNode.showInfo()
|
iface.localNode.showInfo()
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"""Meshtastic unit tests for tunnel.py"""
|
"""Meshtastic unit tests for tunnel.py"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
@@ -7,7 +6,8 @@ from unittest.mock import MagicMock, patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ..globals import Globals
|
from meshtastic import mt_config
|
||||||
|
|
||||||
from ..tcp_interface import TCPInterface
|
from ..tcp_interface import TCPInterface
|
||||||
from ..tunnel import Tunnel, onTunnelReceive
|
from ..tunnel import Tunnel, onTunnelReceive
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ def test_Tunnel_with_interface(mock_platform_system, caplog, iface_with_nodes):
|
|||||||
with caplog.at_level(logging.WARNING):
|
with caplog.at_level(logging.WARNING):
|
||||||
with patch("socket.socket"):
|
with patch("socket.socket"):
|
||||||
tun = Tunnel(iface)
|
tun = Tunnel(iface)
|
||||||
assert tun == Globals.getInstance().get_tunnelInstance()
|
assert tun == mt_config.tunnelInstance
|
||||||
iface.close()
|
iface.close()
|
||||||
assert re.search(r"Not creating a TapDevice()", caplog.text, re.MULTILINE)
|
assert re.search(r"Not creating a TapDevice()", caplog.text, re.MULTILINE)
|
||||||
assert re.search(r"Not starting TUN reader", caplog.text, re.MULTILINE)
|
assert re.search(r"Not starting TUN reader", caplog.text, re.MULTILINE)
|
||||||
@@ -65,7 +65,7 @@ def test_onTunnelReceive_from_ourselves(mock_platform_system, caplog, iface_with
|
|||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
iface.myInfo.my_node_num = 2475227164
|
iface.myInfo.my_node_num = 2475227164
|
||||||
sys.argv = [""]
|
sys.argv = [""]
|
||||||
Globals.getInstance().set_args(sys.argv)
|
mt_config.args = sys.argv
|
||||||
packet = {"decoded": {"payload": "foo"}, "from": 2475227164}
|
packet = {"decoded": {"payload": "foo"}, "from": 2475227164}
|
||||||
a_mock = MagicMock()
|
a_mock = MagicMock()
|
||||||
a_mock.return_value = "Linux"
|
a_mock.return_value = "Linux"
|
||||||
@@ -73,7 +73,7 @@ def test_onTunnelReceive_from_ourselves(mock_platform_system, caplog, iface_with
|
|||||||
with caplog.at_level(logging.DEBUG):
|
with caplog.at_level(logging.DEBUG):
|
||||||
with patch("socket.socket"):
|
with patch("socket.socket"):
|
||||||
tun = Tunnel(iface)
|
tun = Tunnel(iface)
|
||||||
Globals.getInstance().set_tunnelInstance(tun)
|
mt_config.tunnelInstance = tun
|
||||||
onTunnelReceive(packet, iface)
|
onTunnelReceive(packet, iface)
|
||||||
assert re.search(r"in onTunnelReceive", caplog.text, re.MULTILINE)
|
assert re.search(r"in onTunnelReceive", caplog.text, re.MULTILINE)
|
||||||
assert re.search(r"Ignoring message we sent", caplog.text, re.MULTILINE)
|
assert re.search(r"Ignoring message we sent", caplog.text, re.MULTILINE)
|
||||||
@@ -88,7 +88,7 @@ def test_onTunnelReceive_from_someone_else(
|
|||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
iface.myInfo.my_node_num = 2475227164
|
iface.myInfo.my_node_num = 2475227164
|
||||||
sys.argv = [""]
|
sys.argv = [""]
|
||||||
Globals.getInstance().set_args(sys.argv)
|
mt_config.args = sys.argv
|
||||||
packet = {"decoded": {"payload": "foo"}, "from": 123}
|
packet = {"decoded": {"payload": "foo"}, "from": 123}
|
||||||
a_mock = MagicMock()
|
a_mock = MagicMock()
|
||||||
a_mock.return_value = "Linux"
|
a_mock.return_value = "Linux"
|
||||||
@@ -96,7 +96,7 @@ def test_onTunnelReceive_from_someone_else(
|
|||||||
with caplog.at_level(logging.DEBUG):
|
with caplog.at_level(logging.DEBUG):
|
||||||
with patch("socket.socket"):
|
with patch("socket.socket"):
|
||||||
tun = Tunnel(iface)
|
tun = Tunnel(iface)
|
||||||
Globals.getInstance().set_tunnelInstance(tun)
|
mt_config.tunnelInstance = tun
|
||||||
onTunnelReceive(packet, iface)
|
onTunnelReceive(packet, iface)
|
||||||
assert re.search(r"in onTunnelReceive", caplog.text, re.MULTILINE)
|
assert re.search(r"in onTunnelReceive", caplog.text, re.MULTILINE)
|
||||||
|
|
||||||
|
|||||||
@@ -22,16 +22,14 @@ import threading
|
|||||||
from pubsub import pub # type: ignore[import-untyped]
|
from pubsub import pub # type: ignore[import-untyped]
|
||||||
from pytap2 import TapDevice
|
from pytap2 import TapDevice
|
||||||
|
|
||||||
from meshtastic import portnums_pb2
|
from meshtastic import portnums_pb2, mt_config
|
||||||
from meshtastic.globals import Globals
|
|
||||||
from meshtastic.util import ipstr, readnet_u16
|
from meshtastic.util import ipstr, readnet_u16
|
||||||
|
|
||||||
|
|
||||||
def onTunnelReceive(packet, interface): # pylint: disable=W0613
|
def onTunnelReceive(packet, interface): # pylint: disable=W0613
|
||||||
"""Callback for received tunneled messages from mesh."""
|
"""Callback for received tunneled messages from mesh."""
|
||||||
logging.debug(f"in onTunnelReceive()")
|
logging.debug(f"in onTunnelReceive()")
|
||||||
our_globals = Globals.getInstance()
|
tunnelInstance = mt_config.tunnelInstance
|
||||||
tunnelInstance = our_globals.get_tunnelInstance()
|
|
||||||
tunnelInstance.onReceive(packet)
|
tunnelInstance.onReceive(packet)
|
||||||
|
|
||||||
|
|
||||||
@@ -67,8 +65,7 @@ class Tunnel:
|
|||||||
if platform.system() != "Linux":
|
if platform.system() != "Linux":
|
||||||
raise Tunnel.TunnelError("Tunnel() can only be run instantiated on a Linux system")
|
raise Tunnel.TunnelError("Tunnel() can only be run instantiated on a Linux system")
|
||||||
|
|
||||||
our_globals = Globals.getInstance()
|
mt_config.tunnelInstance = self
|
||||||
our_globals.set_tunnelInstance(self)
|
|
||||||
|
|
||||||
"""A list of chatty UDP services we should never accidentally
|
"""A list of chatty UDP services we should never accidentally
|
||||||
forward to our slow network"""
|
forward to our slow network"""
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
from google.protobuf.json_format import MessageToJson
|
from google.protobuf.json_format import MessageToJson
|
||||||
|
|
||||||
import packaging.version as pkg_version
|
import packaging.version as pkg_version
|
||||||
@@ -153,16 +155,16 @@ class dotdict(dict):
|
|||||||
class Timeout:
|
class Timeout:
|
||||||
"""Timeout class"""
|
"""Timeout class"""
|
||||||
|
|
||||||
def __init__(self, maxSecs=20):
|
def __init__(self, maxSecs: int=20):
|
||||||
self.expireTime = 0
|
self.expireTime: Union[int, float] = 0
|
||||||
self.sleepInterval = 0.1
|
self.sleepInterval: float = 0.1
|
||||||
self.expireTimeout = maxSecs
|
self.expireTimeout: int = maxSecs
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""Restart the waitForSet timer"""
|
"""Restart the waitForSet timer"""
|
||||||
self.expireTime = time.time() + self.expireTimeout
|
self.expireTime = time.time() + self.expireTimeout
|
||||||
|
|
||||||
def waitForSet(self, target, attrs=()):
|
def waitForSet(self, target, attrs=()) -> bool:
|
||||||
"""Block until the specified attributes are set. Returns True if config has been received."""
|
"""Block until the specified attributes are set. Returns True if config has been received."""
|
||||||
self.reset()
|
self.reset()
|
||||||
while time.time() < self.expireTime:
|
while time.time() < self.expireTime:
|
||||||
@@ -173,7 +175,7 @@ class Timeout:
|
|||||||
|
|
||||||
def waitForAckNak(
|
def waitForAckNak(
|
||||||
self, acknowledgment, attrs=("receivedAck", "receivedNak", "receivedImplAck")
|
self, acknowledgment, attrs=("receivedAck", "receivedNak", "receivedImplAck")
|
||||||
):
|
) -> bool:
|
||||||
"""Block until an ACK or NAK has been received. Returns True if ACK or NAK has been received."""
|
"""Block until an ACK or NAK has been received. Returns True if ACK or NAK has been received."""
|
||||||
self.reset()
|
self.reset()
|
||||||
while time.time() < self.expireTime:
|
while time.time() < self.expireTime:
|
||||||
@@ -183,7 +185,7 @@ class Timeout:
|
|||||||
time.sleep(self.sleepInterval)
|
time.sleep(self.sleepInterval)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute"):
|
def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute") -> bool:
|
||||||
"""Block until traceroute response is received. Returns True if traceroute response has been received."""
|
"""Block until traceroute response is received. Returns True if traceroute response has been received."""
|
||||||
self.expireTimeout *= waitFactor
|
self.expireTimeout *= waitFactor
|
||||||
self.reset()
|
self.reset()
|
||||||
@@ -194,7 +196,7 @@ class Timeout:
|
|||||||
time.sleep(self.sleepInterval)
|
time.sleep(self.sleepInterval)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def waitForTelemetry(self, acknowledgment):
|
def waitForTelemetry(self, acknowledgment) -> bool:
|
||||||
"""Block until telemetry response is received. Returns True if telemetry response has been received."""
|
"""Block until telemetry response is received. Returns True if telemetry response has been received."""
|
||||||
self.reset()
|
self.reset()
|
||||||
while time.time() < self.expireTime:
|
while time.time() < self.expireTime:
|
||||||
@@ -204,6 +206,16 @@ class Timeout:
|
|||||||
time.sleep(self.sleepInterval)
|
time.sleep(self.sleepInterval)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def waitForPosition(self, acknowledgment) -> bool:
|
||||||
|
"""Block until position response is received. Returns True if position response has been received."""
|
||||||
|
self.reset()
|
||||||
|
while time.time() < self.expireTime:
|
||||||
|
if getattr(acknowledgment, "receivedPosition", None):
|
||||||
|
acknowledgment.reset()
|
||||||
|
return True
|
||||||
|
time.sleep(self.sleepInterval)
|
||||||
|
return False
|
||||||
|
|
||||||
class Acknowledgment:
|
class Acknowledgment:
|
||||||
"A class that records which type of acknowledgment was just received, if any."
|
"A class that records which type of acknowledgment was just received, if any."
|
||||||
|
|
||||||
@@ -214,6 +226,7 @@ class Acknowledgment:
|
|||||||
self.receivedImplAck = False
|
self.receivedImplAck = False
|
||||||
self.receivedTraceRoute = False
|
self.receivedTraceRoute = False
|
||||||
self.receivedTelemetry = False
|
self.receivedTelemetry = False
|
||||||
|
self.receivedPosition = False
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""reset"""
|
"""reset"""
|
||||||
@@ -222,6 +235,7 @@ class Acknowledgment:
|
|||||||
self.receivedImplAck = False
|
self.receivedImplAck = False
|
||||||
self.receivedTraceRoute = False
|
self.receivedTraceRoute = False
|
||||||
self.receivedTelemetry = False
|
self.receivedTelemetry = False
|
||||||
|
self.receivedPosition = False
|
||||||
|
|
||||||
|
|
||||||
class DeferredExecution:
|
class DeferredExecution:
|
||||||
|
|||||||
Submodule protobufs updated: 68720ed8db...86640f20db
@@ -1,6 +1,6 @@
|
|||||||
markdown
|
markdown
|
||||||
pyserial
|
pyserial
|
||||||
protobuf
|
protobuf>=5.26.0
|
||||||
dotmap
|
dotmap
|
||||||
pexpect
|
pexpect
|
||||||
pyqrcode
|
pyqrcode
|
||||||
|
|||||||
4
setup.py
4
setup.py
@@ -13,7 +13,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="2.3.3",
|
version="2.3.6",
|
||||||
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",
|
||||||
@@ -34,7 +34,7 @@ setup(
|
|||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"pyserial>=3.4",
|
"pyserial>=3.4",
|
||||||
"protobuf>=3.13.0",
|
"protobuf>=5.26.0",
|
||||||
"requests>=2.25.0",
|
"requests>=2.25.0",
|
||||||
"pypubsub>=4.0.3",
|
"pypubsub>=4.0.3",
|
||||||
"dotmap>=1.3.14",
|
"dotmap>=1.3.14",
|
||||||
|
|||||||
Reference in New Issue
Block a user