mirror of
https://github.com/meshtastic/python.git
synced 2025-12-31 11:57:57 -05:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c703aff1d | ||
|
|
5441266565 | ||
|
|
890557fa5d | ||
|
|
e27d210a71 | ||
|
|
16c08b8b47 | ||
|
|
ebd3c7f5e8 | ||
|
|
da0312a5b0 | ||
|
|
919ae8c40f | ||
|
|
dd4fccbc77 | ||
|
|
32682b5230 | ||
|
|
9dab76bb64 | ||
|
|
e6d61c6603 | ||
|
|
ee857c5128 | ||
|
|
87a4bb0888 | ||
|
|
d72cc0e201 | ||
|
|
b350b9eab9 | ||
|
|
dc112f2f3a | ||
|
|
14ae4eeac1 | ||
|
|
bbc526d0a8 | ||
|
|
abe98f5079 | ||
|
|
e8dfee8454 | ||
|
|
1746ad15d7 | ||
|
|
4d67e7fc76 | ||
|
|
3b112d2f49 | ||
|
|
93e9c1c66c | ||
|
|
8e641b3186 | ||
|
|
ed545cd9b4 | ||
|
|
bcd60c9ef7 | ||
|
|
c3d044e3f2 | ||
|
|
8d538e8f24 | ||
|
|
fa1a3d7901 | ||
|
|
3adeb925dd | ||
|
|
9de04a12e1 | ||
|
|
e01a1bb6e0 | ||
|
|
1ebea53703 | ||
|
|
ba2d6c9d93 | ||
|
|
fef0e1b77f | ||
|
|
dc1dec17b5 | ||
|
|
02ed17837b | ||
|
|
8cf996a242 | ||
|
|
02189aae07 | ||
|
|
894581ce00 | ||
|
|
fb8db01427 | ||
|
|
fa5ede93ed | ||
|
|
022a8a1017 | ||
|
|
21f6e25ab0 | ||
|
|
243e297505 | ||
|
|
9fc86f9450 | ||
|
|
e5999f5be6 | ||
|
|
3886bc16d7 | ||
|
|
7fe98bc266 | ||
|
|
2f9307fd99 | ||
|
|
3d6fa369e8 | ||
|
|
6812f508bc | ||
|
|
158cac6b0b | ||
|
|
4d10b6e1bd | ||
|
|
bb6e3b7a49 | ||
|
|
8b29dc683a | ||
|
|
d4156d78c6 | ||
|
|
c52977781e | ||
|
|
5ebc8e6f95 | ||
|
|
56d1dcafe5 | ||
|
|
393c765557 | ||
|
|
243b310eb2 | ||
|
|
cd1ecab4e0 | ||
|
|
c710953b85 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,3 +15,4 @@ venv/
|
|||||||
__pycache__
|
__pycache__
|
||||||
examples/__pycache__
|
examples/__pycache__
|
||||||
meshtastic.spec
|
meshtastic.spec
|
||||||
|
.hypothesis/
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ If you're interested in contributing but don't have specific things you'd like t
|
|||||||
This should always be considered a list in progress and flux -- inclusion doesn't guarantee implementation, and exclusion doesn't mean something's not wanted. GitHub issues are a great place to discuss ideas.
|
This should always be considered a list in progress and flux -- inclusion doesn't guarantee implementation, and exclusion doesn't mean something's not wanted. GitHub issues are a great place to discuss ideas.
|
||||||
|
|
||||||
* Types
|
* Types
|
||||||
* type annotations throughout the codebase
|
* type annotations throughout the codebase, and upgrading mypy running in CI to `--strict`
|
||||||
* mypy running in CI to type-check new code
|
|
||||||
* async-friendliness
|
* async-friendliness
|
||||||
* CLI completeness & consistency
|
* CLI completeness & consistency
|
||||||
* the CLI should support all features of the firmware
|
* the CLI should support all features of the firmware
|
||||||
@@ -40,9 +39,9 @@ This should always be considered a list in progress and flux -- inclusion doesn'
|
|||||||
* pubsub events should be documented clearly
|
* pubsub events should be documented clearly
|
||||||
* helpers for third-party code
|
* helpers for third-party code
|
||||||
* it should be easy to write a script that supports similar options to the CLI so many tools support the same ways of connecting to nodes
|
* it should be easy to write a script that supports similar options to the CLI so many tools support the same ways of connecting to nodes
|
||||||
* interactive client
|
|
||||||
* data storage & processing
|
* data storage & processing
|
||||||
* there should be a standardized way of recording packets for later use, debugging, etc.
|
* there should be a standardized way of recording packets for later use, debugging, etc.
|
||||||
|
* a persistence layer could also keep track of nodes beyond nodedb, as the apps do
|
||||||
* a sqlite database schema and tools for writing to it may be a good starting point
|
* a sqlite database schema and tools for writing to it may be a good starting point
|
||||||
* enable maps, charts, visualizations
|
* enable maps, charts, visualizations
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
"""
|
"""
|
||||||
# an API for Meshtastic devices
|
# A library for the Meshtastic Client API
|
||||||
|
|
||||||
Primary class: SerialInterface
|
Primary interfaces: SerialInterface, TCPInterface, BLEInterface
|
||||||
Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
|
Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
|
||||||
Source code on [github](https://github.com/meshtastic/python)
|
Source code on [github](https://github.com/meshtastic/python)
|
||||||
|
|
||||||
properties of SerialInterface:
|
notable properties of interface classes:
|
||||||
|
|
||||||
- localConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
|
|
||||||
the device.
|
|
||||||
- nodes - The database of received nodes. Includes always up-to-date location and username information for each
|
- nodes - The database of received nodes. Includes always up-to-date location and username information for each
|
||||||
node in the mesh. This is a read-only datastructure.
|
node in the mesh. This is a read-only datastructure.
|
||||||
- nodesByNum - like "nodes" but keyed by nodeNum instead of nodeId
|
- nodesByNum - like "nodes" but keyed by nodeNum instead of nodeId
|
||||||
- myInfo - Contains read-only information about the local radio device (software version, hardware version, etc)
|
- myInfo & metadata - Contain read-only information about the local radio device (software version, hardware version, etc)
|
||||||
|
- localNode - Pointer to a node object for the local node
|
||||||
|
|
||||||
|
notable properties of nodes:
|
||||||
|
- localConfig - Current radio settings, can be written to the radio with the `writeConfig` method.
|
||||||
|
- moduleConfig - Current module settings, can be written to the radio with the `writeConfig` method.
|
||||||
|
- channels - The node's channels, keyed by index.
|
||||||
|
|
||||||
# Published PubSub topics
|
# Published PubSub topics
|
||||||
|
|
||||||
@@ -113,6 +117,9 @@ OUR_APP_VERSION = 20300
|
|||||||
format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20
|
format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
NODELESS_WANT_CONFIG_ID = 69420
|
||||||
|
"""A special thing to pass for want_config_id that instructs nodes to skip sending nodeinfos other than its own."""
|
||||||
|
|
||||||
publishingThread = DeferredExecution("publishing")
|
publishingThread = DeferredExecution("publishing")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ 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.mesh_interface import MeshInterface
|
||||||
|
|
||||||
def onReceive(packet, interface):
|
def onReceive(packet, interface):
|
||||||
"""Callback invoked when a packet arrives"""
|
"""Callback invoked when a packet arrives"""
|
||||||
@@ -56,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"""
|
||||||
@@ -220,13 +226,14 @@ def setPref(config, comp_name, valStr) -> bool:
|
|||||||
config_values = getattr(config_part, 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:
|
||||||
|
config_values = getattr(config, config_type.name)
|
||||||
if val == 0:
|
if val == 0:
|
||||||
# clear values
|
# clear values
|
||||||
print("Clearing ignore_incoming list")
|
print("Clearing ignore_incoming list")
|
||||||
del config_type.message_type.ignore_incoming[:]
|
del config_values.ignore_incoming[:]
|
||||||
else:
|
else:
|
||||||
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_values.ignore_incoming.extend([int(valStr)])
|
||||||
|
|
||||||
prefix = f"{'.'.join(name[0:-1])}." 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 mt_config.camel_case:
|
if mt_config.camel_case:
|
||||||
@@ -250,31 +257,41 @@ def onConnected(interface):
|
|||||||
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.remove_position:
|
||||||
|
if args.dest != BROADCAST_ADDR:
|
||||||
|
print("Setting positions of remote nodes is not supported.")
|
||||||
|
return
|
||||||
|
closeNow = True
|
||||||
|
print("Removing fixed position and disabling fixed position setting")
|
||||||
|
interface.localNode.removeFixedPosition()
|
||||||
|
elif 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
|
||||||
lat = 0.0
|
lat = 0
|
||||||
lon = 0.0
|
lon = 0
|
||||||
# TODO: use getNode(args.dest) to be able to set it for a remote node
|
|
||||||
localConfig = interface.localNode.localConfig
|
|
||||||
if args.setalt:
|
if args.setalt:
|
||||||
alt = int(args.setalt)
|
alt = int(args.setalt)
|
||||||
localConfig.position.fixed_position = True
|
|
||||||
print(f"Fixing altitude at {alt} meters")
|
print(f"Fixing altitude at {alt} meters")
|
||||||
if args.setlat:
|
if args.setlat:
|
||||||
lat = float(args.setlat)
|
try:
|
||||||
localConfig.position.fixed_position = True
|
lat = int(args.setlat)
|
||||||
|
except ValueError:
|
||||||
|
lat = float(args.setlat)
|
||||||
print(f"Fixing latitude at {lat} degrees")
|
print(f"Fixing latitude at {lat} degrees")
|
||||||
if args.setlon:
|
if args.setlon:
|
||||||
lon = float(args.setlon)
|
try:
|
||||||
localConfig.position.fixed_position = True
|
lon = int(args.setlon)
|
||||||
|
except ValueError:
|
||||||
|
lon = float(args.setlon)
|
||||||
print(f"Fixing longitude at {lon} degrees")
|
print(f"Fixing longitude at {lon} degrees")
|
||||||
|
|
||||||
print("Setting device position")
|
print("Setting device position and enabling fixed position setting")
|
||||||
# can include lat/long/alt etc: latitude = 37.5, longitude = -122.1
|
# can include lat/long/alt etc: latitude = 37.5, longitude = -122.1
|
||||||
interface.sendPosition(lat, lon, alt)
|
interface.localNode.setFixedPosition(lat, lon, alt)
|
||||||
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
|
||||||
if interface.localNode.nodeNum in interface.nodesByNum and "position" in interface.nodesByNum[interface.localNode.nodeNum]:
|
if interface.localNode.nodeNum in interface.nodesByNum and "position" in interface.nodesByNum[interface.localNode.nodeNum]:
|
||||||
@@ -366,6 +383,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
|
||||||
@@ -400,12 +422,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}"
|
||||||
)
|
)
|
||||||
@@ -425,22 +443,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.request_position:
|
||||||
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 position request to {args.dest} (this could take a while)")
|
channelIndex = mt_config.channel_index or 0
|
||||||
interface.sendPosition(destinationId=args.dest, wantResponse=True)
|
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:
|
||||||
@@ -599,6 +623,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)
|
||||||
@@ -703,6 +730,10 @@ def onConnected(interface):
|
|||||||
ch = interface.getNode(args.dest).channels[channelIndex]
|
ch = interface.getNode(args.dest).channels[channelIndex]
|
||||||
|
|
||||||
if args.ch_enable or args.ch_disable:
|
if args.ch_enable or args.ch_disable:
|
||||||
|
print(
|
||||||
|
"Warning: --ch-enable and --ch-disable can produce noncontiguous channels, "
|
||||||
|
"which can cause errors in some clients. Whenever possible, use --ch-add and --ch-del instead."
|
||||||
|
)
|
||||||
if channelIndex == 0:
|
if channelIndex == 0:
|
||||||
meshtastic.util.our_exit(
|
meshtastic.util.our_exit(
|
||||||
"Warning: Cannot enable/disable PRIMARY channel."
|
"Warning: Cannot enable/disable PRIMARY channel."
|
||||||
@@ -801,10 +832,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())
|
||||||
|
|
||||||
@@ -813,6 +848,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
|
||||||
|
|
||||||
@@ -832,6 +870,10 @@ def onConnected(interface):
|
|||||||
)
|
)
|
||||||
interface.getNode(args.dest, False).iface.waitForAckNak()
|
interface.getNode(args.dest, False).iface.waitForAckNak()
|
||||||
|
|
||||||
|
if args.wait_to_disconnect:
|
||||||
|
print(f"Waiting {args.wait_to_disconnect} seconds before disconnecting" )
|
||||||
|
time.sleep(int(args.wait_to_disconnect))
|
||||||
|
|
||||||
# if the user didn't ask for serial debugging output, we might want to exit after we've done our operation
|
# if the user didn't ask for serial debugging output, we might want to exit after we've done our operation
|
||||||
if (not args.seriallog) and closeNow:
|
if (not args.seriallog) and closeNow:
|
||||||
interface.close() # after running command then exit
|
interface.close() # after running command then exit
|
||||||
@@ -1008,11 +1050,11 @@ def common():
|
|||||||
meshtastic.util.our_exit("BLE scan finished", 0)
|
meshtastic.util.our_exit("BLE scan finished", 0)
|
||||||
return
|
return
|
||||||
elif args.ble:
|
elif args.ble:
|
||||||
client = BLEInterface(args.ble, debugOut=logfile, noProto=args.noproto)
|
client = BLEInterface(args.ble, debugOut=logfile, noProto=args.noproto, noNodes=args.no_nodes)
|
||||||
elif args.host:
|
elif args.host:
|
||||||
try:
|
try:
|
||||||
client = meshtastic.tcp_interface.TCPInterface(
|
client = meshtastic.tcp_interface.TCPInterface(
|
||||||
args.host, debugOut=logfile, noProto=args.noproto
|
args.host, debugOut=logfile, noProto=args.noproto, noNodes=args.no_nodes
|
||||||
)
|
)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
meshtastic.util.our_exit(
|
meshtastic.util.our_exit(
|
||||||
@@ -1021,7 +1063,7 @@ def common():
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
client = meshtastic.serial_interface.SerialInterface(
|
client = meshtastic.serial_interface.SerialInterface(
|
||||||
args.port, debugOut=logfile, noProto=args.noproto
|
args.port, debugOut=logfile, noProto=args.noproto, noNodes=args.no_nodes
|
||||||
)
|
)
|
||||||
except PermissionError as ex:
|
except PermissionError as ex:
|
||||||
username = os.getlogin()
|
username = os.getlogin()
|
||||||
@@ -1036,7 +1078,7 @@ def common():
|
|||||||
if client.devPath is None:
|
if client.devPath is None:
|
||||||
try:
|
try:
|
||||||
client = meshtastic.tcp_interface.TCPInterface(
|
client = meshtastic.tcp_interface.TCPInterface(
|
||||||
"localhost", debugOut=logfile, noProto=args.noproto
|
"localhost", debugOut=logfile, noProto=args.noproto, noNodes=args.no_nodes
|
||||||
)
|
)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
meshtastic.util.our_exit(
|
meshtastic.util.our_exit(
|
||||||
@@ -1154,7 +1196,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",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1316,7 +1367,7 @@ def initParser():
|
|||||||
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--request-position",
|
"--request-position",
|
||||||
help="Request the position from a nade. "
|
help="Request the position from a node. "
|
||||||
"You need to pass the destination ID as an argument with '--dest'. "
|
"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",
|
||||||
@@ -1334,7 +1385,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",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1396,12 +1453,32 @@ def initParser():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
group.add_argument("--setalt", help="Set device altitude in meters (allows use without GPS)")
|
group.add_argument(
|
||||||
|
"--no-nodes",
|
||||||
group.add_argument("--setlat", help="Set device latitude (allows use without GPS)")
|
help="Request that the node not send node info to the client. "
|
||||||
|
"Will break things that depend on the nodedb, but will speed up startup. Requires 2.3.11+ firmware.",
|
||||||
|
action="store_true",
|
||||||
|
)
|
||||||
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--setlon", help="Set device longitude (allows use without GPS)"
|
"--setalt",
|
||||||
|
help="Set device altitude in meters (allows use without GPS), and enable fixed position.",
|
||||||
|
)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--setlat",
|
||||||
|
help="Set device latitude (allows use without GPS), and enable fixed position. Accepts a decimal value or an integer premultiplied by 1e7.",
|
||||||
|
)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--setlon",
|
||||||
|
help="Set device longitude (allows use without GPS), and enable fixed position. Accepts a decimal value or an integer premultiplied by 1e7.",
|
||||||
|
)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--remove-position",
|
||||||
|
help="Clear any existing fixed position and disable fixed position.",
|
||||||
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
@@ -1429,6 +1506,14 @@ def initParser():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--wait-to-disconnect",
|
||||||
|
help="How many seconds to wait before disconnecting from the device.",
|
||||||
|
const="5",
|
||||||
|
nargs="?",
|
||||||
|
action="store",
|
||||||
|
)
|
||||||
|
|
||||||
group.add_argument(
|
group.add_argument(
|
||||||
"--noproto",
|
"--noproto",
|
||||||
help="Don't start the API, just function as a dumb serial terminal.",
|
help="Don't start the API, just function as a dumb serial terminal.",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15meshtastic/atak.proto\x12\nmeshtastic\"\xe6\x01\n\tTAKPacket\x12\x15\n\ris_compressed\x18\x01 \x01(\x08\x12$\n\x07\x63ontact\x18\x02 \x01(\x0b\x32\x13.meshtastic.Contact\x12 \n\x05group\x18\x03 \x01(\x0b\x32\x11.meshtastic.Group\x12\"\n\x06status\x18\x04 \x01(\x0b\x32\x12.meshtastic.Status\x12\x1e\n\x03pli\x18\x05 \x01(\x0b\x32\x0f.meshtastic.PLIH\x00\x12#\n\x04\x63hat\x18\x06 \x01(\x0b\x32\x13.meshtastic.GeoChatH\x00\x42\x11\n\x0fpayload_variant\"2\n\x07GeoChat\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0f\n\x02to\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x05\n\x03_to\"M\n\x05Group\x12$\n\x04role\x18\x01 \x01(\x0e\x32\x16.meshtastic.MemberRole\x12\x1e\n\x04team\x18\x02 \x01(\x0e\x32\x10.meshtastic.Team\"\x19\n\x06Status\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\r\"4\n\x07\x43ontact\x12\x10\n\x08\x63\x61llsign\x18\x01 \x01(\t\x12\x17\n\x0f\x64\x65vice_callsign\x18\x02 \x01(\t\"_\n\x03PLI\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\r\n\x05speed\x18\x04 \x01(\r\x12\x0e\n\x06\x63ourse\x18\x05 \x01(\r*\xc0\x01\n\x04Team\x12\x14\n\x10Unspecifed_Color\x10\x00\x12\t\n\x05White\x10\x01\x12\n\n\x06Yellow\x10\x02\x12\n\n\x06Orange\x10\x03\x12\x0b\n\x07Magenta\x10\x04\x12\x07\n\x03Red\x10\x05\x12\n\n\x06Maroon\x10\x06\x12\n\n\x06Purple\x10\x07\x12\r\n\tDark_Blue\x10\x08\x12\x08\n\x04\x42lue\x10\t\x12\x08\n\x04\x43yan\x10\n\x12\x08\n\x04Teal\x10\x0b\x12\t\n\x05Green\x10\x0c\x12\x0e\n\nDark_Green\x10\r\x12\t\n\x05\x42rown\x10\x0e*\x7f\n\nMemberRole\x12\x0e\n\nUnspecifed\x10\x00\x12\x0e\n\nTeamMember\x10\x01\x12\x0c\n\x08TeamLead\x10\x02\x12\x06\n\x02HQ\x10\x03\x12\n\n\x06Sniper\x10\x04\x12\t\n\x05Medic\x10\x05\x12\x13\n\x0f\x46orwardObserver\x10\x06\x12\x07\n\x03RTO\x10\x07\x12\x06\n\x02K9\x10\x08\x42_\n\x13\x63om.geeksville.meshB\nATAKProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15meshtastic/atak.proto\x12\nmeshtastic\"\xe6\x01\n\tTAKPacket\x12\x15\n\ris_compressed\x18\x01 \x01(\x08\x12$\n\x07\x63ontact\x18\x02 \x01(\x0b\x32\x13.meshtastic.Contact\x12 \n\x05group\x18\x03 \x01(\x0b\x32\x11.meshtastic.Group\x12\"\n\x06status\x18\x04 \x01(\x0b\x32\x12.meshtastic.Status\x12\x1e\n\x03pli\x18\x05 \x01(\x0b\x32\x0f.meshtastic.PLIH\x00\x12#\n\x04\x63hat\x18\x06 \x01(\x0b\x32\x13.meshtastic.GeoChatH\x00\x42\x11\n\x0fpayload_variant\"\\\n\x07GeoChat\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0f\n\x02to\x18\x02 \x01(\tH\x00\x88\x01\x01\x12\x18\n\x0bto_callsign\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x05\n\x03_toB\x0e\n\x0c_to_callsign\"M\n\x05Group\x12$\n\x04role\x18\x01 \x01(\x0e\x32\x16.meshtastic.MemberRole\x12\x1e\n\x04team\x18\x02 \x01(\x0e\x32\x10.meshtastic.Team\"\x19\n\x06Status\x12\x0f\n\x07\x62\x61ttery\x18\x01 \x01(\r\"4\n\x07\x43ontact\x12\x10\n\x08\x63\x61llsign\x18\x01 \x01(\t\x12\x17\n\x0f\x64\x65vice_callsign\x18\x02 \x01(\t\"_\n\x03PLI\x12\x12\n\nlatitude_i\x18\x01 \x01(\x0f\x12\x13\n\x0blongitude_i\x18\x02 \x01(\x0f\x12\x10\n\x08\x61ltitude\x18\x03 \x01(\x05\x12\r\n\x05speed\x18\x04 \x01(\r\x12\x0e\n\x06\x63ourse\x18\x05 \x01(\r*\xc0\x01\n\x04Team\x12\x14\n\x10Unspecifed_Color\x10\x00\x12\t\n\x05White\x10\x01\x12\n\n\x06Yellow\x10\x02\x12\n\n\x06Orange\x10\x03\x12\x0b\n\x07Magenta\x10\x04\x12\x07\n\x03Red\x10\x05\x12\n\n\x06Maroon\x10\x06\x12\n\n\x06Purple\x10\x07\x12\r\n\tDark_Blue\x10\x08\x12\x08\n\x04\x42lue\x10\t\x12\x08\n\x04\x43yan\x10\n\x12\x08\n\x04Teal\x10\x0b\x12\t\n\x05Green\x10\x0c\x12\x0e\n\nDark_Green\x10\r\x12\t\n\x05\x42rown\x10\x0e*\x7f\n\nMemberRole\x12\x0e\n\nUnspecifed\x10\x00\x12\x0e\n\nTeamMember\x10\x01\x12\x0c\n\x08TeamLead\x10\x02\x12\x06\n\x02HQ\x10\x03\x12\n\n\x06Sniper\x10\x04\x12\t\n\x05Medic\x10\x05\x12\x13\n\x0f\x46orwardObserver\x10\x06\x12\x07\n\x03RTO\x10\x07\x12\x06\n\x02K9\x10\x08\x42_\n\x13\x63om.geeksville.meshB\nATAKProtosZ\"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.atak_pb2', globals())
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.atak_pb2', globals())
|
||||||
@@ -21,20 +21,20 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|||||||
|
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nATAKProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nATAKProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
|
||||||
_TEAM._serialized_start=580
|
_TEAM._serialized_start=622
|
||||||
_TEAM._serialized_end=772
|
_TEAM._serialized_end=814
|
||||||
_MEMBERROLE._serialized_start=774
|
_MEMBERROLE._serialized_start=816
|
||||||
_MEMBERROLE._serialized_end=901
|
_MEMBERROLE._serialized_end=943
|
||||||
_TAKPACKET._serialized_start=38
|
_TAKPACKET._serialized_start=38
|
||||||
_TAKPACKET._serialized_end=268
|
_TAKPACKET._serialized_end=268
|
||||||
_GEOCHAT._serialized_start=270
|
_GEOCHAT._serialized_start=270
|
||||||
_GEOCHAT._serialized_end=320
|
_GEOCHAT._serialized_end=362
|
||||||
_GROUP._serialized_start=322
|
_GROUP._serialized_start=364
|
||||||
_GROUP._serialized_end=399
|
_GROUP._serialized_end=441
|
||||||
_STATUS._serialized_start=401
|
_STATUS._serialized_start=443
|
||||||
_STATUS._serialized_end=426
|
_STATUS._serialized_end=468
|
||||||
_CONTACT._serialized_start=428
|
_CONTACT._serialized_start=470
|
||||||
_CONTACT._serialized_end=480
|
_CONTACT._serialized_end=522
|
||||||
_PLI._serialized_start=482
|
_PLI._serialized_start=524
|
||||||
_PLI._serialized_end=577
|
_PLI._serialized_end=619
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -302,6 +302,7 @@ class GeoChat(google.protobuf.message.Message):
|
|||||||
|
|
||||||
MESSAGE_FIELD_NUMBER: builtins.int
|
MESSAGE_FIELD_NUMBER: builtins.int
|
||||||
TO_FIELD_NUMBER: builtins.int
|
TO_FIELD_NUMBER: builtins.int
|
||||||
|
TO_CALLSIGN_FIELD_NUMBER: builtins.int
|
||||||
message: builtins.str
|
message: builtins.str
|
||||||
"""
|
"""
|
||||||
The text message
|
The text message
|
||||||
@@ -310,15 +311,23 @@ class GeoChat(google.protobuf.message.Message):
|
|||||||
"""
|
"""
|
||||||
Uid recipient of the message
|
Uid recipient of the message
|
||||||
"""
|
"""
|
||||||
|
to_callsign: builtins.str
|
||||||
|
"""
|
||||||
|
Callsign of the recipient for the message
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
message: builtins.str = ...,
|
message: builtins.str = ...,
|
||||||
to: builtins.str | None = ...,
|
to: builtins.str | None = ...,
|
||||||
|
to_callsign: builtins.str | None = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def HasField(self, field_name: typing_extensions.Literal["_to", b"_to", "to", b"to"]) -> builtins.bool: ...
|
def HasField(self, field_name: typing_extensions.Literal["_to", b"_to", "_to_callsign", b"_to_callsign", "to", b"to", "to_callsign", b"to_callsign"]) -> builtins.bool: ...
|
||||||
def ClearField(self, field_name: typing_extensions.Literal["_to", b"_to", "message", b"message", "to", b"to"]) -> None: ...
|
def ClearField(self, field_name: typing_extensions.Literal["_to", b"_to", "_to_callsign", b"_to_callsign", "message", b"message", "to", b"to", "to_callsign", b"to_callsign"]) -> None: ...
|
||||||
|
@typing.overload
|
||||||
def WhichOneof(self, oneof_group: typing_extensions.Literal["_to", b"_to"]) -> typing_extensions.Literal["to"] | None: ...
|
def WhichOneof(self, oneof_group: typing_extensions.Literal["_to", b"_to"]) -> typing_extensions.Literal["to"] | None: ...
|
||||||
|
@typing.overload
|
||||||
|
def WhichOneof(self, oneof_group: typing_extensions.Literal["_to_callsign", b"_to_callsign"]) -> typing_extensions.Literal["to_callsign"] | None: ...
|
||||||
|
|
||||||
global___GeoChat = GeoChat
|
global___GeoChat = GeoChat
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import time
|
|||||||
import struct
|
import struct
|
||||||
import asyncio
|
import asyncio
|
||||||
from threading import Thread, Event
|
from threading import Thread, Event
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from bleak import BleakScanner, BleakClient
|
from bleak import BleakScanner, BleakClient
|
||||||
|
|
||||||
from meshtastic.mesh_interface import MeshInterface
|
from meshtastic.mesh_interface import MeshInterface
|
||||||
@@ -30,7 +32,7 @@ class BLEInterface(MeshInterface):
|
|||||||
MESH = False
|
MESH = False
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, address, noProto = False, debugOut = None):
|
def __init__(self, address: Optional[str], noProto: bool = False, debugOut = None, noNodes: bool = False):
|
||||||
self.state = BLEInterface.BLEState()
|
self.state = BLEInterface.BLEState()
|
||||||
|
|
||||||
if not address:
|
if not address:
|
||||||
@@ -58,7 +60,7 @@ class BLEInterface(MeshInterface):
|
|||||||
return
|
return
|
||||||
|
|
||||||
logging.debug("Mesh init starting")
|
logging.debug("Mesh init starting")
|
||||||
MeshInterface.__init__(self, debugOut = debugOut, noProto = noProto)
|
MeshInterface.__init__(self, debugOut = debugOut, noProto = noProto, noNodes = noNodes)
|
||||||
self._startConfig()
|
self._startConfig()
|
||||||
if not self.noProto:
|
if not self.noProto:
|
||||||
self._waitConnected(timeout = 60.0)
|
self._waitConnected(timeout = 60.0)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18meshtastic/channel.proto\x12\nmeshtastic\"\xb8\x01\n\x0f\x43hannelSettings\x12\x17\n\x0b\x63hannel_num\x18\x01 \x01(\rB\x02\x18\x01\x12\x0b\n\x03psk\x18\x02 \x01(\x0c\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\n\n\x02id\x18\x04 \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x05 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x06 \x01(\x08\x12\x33\n\x0fmodule_settings\x18\x07 \x01(\x0b\x32\x1a.meshtastic.ModuleSettings\",\n\x0eModuleSettings\x12\x1a\n\x12position_precision\x18\x01 \x01(\r\"\xa1\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12-\n\x08settings\x18\x02 \x01(\x0b\x32\x1b.meshtastic.ChannelSettings\x12&\n\x04role\x18\x03 \x01(\x0e\x32\x18.meshtastic.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42\x62\n\x13\x63om.geeksville.meshB\rChannelProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18meshtastic/channel.proto\x12\nmeshtastic\"\xb8\x01\n\x0f\x43hannelSettings\x12\x17\n\x0b\x63hannel_num\x18\x01 \x01(\rB\x02\x18\x01\x12\x0b\n\x03psk\x18\x02 \x01(\x0c\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\n\n\x02id\x18\x04 \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x05 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x06 \x01(\x08\x12\x33\n\x0fmodule_settings\x18\x07 \x01(\x0b\x32\x1a.meshtastic.ModuleSettings\"E\n\x0eModuleSettings\x12\x1a\n\x12position_precision\x18\x01 \x01(\r\x12\x17\n\x0fis_client_muted\x18\x02 \x01(\x08\"\xa1\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12-\n\x08settings\x18\x02 \x01(\x0b\x32\x1b.meshtastic.ChannelSettings\x12&\n\x04role\x18\x03 \x01(\x0e\x32\x18.meshtastic.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42\x62\n\x13\x63om.geeksville.meshB\rChannelProtosZ\"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.channel_pb2', globals())
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.channel_pb2', globals())
|
||||||
@@ -26,9 +26,9 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|||||||
_CHANNELSETTINGS._serialized_start=41
|
_CHANNELSETTINGS._serialized_start=41
|
||||||
_CHANNELSETTINGS._serialized_end=225
|
_CHANNELSETTINGS._serialized_end=225
|
||||||
_MODULESETTINGS._serialized_start=227
|
_MODULESETTINGS._serialized_start=227
|
||||||
_MODULESETTINGS._serialized_end=271
|
_MODULESETTINGS._serialized_end=296
|
||||||
_CHANNEL._serialized_start=274
|
_CHANNEL._serialized_start=299
|
||||||
_CHANNEL._serialized_end=435
|
_CHANNEL._serialized_end=460
|
||||||
_CHANNEL_ROLE._serialized_start=387
|
_CHANNEL_ROLE._serialized_start=412
|
||||||
_CHANNEL_ROLE._serialized_end=435
|
_CHANNEL_ROLE._serialized_end=460
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -125,16 +125,23 @@ class ModuleSettings(google.protobuf.message.Message):
|
|||||||
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
||||||
|
|
||||||
POSITION_PRECISION_FIELD_NUMBER: builtins.int
|
POSITION_PRECISION_FIELD_NUMBER: builtins.int
|
||||||
|
IS_CLIENT_MUTED_FIELD_NUMBER: builtins.int
|
||||||
position_precision: builtins.int
|
position_precision: builtins.int
|
||||||
"""
|
"""
|
||||||
Bits of precision for the location sent in position packets.
|
Bits of precision for the location sent in position packets.
|
||||||
"""
|
"""
|
||||||
|
is_client_muted: builtins.bool
|
||||||
|
"""
|
||||||
|
Controls whether or not the phone / clients should mute the current channel
|
||||||
|
Useful for noisy public channels you don't necessarily want to disable
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
position_precision: builtins.int = ...,
|
position_precision: builtins.int = ...,
|
||||||
|
is_client_muted: builtins.bool = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing_extensions.Literal["position_precision", b"position_precision"]) -> None: ...
|
def ClearField(self, field_name: typing_extensions.Literal["is_client_muted", b"is_client_muted", "position_precision", b"position_precision"]) -> None: ...
|
||||||
|
|
||||||
global___ModuleSettings = ModuleSettings
|
global___ModuleSettings = ModuleSettings
|
||||||
|
|
||||||
|
|||||||
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):
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import sys
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
from typing import Any, Callable, Dict, List, Optional, Union
|
from typing import Any, Callable, Dict, List, Optional, Union
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ from meshtastic import (
|
|||||||
BROADCAST_ADDR,
|
BROADCAST_ADDR,
|
||||||
BROADCAST_NUM,
|
BROADCAST_NUM,
|
||||||
LOCAL_ADDR,
|
LOCAL_ADDR,
|
||||||
|
NODELESS_WANT_CONFIG_ID,
|
||||||
ResponseHandler,
|
ResponseHandler,
|
||||||
protocols,
|
protocols,
|
||||||
publishingThread,
|
publishingThread,
|
||||||
@@ -40,7 +42,7 @@ from meshtastic.util import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MeshInterface:
|
class MeshInterface: # pylint: disable=R0902
|
||||||
"""Interface class for meshtastic devices
|
"""Interface class for meshtastic devices
|
||||||
|
|
||||||
Properties:
|
Properties:
|
||||||
@@ -56,12 +58,14 @@ class MeshInterface:
|
|||||||
self.message = message
|
self.message = message
|
||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
||||||
def __init__(self, debugOut=None, noProto: bool=False) -> None:
|
def __init__(self, debugOut=None, noProto: bool=False, noNodes: bool=False) -> None:
|
||||||
"""Constructor
|
"""Constructor
|
||||||
|
|
||||||
Keyword Arguments:
|
Keyword Arguments:
|
||||||
noProto -- If True, don't try to run our protocol on the
|
noProto -- If True, don't try to run our protocol on the
|
||||||
link - just be a dumb serial client.
|
link - just be a dumb serial client.
|
||||||
|
noNodes -- If True, instruct the node to not send its nodedb
|
||||||
|
on startup, just other configuration information.
|
||||||
"""
|
"""
|
||||||
self.debugOut = debugOut
|
self.debugOut = debugOut
|
||||||
self.nodes: Optional[Dict[str,Dict]] = None # FIXME
|
self.nodes: Optional[Dict[str,Dict]] = None # FIXME
|
||||||
@@ -80,11 +84,13 @@ class MeshInterface:
|
|||||||
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: int = random.randint(0, 0xFFFFFFFF)
|
self.currentPacketId: int = random.randint(0, 0xFFFFFFFF)
|
||||||
self.nodesByNum: Optional[Dict[int, Dict]] = None
|
self.nodesByNum: Optional[Dict[int, Dict]] = None
|
||||||
self.configId: Optional[int] = None
|
self.noNodes: bool = noNodes
|
||||||
|
self.configId: Optional[int] = NODELESS_WANT_CONFIG_ID if noNodes else None
|
||||||
self.gotResponse: bool = False # used in gpio read
|
self.gotResponse: bool = False # used in gpio read
|
||||||
self.mask: Optional[int] = None # used in gpio read and gpio watch
|
self.mask: Optional[int] = None # used in gpio read and gpio watch
|
||||||
self.queueStatus: Optional[mesh_pb2.QueueStatus] = None
|
self.queueStatus: Optional[mesh_pb2.QueueStatus] = None
|
||||||
self.queue: collections.OrderedDict = collections.OrderedDict()
|
self.queue: collections.OrderedDict = collections.OrderedDict()
|
||||||
|
self._localChannels = None
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Shutdown this interface"""
|
"""Shutdown this interface"""
|
||||||
@@ -133,7 +139,7 @@ class MeshInterface:
|
|||||||
# use id as dictionary key for correct json format in list of nodes
|
# use id as dictionary key for correct json format in list of nodes
|
||||||
nodeid = n2["user"]["id"]
|
nodeid = n2["user"]["id"]
|
||||||
nodes[nodeid] = n2
|
nodes[nodeid] = n2
|
||||||
infos = owner + myinfo + metadata + mesh + json.dumps(nodes)
|
infos = owner + myinfo + metadata + mesh + json.dumps(nodes, indent=2)
|
||||||
print(infos)
|
print(infos)
|
||||||
return infos
|
return infos
|
||||||
|
|
||||||
@@ -165,7 +171,8 @@ class MeshInterface:
|
|||||||
if not includeSelf and node["num"] == self.localNode.nodeNum:
|
if not includeSelf and node["num"] == self.localNode.nodeNum:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
row = {"N": 0, "User": f"UNK: {node['num']}", "ID": f"!{node['num']:08x}"}
|
presumptive_id = f"!{node['num']:08x}"
|
||||||
|
row = {"N": 0, "User": f"Meshtastic {presumptive_id[-4:]}", "ID": presumptive_id}
|
||||||
|
|
||||||
user = node.get("user")
|
user = node.get("user")
|
||||||
if user:
|
if user:
|
||||||
@@ -174,6 +181,7 @@ class MeshInterface:
|
|||||||
"User": user.get("longName", "N/A"),
|
"User": user.get("longName", "N/A"),
|
||||||
"AKA": user.get("shortName", "N/A"),
|
"AKA": user.get("shortName", "N/A"),
|
||||||
"ID": user["id"],
|
"ID": user["id"],
|
||||||
|
"Hardware": user.get("hwModel", "UNSET")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -210,8 +218,8 @@ 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"),
|
"Hops Away": node.get("hopsAway", "0/unknown"),
|
||||||
"Channel": node.get("channel"),
|
"Channel": node.get("channel", 0),
|
||||||
"LastHeard": getLH(node.get("lastHeard")),
|
"LastHeard": getLH(node.get("lastHeard")),
|
||||||
"Since": getTimeAgo(node.get("lastHeard")),
|
"Since": getTimeAgo(node.get("lastHeard")),
|
||||||
}
|
}
|
||||||
@@ -247,7 +255,7 @@ class MeshInterface:
|
|||||||
destinationId: Union[int, str]=BROADCAST_ADDR,
|
destinationId: Union[int, str]=BROADCAST_ADDR,
|
||||||
wantAck: bool=False,
|
wantAck: bool=False,
|
||||||
wantResponse: bool=False,
|
wantResponse: bool=False,
|
||||||
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None,
|
onResponse: Optional[Callable[[dict], Any]]=None,
|
||||||
channelIndex: int=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
|
||||||
@@ -287,7 +295,7 @@ class MeshInterface:
|
|||||||
portNum: portnums_pb2.PortNum.ValueType=portnums_pb2.PortNum.PRIVATE_APP,
|
portNum: portnums_pb2.PortNum.ValueType=portnums_pb2.PortNum.PRIVATE_APP,
|
||||||
wantAck: bool=False,
|
wantAck: bool=False,
|
||||||
wantResponse: bool=False,
|
wantResponse: bool=False,
|
||||||
onResponse: Optional[Callable[[mesh_pb2.MeshPacket], Any]]=None,
|
onResponse: Optional[Callable[[dict], Any]]=None,
|
||||||
channelIndex: int=0,
|
channelIndex: int=0,
|
||||||
):
|
):
|
||||||
"""Send a data packet to some other node
|
"""Send a data packet to some other node
|
||||||
@@ -351,6 +359,7 @@ class MeshInterface:
|
|||||||
destinationId: Union[int, str]=BROADCAST_ADDR,
|
destinationId: Union[int, str]=BROADCAST_ADDR,
|
||||||
wantAck: bool=False,
|
wantAck: bool=False,
|
||||||
wantResponse: bool=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)
|
||||||
@@ -393,6 +402,7 @@ class MeshInterface:
|
|||||||
wantAck=wantAck,
|
wantAck=wantAck,
|
||||||
wantResponse=wantResponse,
|
wantResponse=wantResponse,
|
||||||
onResponse=onResponse,
|
onResponse=onResponse,
|
||||||
|
channelIndex=channelIndex,
|
||||||
)
|
)
|
||||||
if wantResponse:
|
if wantResponse:
|
||||||
self.waitForPosition()
|
self.waitForPosition()
|
||||||
@@ -426,7 +436,7 @@ 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 sendTraceRoute(self, dest: Union[int, str], hopLimit: int):
|
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(
|
||||||
@@ -435,12 +445,13 @@ 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 if self.nodes else 0, 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: dict):
|
||||||
"""on response for trace route"""
|
"""on response for trace route"""
|
||||||
routeDiscovery = mesh_pb2.RouteDiscovery()
|
routeDiscovery = mesh_pb2.RouteDiscovery()
|
||||||
routeDiscovery.ParseFromString(p["decoded"]["payload"])
|
routeDiscovery.ParseFromString(p["decoded"]["payload"])
|
||||||
@@ -456,26 +467,27 @@ 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
|
||||||
@@ -488,11 +500,12 @@ class MeshInterface:
|
|||||||
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()
|
||||||
|
|
||||||
def onResponseTelemetry(self, p):
|
def onResponseTelemetry(self, p: dict):
|
||||||
"""on response for telemetry"""
|
"""on response for telemetry"""
|
||||||
if p["decoded"]["portnum"] == 'TELEMETRY_APP':
|
if p["decoded"]["portnum"] == 'TELEMETRY_APP':
|
||||||
self._acknowledgment.receivedTelemetry = True
|
self._acknowledgment.receivedTelemetry = True
|
||||||
@@ -515,7 +528,7 @@ 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: int, callback: Callable):
|
def _addResponseHandler(self, requestId: int, callback: Callable[[dict], Any]):
|
||||||
self.responseHandlers[requestId] = ResponseHandler(callback)
|
self.responseHandlers[requestId] = ResponseHandler(callback)
|
||||||
|
|
||||||
def _sendPacket(self, meshPacket: mesh_pb2.MeshPacket, destinationId: Union[int,str]=BROADCAST_ADDR, wantAck: bool=False):
|
def _sendPacket(self, meshPacket: mesh_pb2.MeshPacket, destinationId: Union[int,str]=BROADCAST_ADDR, wantAck: bool=False):
|
||||||
@@ -677,6 +690,7 @@ class MeshInterface:
|
|||||||
self.heartbeatTimer = threading.Timer(i, callback)
|
self.heartbeatTimer = threading.Timer(i, callback)
|
||||||
self.heartbeatTimer.start()
|
self.heartbeatTimer.start()
|
||||||
p = mesh_pb2.ToRadio()
|
p = mesh_pb2.ToRadio()
|
||||||
|
p.heartbeat.CopyFrom(mesh_pb2.Heartbeat())
|
||||||
self._sendToRadio(p)
|
self._sendToRadio(p)
|
||||||
|
|
||||||
callback() # run our periodic callback now, it will make another timer if necessary
|
callback() # run our periodic callback now, it will make another timer if necessary
|
||||||
@@ -700,9 +714,11 @@ class MeshInterface:
|
|||||||
self.myInfo = None
|
self.myInfo = None
|
||||||
self.nodes = {} # nodes keyed by ID
|
self.nodes = {} # nodes keyed by ID
|
||||||
self.nodesByNum = {} # nodes keyed by nodenum
|
self.nodesByNum = {} # nodes keyed by nodenum
|
||||||
|
self._localChannels = [] # empty until we start getting channels pushed from the device (during config)
|
||||||
|
|
||||||
startConfig = mesh_pb2.ToRadio()
|
startConfig = mesh_pb2.ToRadio()
|
||||||
self.configId = random.randint(0, 0xFFFFFFFF)
|
if self.configId is None or not self.noNodes:
|
||||||
|
self.configId = random.randint(0, 0xFFFFFFFF)
|
||||||
startConfig.want_config_id = self.configId
|
startConfig.want_config_id = self.configId
|
||||||
self._sendToRadio(startConfig)
|
self._sendToRadio(startConfig)
|
||||||
|
|
||||||
@@ -781,7 +797,12 @@ class MeshInterface:
|
|||||||
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()
|
# This is no longer necessary because the current protocol statemachine has already proactively sent us the locally visible channels
|
||||||
|
# self.localNode.requestChannels()
|
||||||
|
self.localNode.setChannels(self._localChannels)
|
||||||
|
|
||||||
|
# the following should only be called after we have settings and channels
|
||||||
|
self._connected() # Tell everyone else we are ready to go
|
||||||
|
|
||||||
def _handleQueueStatusFromRadio(self, queueStatus) -> None:
|
def _handleQueueStatusFromRadio(self, queueStatus) -> None:
|
||||||
self.queueStatus = queueStatus
|
self.queueStatus = queueStatus
|
||||||
@@ -831,16 +852,18 @@ class MeshInterface:
|
|||||||
logging.debug(f"Received device metadata: {stripnl(fromRadio.metadata)}")
|
logging.debug(f"Received device metadata: {stripnl(fromRadio.metadata)}")
|
||||||
|
|
||||||
elif fromRadio.HasField("node_info"):
|
elif fromRadio.HasField("node_info"):
|
||||||
node = asDict["nodeInfo"]
|
logging.debug(f"Received nodeinfo: {asDict['nodeInfo']}")
|
||||||
|
|
||||||
|
node = self._getOrCreateByNum(asDict["nodeInfo"]["num"])
|
||||||
|
node.update(asDict["nodeInfo"])
|
||||||
try:
|
try:
|
||||||
newpos = self._fixupPosition(node["position"])
|
newpos = self._fixupPosition(node["position"])
|
||||||
node["position"] = newpos
|
node["position"] = newpos
|
||||||
except:
|
except:
|
||||||
logging.debug("Node without position")
|
logging.debug("Node without position")
|
||||||
|
|
||||||
logging.debug(f"Received nodeinfo: {node}")
|
# no longer necessary since we're mutating directly in nodesByNum via _getOrCreateByNum
|
||||||
|
#self.nodesByNum[node["num"]] = node
|
||||||
self.nodesByNum[node["num"]] = node
|
|
||||||
if "user" in node: # Some nodes might not have user/ids assigned yet
|
if "user" in node: # Some nodes might not have user/ids assigned yet
|
||||||
if "id" in node["user"]:
|
if "id" in node["user"]:
|
||||||
self.nodes[node["user"]["id"]] = node
|
self.nodes[node["user"]["id"]] = node
|
||||||
@@ -854,21 +877,36 @@ class MeshInterface:
|
|||||||
# stream API fromRadio.config_complete_id
|
# stream API fromRadio.config_complete_id
|
||||||
logging.debug(f"Config complete ID {self.configId}")
|
logging.debug(f"Config complete ID {self.configId}")
|
||||||
self._handleConfigComplete()
|
self._handleConfigComplete()
|
||||||
|
elif fromRadio.HasField("channel"):
|
||||||
|
self._handleChannel(fromRadio.channel)
|
||||||
elif fromRadio.HasField("packet"):
|
elif fromRadio.HasField("packet"):
|
||||||
self._handlePacketFromRadio(fromRadio.packet)
|
self._handlePacketFromRadio(fromRadio.packet)
|
||||||
|
|
||||||
elif fromRadio.HasField("queueStatus"):
|
elif fromRadio.HasField("queueStatus"):
|
||||||
self._handleQueueStatusFromRadio(fromRadio.queueStatus)
|
self._handleQueueStatusFromRadio(fromRadio.queueStatus)
|
||||||
|
|
||||||
elif fromRadio.rebooted:
|
elif fromRadio.HasField("mqttClientProxyMessage"):
|
||||||
|
publishingThread.queueWork(
|
||||||
|
lambda: pub.sendMessage(
|
||||||
|
"meshtastic.mqttclientproxymessage", proxymessage=fromRadio.mqttClientProxyMessage, interface=self
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif fromRadio.HasField("xmodemPacket"):
|
||||||
|
publishingThread.queueWork(
|
||||||
|
lambda: pub.sendMessage(
|
||||||
|
"meshtastic.xmodempacket", packet=fromRadio.xmodemPacket, interface=self
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif fromRadio.HasField("rebooted") and fromRadio.rebooted:
|
||||||
# Tell clients the device went away. Careful not to call the overridden
|
# Tell clients the device went away. Careful not to call the overridden
|
||||||
# subclass version that closes the serial port
|
# subclass version that closes the serial port
|
||||||
MeshInterface._disconnected(self)
|
MeshInterface._disconnected(self)
|
||||||
|
|
||||||
self._startConfig() # redownload the node db etc...
|
self._startConfig() # redownload the node db etc...
|
||||||
|
|
||||||
elif fromRadio.config or fromRadio.moduleConfig:
|
elif fromRadio.HasField("config") or fromRadio.HasField("moduleConfig"):
|
||||||
if fromRadio.config.HasField("device"):
|
if fromRadio.config.HasField("device"):
|
||||||
self.localNode.localConfig.device.CopyFrom(fromRadio.config.device)
|
self.localNode.localConfig.device.CopyFrom(fromRadio.config.device)
|
||||||
elif fromRadio.config.HasField("position"):
|
elif fromRadio.config.HasField("position"):
|
||||||
@@ -896,6 +934,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
|
||||||
@@ -942,9 +984,9 @@ class MeshInterface:
|
|||||||
Returns the position with the updated keys
|
Returns the position with the updated keys
|
||||||
"""
|
"""
|
||||||
if "latitudeI" in position:
|
if "latitudeI" in position:
|
||||||
position["latitude"] = position["latitudeI"] * 1e-7
|
position["latitude"] = float(position["latitudeI"] * Decimal("1e-7"))
|
||||||
if "longitudeI" in position:
|
if "longitudeI" in position:
|
||||||
position["longitude"] = position["longitudeI"] * 1e-7
|
position["longitude"] = float(position["longitudeI"] * Decimal("1e-7"))
|
||||||
return position
|
return position
|
||||||
|
|
||||||
def _nodeNumToId(self, num):
|
def _nodeNumToId(self, num):
|
||||||
@@ -973,10 +1015,23 @@ class MeshInterface:
|
|||||||
if nodeNum in self.nodesByNum:
|
if nodeNum in self.nodesByNum:
|
||||||
return self.nodesByNum[nodeNum]
|
return self.nodesByNum[nodeNum]
|
||||||
else:
|
else:
|
||||||
n = {"num": nodeNum} # Create a minimal node db entry
|
presumptive_id = f"!{nodeNum:08x}"
|
||||||
|
n = {
|
||||||
|
"num": nodeNum,
|
||||||
|
"user": {
|
||||||
|
"id": presumptive_id,
|
||||||
|
"longName": f"Meshtastic {presumptive_id[-4:]}",
|
||||||
|
"shortName": f"{presumptive_id[-4:]}",
|
||||||
|
"hwModel": "UNSET"
|
||||||
|
}
|
||||||
|
} # Create a minimal node db entry
|
||||||
self.nodesByNum[nodeNum] = n
|
self.nodesByNum[nodeNum] = n
|
||||||
return n
|
return n
|
||||||
|
|
||||||
|
def _handleChannel(self, channel):
|
||||||
|
"""During initial config the local node will proactively send all N (8) channels it knows"""
|
||||||
|
self._localChannels.append(channel)
|
||||||
|
|
||||||
def _handlePacketFromRadio(self, meshPacket, hack=False):
|
def _handlePacketFromRadio(self, meshPacket, hack=False):
|
||||||
"""Handle a MeshPacket that just arrived from the radio
|
"""Handle a MeshPacket that just arrived from the radio
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -112,6 +112,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
|||||||
"""
|
"""
|
||||||
LoRAType device: https://loratype.org/
|
LoRAType device: https://loratype.org/
|
||||||
"""
|
"""
|
||||||
|
WIPHONE: _HardwareModel.ValueType # 20
|
||||||
|
"""
|
||||||
|
wiphone https://www.wiphone.io/
|
||||||
|
"""
|
||||||
STATION_G1: _HardwareModel.ValueType # 25
|
STATION_G1: _HardwareModel.ValueType # 25
|
||||||
"""
|
"""
|
||||||
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
||||||
@@ -271,6 +275,21 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
|
|||||||
"""
|
"""
|
||||||
CDEBYTE EoRa-S3 board using their own MM modules, clone of LILYGO T3S3
|
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
|
||||||
|
"""
|
||||||
|
NRF52_PROMICRO_DIY: _HardwareModel.ValueType # 63
|
||||||
|
"""
|
||||||
|
NRF52_PROMICRO_DIY
|
||||||
|
Promicro NRF52840 with SX1262/LLCC68, SSD1306 OLED and NEO6M GPS
|
||||||
|
"""
|
||||||
|
RADIOMASTER_900_BANDIT_NANO: _HardwareModel.ValueType # 64
|
||||||
|
"""
|
||||||
|
RadioMaster 900 Bandit Nano, https://www.radiomasterrc.com/products/bandit-nano-expresslrs-rf-module
|
||||||
|
ESP32-D0WDQ6 With SX1276/SKY66122, SSD1306 OLED and No GPS
|
||||||
|
"""
|
||||||
PRIVATE_HW: _HardwareModel.ValueType # 255
|
PRIVATE_HW: _HardwareModel.ValueType # 255
|
||||||
"""
|
"""
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
@@ -368,6 +387,10 @@ LORA_TYPE: HardwareModel.ValueType # 19
|
|||||||
"""
|
"""
|
||||||
LoRAType device: https://loratype.org/
|
LoRAType device: https://loratype.org/
|
||||||
"""
|
"""
|
||||||
|
WIPHONE: HardwareModel.ValueType # 20
|
||||||
|
"""
|
||||||
|
wiphone https://www.wiphone.io/
|
||||||
|
"""
|
||||||
STATION_G1: HardwareModel.ValueType # 25
|
STATION_G1: HardwareModel.ValueType # 25
|
||||||
"""
|
"""
|
||||||
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
|
||||||
@@ -527,6 +550,21 @@ CDEBYTE_EORA_S3: HardwareModel.ValueType # 61
|
|||||||
"""
|
"""
|
||||||
CDEBYTE EoRa-S3 board using their own MM modules, clone of LILYGO T3S3
|
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
|
||||||
|
"""
|
||||||
|
NRF52_PROMICRO_DIY: HardwareModel.ValueType # 63
|
||||||
|
"""
|
||||||
|
NRF52_PROMICRO_DIY
|
||||||
|
Promicro NRF52840 with SX1262/LLCC68, SSD1306 OLED and NEO6M GPS
|
||||||
|
"""
|
||||||
|
RADIOMASTER_900_BANDIT_NANO: HardwareModel.ValueType # 64
|
||||||
|
"""
|
||||||
|
RadioMaster 900 Bandit Nano, https://www.radiomasterrc.com/products/bandit-nano-expresslrs-rf-module
|
||||||
|
ESP32-D0WDQ6 With SX1276/SKY66122, SSD1306 OLED and No GPS
|
||||||
|
"""
|
||||||
PRIVATE_HW: HardwareModel.ValueType # 255
|
PRIVATE_HW: HardwareModel.ValueType # 255
|
||||||
"""
|
"""
|
||||||
------------------------------------------------------------------------------------------------------------------------------------------
|
------------------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -405,6 +405,8 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
|
|
||||||
ENABLED_FIELD_NUMBER: builtins.int
|
ENABLED_FIELD_NUMBER: builtins.int
|
||||||
PAXCOUNTER_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
|
PAXCOUNTER_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
|
||||||
|
WIFI_THRESHOLD_FIELD_NUMBER: builtins.int
|
||||||
|
BLE_THRESHOLD_FIELD_NUMBER: builtins.int
|
||||||
enabled: builtins.bool
|
enabled: builtins.bool
|
||||||
"""
|
"""
|
||||||
Enable the Paxcounter Module
|
Enable the Paxcounter Module
|
||||||
@@ -414,13 +416,23 @@ class ModuleConfig(google.protobuf.message.Message):
|
|||||||
Interval in seconds of how often we should try to send our
|
Interval in seconds of how often we should try to send our
|
||||||
metrics to the mesh
|
metrics to the mesh
|
||||||
"""
|
"""
|
||||||
|
wifi_threshold: builtins.int
|
||||||
|
"""
|
||||||
|
WiFi RSSI threshold. Defaults to -80
|
||||||
|
"""
|
||||||
|
ble_threshold: builtins.int
|
||||||
|
"""
|
||||||
|
BLE RSSI threshold. Defaults to -80
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
enabled: builtins.bool = ...,
|
enabled: builtins.bool = ...,
|
||||||
paxcounter_update_interval: builtins.int = ...,
|
paxcounter_update_interval: builtins.int = ...,
|
||||||
|
wifi_threshold: builtins.int = ...,
|
||||||
|
ble_threshold: builtins.int = ...,
|
||||||
) -> None: ...
|
) -> None: ...
|
||||||
def ClearField(self, field_name: typing_extensions.Literal["enabled", b"enabled", "paxcounter_update_interval", b"paxcounter_update_interval"]) -> None: ...
|
def ClearField(self, field_name: typing_extensions.Literal["ble_threshold", b"ble_threshold", "enabled", b"enabled", "paxcounter_update_interval", b"paxcounter_update_interval", "wifi_threshold", b"wifi_threshold"]) -> None: ...
|
||||||
|
|
||||||
@typing_extensions.final
|
@typing_extensions.final
|
||||||
class SerialConfig(google.protobuf.message.Message):
|
class SerialConfig(google.protobuf.message.Message):
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import time
|
|||||||
|
|
||||||
from typing import Union
|
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, mesh_pb2, portnums_pb2
|
||||||
from meshtastic.util import (
|
from meshtastic.util import (
|
||||||
Timeout,
|
Timeout,
|
||||||
camel_to_snake,
|
camel_to_snake,
|
||||||
@@ -64,14 +64,19 @@ class Node:
|
|||||||
"""Show human readable description of our node"""
|
"""Show human readable description of our node"""
|
||||||
prefs = ""
|
prefs = ""
|
||||||
if self.localConfig:
|
if self.localConfig:
|
||||||
prefs = message_to_json(self.localConfig)
|
prefs = message_to_json(self.localConfig, multiline=True)
|
||||||
print(f"Preferences: {prefs}\n")
|
print(f"Preferences: {prefs}\n")
|
||||||
prefs = ""
|
prefs = ""
|
||||||
if self.moduleConfig:
|
if self.moduleConfig:
|
||||||
prefs = message_to_json(self.moduleConfig)
|
prefs = message_to_json(self.moduleConfig, multiline=True)
|
||||||
print(f"Module preferences: {prefs}\n")
|
print(f"Module preferences: {prefs}\n")
|
||||||
self.showChannels()
|
self.showChannels()
|
||||||
|
|
||||||
|
def setChannels(self, channels):
|
||||||
|
"""Set the channels for this node"""
|
||||||
|
self.channels = channels
|
||||||
|
self._fixupChannels()
|
||||||
|
|
||||||
def requestChannels(self):
|
def requestChannels(self):
|
||||||
"""Send regular MeshPackets to ask channels."""
|
"""Send regular MeshPackets to ask channels."""
|
||||||
logging.debug(f"requestChannels for nodeNum:{self.nodeNum}")
|
logging.debug(f"requestChannels for nodeNum:{self.nodeNum}")
|
||||||
@@ -313,6 +318,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")
|
||||||
@@ -569,6 +576,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()
|
||||||
@@ -588,9 +608,10 @@ class Node:
|
|||||||
p.get_device_metadata_request = True
|
p.get_device_metadata_request = True
|
||||||
logging.info(f"Requesting device metadata")
|
logging.info(f"Requesting device metadata")
|
||||||
|
|
||||||
return self._sendAdmin(
|
self._sendAdmin(
|
||||||
p, wantResponse=True, onResponse=self.onRequestGetMetadata
|
p, wantResponse=True, onResponse=self.onRequestGetMetadata
|
||||||
)
|
)
|
||||||
|
self.iface.waitForAckNak()
|
||||||
|
|
||||||
def factoryReset(self):
|
def factoryReset(self):
|
||||||
"""Tell the node to factory reset."""
|
"""Tell the node to factory reset."""
|
||||||
@@ -635,11 +656,43 @@ class Node:
|
|||||||
onResponse = self.onAckNak
|
onResponse = self.onAckNak
|
||||||
return self._sendAdmin(p, onResponse=onResponse)
|
return self._sendAdmin(p, onResponse=onResponse)
|
||||||
|
|
||||||
|
def setFixedPosition(self, lat: Union[int, float], lon: Union[int, float], alt: int):
|
||||||
|
"""Tell the node to set fixed position to the provided value and enable the fixed position setting"""
|
||||||
|
if self != self.iface.localNode:
|
||||||
|
logging.error("Setting position of remote nodes is not supported.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
p = mesh_pb2.Position()
|
||||||
|
if isinstance(lat, float) and lat != 0.0:
|
||||||
|
p.latitude_i = int(lat / 1e-7)
|
||||||
|
elif isinstance(lat, int) and lat != 0:
|
||||||
|
p.latitude_i = lat
|
||||||
|
|
||||||
|
if isinstance(lon, float) and lon != 0.0:
|
||||||
|
p.longitude_i = int(lon / 1e-7)
|
||||||
|
elif isinstance(lon, int) and lon != 0:
|
||||||
|
p.longitude_i = lon
|
||||||
|
|
||||||
|
if alt != 0:
|
||||||
|
p.altitude = alt
|
||||||
|
|
||||||
|
a = admin_pb2.AdminMessage()
|
||||||
|
a.set_fixed_position.CopyFrom(p)
|
||||||
|
return self._sendAdmin(a)
|
||||||
|
|
||||||
|
def removeFixedPosition(self):
|
||||||
|
"""Tell the node to remove the fixed position and set the fixed position setting to false"""
|
||||||
|
p = admin_pb2.AdminMessage()
|
||||||
|
p.remove_fixed_position = True
|
||||||
|
logging.info(f"Telling node to remove fixed position")
|
||||||
|
|
||||||
|
return self._sendAdmin(p)
|
||||||
|
|
||||||
def _fixupChannels(self):
|
def _fixupChannels(self):
|
||||||
"""Fixup indexes and add disabled channels as needed"""
|
"""Fixup indexes and add disabled channels as needed"""
|
||||||
|
|
||||||
# Add extra disabled channels as needed
|
# Add extra disabled channels as needed
|
||||||
# TODO: These 2 lines seem to not do anything.
|
# This is needed because the protobufs will have index **missing** if the channel number is zero
|
||||||
for index, ch in enumerate(self.channels):
|
for index, ch in enumerate(self.channels):
|
||||||
ch.index = index # fixup indexes
|
ch.index = index # fixup indexes
|
||||||
|
|
||||||
@@ -661,24 +714,30 @@ class Node:
|
|||||||
"""Handle the response packet for requesting device metadata getMetadata()"""
|
"""Handle the response packet for requesting device metadata getMetadata()"""
|
||||||
logging.debug(f"onRequestGetMetadata() p:{p}")
|
logging.debug(f"onRequestGetMetadata() p:{p}")
|
||||||
|
|
||||||
if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name(
|
if "routing" in p["decoded"]:
|
||||||
portnums_pb2.PortNum.ROUTING_APP
|
|
||||||
):
|
|
||||||
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||||
logging.warning(
|
print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}')
|
||||||
f'Metadata request failed, error reason: {p["decoded"]["routing"]["errorReason"]}'
|
self.iface._acknowledgment.receivedNak = True
|
||||||
)
|
else:
|
||||||
self._timeout.expireTime = time.time() # Do not wait any longer
|
self.iface._acknowledgment.receivedAck = True
|
||||||
return # Don't try to parse this routing message
|
if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name(
|
||||||
logging.debug(f"Retrying metadata request.")
|
portnums_pb2.PortNum.ROUTING_APP
|
||||||
self.getMetadata()
|
):
|
||||||
return
|
if p["decoded"]["routing"]["errorReason"] != "NONE":
|
||||||
|
logging.warning(
|
||||||
|
f'Metadata request failed, error reason: {p["decoded"]["routing"]["errorReason"]}'
|
||||||
|
)
|
||||||
|
self._timeout.expireTime = time.time() # Do not wait any longer
|
||||||
|
return # Don't try to parse this routing message
|
||||||
|
logging.debug(f"Retrying metadata request.")
|
||||||
|
self.getMetadata()
|
||||||
|
return
|
||||||
|
|
||||||
c = p["decoded"]["admin"]["raw"].get_device_metadata_response
|
c = p["decoded"]["admin"]["raw"].get_device_metadata_response
|
||||||
self._timeout.reset() # We made forward progress
|
self._timeout.reset() # We made forward progress
|
||||||
logging.debug(f"Received metadata {stripnl(c)}")
|
logging.debug(f"Received metadata {stripnl(c)}")
|
||||||
print(f"\nfirmware_version: {c.firmware_version}")
|
print(f"\nfirmware_version: {c.firmware_version}")
|
||||||
print(f"device_state_version: {c.device_state_version}")
|
print(f"device_state_version: {c.device_state_version}")
|
||||||
|
|
||||||
def onResponseRequestChannel(self, p):
|
def onResponseRequestChannel(self, p):
|
||||||
"""Handle the response packet for requesting a channel _requestChannel()"""
|
"""Handle the response packet for requesting a channel _requestChannel()"""
|
||||||
@@ -711,9 +770,6 @@ class Node:
|
|||||||
|
|
||||||
self.channels = self.partialChannels
|
self.channels = self.partialChannels
|
||||||
self._fixupChannels()
|
self._fixupChannels()
|
||||||
|
|
||||||
# FIXME, the following should only be called after we have settings and channels
|
|
||||||
self.iface._connected() # Tell everyone else we are ready to go
|
|
||||||
else:
|
else:
|
||||||
self._requestChannel(index + 1)
|
self._requestChannel(index + 1)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import logging
|
|||||||
import platform
|
import platform
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import serial # type: ignore[import-untyped]
|
import serial # type: ignore[import-untyped]
|
||||||
|
|
||||||
import meshtastic.util
|
import meshtastic.util
|
||||||
@@ -16,7 +18,7 @@ if platform.system() != "Windows":
|
|||||||
class SerialInterface(StreamInterface):
|
class SerialInterface(StreamInterface):
|
||||||
"""Interface class for meshtastic devices over a serial link"""
|
"""Interface class for meshtastic devices over a serial link"""
|
||||||
|
|
||||||
def __init__(self, devPath=None, debugOut=None, noProto=False, connectNow=True):
|
def __init__(self, devPath: Optional[str]=None, debugOut=None, noProto=False, connectNow=True, noNodes: bool=False):
|
||||||
"""Constructor, opens a connection to a specified serial port, or if unspecified try to
|
"""Constructor, opens a connection to a specified serial port, or if unspecified try to
|
||||||
find one Meshtastic device by probing
|
find one Meshtastic device by probing
|
||||||
|
|
||||||
@@ -26,7 +28,7 @@ class SerialInterface(StreamInterface):
|
|||||||
"""
|
"""
|
||||||
self.noProto = noProto
|
self.noProto = noProto
|
||||||
|
|
||||||
self.devPath = devPath
|
self.devPath: Optional[str] = devPath
|
||||||
|
|
||||||
if self.devPath is None:
|
if self.devPath is None:
|
||||||
ports = meshtastic.util.findPorts(True)
|
ports = meshtastic.util.findPorts(True)
|
||||||
@@ -60,7 +62,7 @@ class SerialInterface(StreamInterface):
|
|||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
StreamInterface.__init__(
|
StreamInterface.__init__(
|
||||||
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow
|
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
|
||||||
)
|
)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ MAX_TO_FROM_RADIO_SIZE = 512
|
|||||||
class StreamInterface(MeshInterface):
|
class StreamInterface(MeshInterface):
|
||||||
"""Interface class for meshtastic devices over a stream link (serial, TCP, etc)"""
|
"""Interface class for meshtastic devices over a stream link (serial, TCP, etc)"""
|
||||||
|
|
||||||
def __init__(self, debugOut=None, noProto=False, connectNow=True):
|
def __init__(self, debugOut=None, noProto=False, connectNow=True, noNodes=False):
|
||||||
"""Constructor, opens a connection to self.stream
|
"""Constructor, opens a connection to self.stream
|
||||||
|
|
||||||
Keyword Arguments:
|
Keyword Arguments:
|
||||||
@@ -43,7 +43,7 @@ class StreamInterface(MeshInterface):
|
|||||||
# FIXME, figure out why daemon=True causes reader thread to exit too early
|
# FIXME, figure out why daemon=True causes reader thread to exit too early
|
||||||
self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True)
|
self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True)
|
||||||
|
|
||||||
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto)
|
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto, noNodes=noNodes)
|
||||||
|
|
||||||
# Start the reader thread after superclass constructor completes init
|
# Start the reader thread after superclass constructor completes init
|
||||||
if connectNow:
|
if connectNow:
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class TCPInterface(StreamInterface):
|
|||||||
noProto=False,
|
noProto=False,
|
||||||
connectNow=True,
|
connectNow=True,
|
||||||
portNumber=4403,
|
portNumber=4403,
|
||||||
|
noNodes:bool=False,
|
||||||
):
|
):
|
||||||
"""Constructor, opens a connection to a specified IP address/hostname
|
"""Constructor, opens a connection to a specified IP address/hostname
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ class TCPInterface(StreamInterface):
|
|||||||
self.socket = None
|
self.socket = None
|
||||||
|
|
||||||
StreamInterface.__init__(
|
StreamInterface.__init__(
|
||||||
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow
|
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
|
||||||
)
|
)
|
||||||
|
|
||||||
def _socket_shutdown(self):
|
def _socket_shutdown(self):
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
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\"\xa8\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\"\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\"\xda\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\x12\x0b\n\x03lux\x18\t \x01(\x02\x12\x11\n\twhite_lux\x18\n \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*\xcb\x02\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\x12\t\n\x05SHT4X\x10\x11\x12\x0c\n\x08VEML7700\x10\x12\x12\x0c\n\x08MLX90632\x10\x13\x12\x0b\n\x07OPT3001\x10\x14\x12\x0c\n\x08LTR390UV\x10\x15\x12\x0e\n\nTSL25911FN\x10\x16\x12\t\n\x05\x41HT10\x10\x17\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=1079
|
_TELEMETRYSENSORTYPE._serialized_start=1129
|
||||||
_TELEMETRYSENSORTYPE._serialized_end=1303
|
_TELEMETRYSENSORTYPE._serialized_end=1460
|
||||||
_DEVICEMETRICS._serialized_start=43
|
_DEVICEMETRICS._serialized_start=43
|
||||||
_DEVICEMETRICS._serialized_end=172
|
_DEVICEMETRICS._serialized_end=172
|
||||||
_ENVIRONMENTMETRICS._serialized_start=175
|
_ENVIRONMENTMETRICS._serialized_start=175
|
||||||
_ENVIRONMENTMETRICS._serialized_end=343
|
_ENVIRONMENTMETRICS._serialized_end=393
|
||||||
_POWERMETRICS._serialized_start=346
|
_POWERMETRICS._serialized_start=396
|
||||||
_POWERMETRICS._serialized_end=486
|
_POWERMETRICS._serialized_end=536
|
||||||
_AIRQUALITYMETRICS._serialized_start=489
|
_AIRQUALITYMETRICS._serialized_start=539
|
||||||
_AIRQUALITYMETRICS._serialized_end=808
|
_AIRQUALITYMETRICS._serialized_end=858
|
||||||
_TELEMETRY._serialized_start=811
|
_TELEMETRY._serialized_start=861
|
||||||
_TELEMETRY._serialized_end=1076
|
_TELEMETRY._serialized_end=1126
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|||||||
@@ -86,6 +86,38 @@ 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
|
||||||
|
"""
|
||||||
|
SHT4X: _TelemetrySensorType.ValueType # 17
|
||||||
|
"""
|
||||||
|
Sensirion High accuracy temperature and humidity
|
||||||
|
"""
|
||||||
|
VEML7700: _TelemetrySensorType.ValueType # 18
|
||||||
|
"""
|
||||||
|
VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor.
|
||||||
|
"""
|
||||||
|
MLX90632: _TelemetrySensorType.ValueType # 19
|
||||||
|
"""
|
||||||
|
MLX90632 non-contact IR temperature sensor.
|
||||||
|
"""
|
||||||
|
OPT3001: _TelemetrySensorType.ValueType # 20
|
||||||
|
"""
|
||||||
|
TI OPT3001 Ambient Light Sensor
|
||||||
|
"""
|
||||||
|
LTR390UV: _TelemetrySensorType.ValueType # 21
|
||||||
|
"""
|
||||||
|
Lite On LTR-390UV-01 UV Light Sensor
|
||||||
|
"""
|
||||||
|
TSL25911FN: _TelemetrySensorType.ValueType # 22
|
||||||
|
"""
|
||||||
|
AMS TSL25911FN RGB Light Sensor
|
||||||
|
"""
|
||||||
|
AHT10: _TelemetrySensorType.ValueType # 23
|
||||||
|
"""
|
||||||
|
AHT10 Integrated temperature and humidity sensor
|
||||||
|
"""
|
||||||
|
|
||||||
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
|
||||||
"""
|
"""
|
||||||
@@ -156,6 +188,38 @@ 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
|
||||||
|
"""
|
||||||
|
SHT4X: TelemetrySensorType.ValueType # 17
|
||||||
|
"""
|
||||||
|
Sensirion High accuracy temperature and humidity
|
||||||
|
"""
|
||||||
|
VEML7700: TelemetrySensorType.ValueType # 18
|
||||||
|
"""
|
||||||
|
VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor.
|
||||||
|
"""
|
||||||
|
MLX90632: TelemetrySensorType.ValueType # 19
|
||||||
|
"""
|
||||||
|
MLX90632 non-contact IR temperature sensor.
|
||||||
|
"""
|
||||||
|
OPT3001: TelemetrySensorType.ValueType # 20
|
||||||
|
"""
|
||||||
|
TI OPT3001 Ambient Light Sensor
|
||||||
|
"""
|
||||||
|
LTR390UV: TelemetrySensorType.ValueType # 21
|
||||||
|
"""
|
||||||
|
Lite On LTR-390UV-01 UV Light Sensor
|
||||||
|
"""
|
||||||
|
TSL25911FN: TelemetrySensorType.ValueType # 22
|
||||||
|
"""
|
||||||
|
AMS TSL25911FN RGB Light Sensor
|
||||||
|
"""
|
||||||
|
AHT10: TelemetrySensorType.ValueType # 23
|
||||||
|
"""
|
||||||
|
AHT10 Integrated temperature and humidity sensor
|
||||||
|
"""
|
||||||
global___TelemetrySensorType = TelemetrySensorType
|
global___TelemetrySensorType = TelemetrySensorType
|
||||||
|
|
||||||
@typing_extensions.final
|
@typing_extensions.final
|
||||||
@@ -219,6 +283,9 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
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
|
IAQ_FIELD_NUMBER: builtins.int
|
||||||
|
DISTANCE_FIELD_NUMBER: builtins.int
|
||||||
|
LUX_FIELD_NUMBER: builtins.int
|
||||||
|
WHITE_LUX_FIELD_NUMBER: builtins.int
|
||||||
temperature: builtins.float
|
temperature: builtins.float
|
||||||
"""
|
"""
|
||||||
Temperature measured
|
Temperature measured
|
||||||
@@ -248,6 +315,18 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
relative scale IAQ value as measured by Bosch BME680 . value 0-500.
|
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.
|
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.
|
||||||
|
"""
|
||||||
|
lux: builtins.float
|
||||||
|
"""
|
||||||
|
VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor.
|
||||||
|
"""
|
||||||
|
white_lux: builtins.float
|
||||||
|
"""
|
||||||
|
VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor.
|
||||||
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -258,8 +337,11 @@ class EnvironmentMetrics(google.protobuf.message.Message):
|
|||||||
voltage: builtins.float = ...,
|
voltage: builtins.float = ...,
|
||||||
current: builtins.float = ...,
|
current: builtins.float = ...,
|
||||||
iaq: builtins.int = ...,
|
iaq: builtins.int = ...,
|
||||||
|
distance: builtins.float = ...,
|
||||||
|
lux: builtins.float = ...,
|
||||||
|
white_lux: 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", "iaq", b"iaq", "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", "lux", b"lux", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "voltage", b"voltage", "white_lux", b"white_lux"]) -> None: ...
|
||||||
|
|
||||||
global___EnvironmentMetrics = EnvironmentMetrics
|
global___EnvironmentMetrics = EnvironmentMetrics
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ def test_init_onNodeInfoReceive(caplog, iface_with_nodes):
|
|||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
iface.myInfo.my_node_num = 2475227164
|
iface.myInfo.my_node_num = 2475227164
|
||||||
packet = {
|
packet = {
|
||||||
"from": "foo",
|
"from": 4808675309,
|
||||||
"decoded": {
|
"decoded": {
|
||||||
"user": {
|
"user": {
|
||||||
"id": "bar",
|
"id": "bar",
|
||||||
|
|||||||
@@ -724,29 +724,79 @@ def test_main_sendtext_with_dest(mock_findPorts, mock_serial, mocked_open, mock_
|
|||||||
assert re.search(r"Warning: There were no self.nodes.", caplog.text, re.MULTILINE)
|
assert re.search(r"Warning: There were no self.nodes.", caplog.text, re.MULTILINE)
|
||||||
assert err == ""
|
assert err == ""
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
|
def test_main_removeposition_invalid(capsys):
|
||||||
|
"""Test --remove-position with an invalid dest"""
|
||||||
|
sys.argv = ["", "--remove-position", "--dest", "!12345678"]
|
||||||
|
mt_config.args = sys.argv
|
||||||
|
iface = MagicMock(autospec=SerialInterface)
|
||||||
|
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||||
|
main()
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
||||||
|
assert re.search(r"remote nodes is not supported", out, re.MULTILINE)
|
||||||
|
assert err == ""
|
||||||
|
mo.assert_called()
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
|
def test_main_setlat_invalid(capsys):
|
||||||
|
"""Test --setlat with an invalid dest"""
|
||||||
|
sys.argv = ["", "--setlat", "37.5", "--dest", "!12345678"]
|
||||||
|
mt_config.args = sys.argv
|
||||||
|
iface = MagicMock(autospec=SerialInterface)
|
||||||
|
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||||
|
main()
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
||||||
|
assert re.search(r"remote nodes is not supported", out, re.MULTILINE)
|
||||||
|
assert err == ""
|
||||||
|
mo.assert_called()
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
|
def test_main_removeposition(capsys):
|
||||||
|
"""Test --remove-position"""
|
||||||
|
sys.argv = ["", "--remove-position"]
|
||||||
|
mt_config.args = sys.argv
|
||||||
|
|
||||||
|
mocked_node = MagicMock(autospec=Node)
|
||||||
|
|
||||||
|
def mock_removeFixedPosition():
|
||||||
|
print("inside mocked removeFixedPosition")
|
||||||
|
|
||||||
|
mocked_node.removeFixedPosition.side_effect = mock_removeFixedPosition
|
||||||
|
|
||||||
|
iface = MagicMock(autospec=SerialInterface)
|
||||||
|
iface.localNode = mocked_node
|
||||||
|
|
||||||
|
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||||
|
main()
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
||||||
|
assert re.search(r"Removing fixed position", out, re.MULTILINE)
|
||||||
|
assert re.search(r"inside mocked removeFixedPosition", out, re.MULTILINE)
|
||||||
|
assert err == ""
|
||||||
|
mo.assert_called()
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
@pytest.mark.usefixtures("reset_mt_config")
|
@pytest.mark.usefixtures("reset_mt_config")
|
||||||
def test_main_setlat(capsys):
|
def test_main_setlat(capsys):
|
||||||
"""Test --sendlat"""
|
"""Test --setlat"""
|
||||||
sys.argv = ["", "--setlat", "37.5"]
|
sys.argv = ["", "--setlat", "37.5"]
|
||||||
mt_config.args = sys.argv
|
mt_config.args = sys.argv
|
||||||
|
|
||||||
mocked_node = MagicMock(autospec=Node)
|
mocked_node = MagicMock(autospec=Node)
|
||||||
|
|
||||||
def mock_writeConfig():
|
def mock_setFixedPosition(lat, lon, alt):
|
||||||
print("inside mocked writeConfig")
|
print("inside mocked setFixedPosition")
|
||||||
|
|
||||||
mocked_node.writeConfig.side_effect = mock_writeConfig
|
|
||||||
|
|
||||||
iface = MagicMock(autospec=SerialInterface)
|
|
||||||
|
|
||||||
def mock_sendPosition(lat, lon, alt):
|
|
||||||
print("inside mocked sendPosition")
|
|
||||||
print(f"{lat} {lon} {alt}")
|
print(f"{lat} {lon} {alt}")
|
||||||
|
|
||||||
iface.sendPosition.side_effect = mock_sendPosition
|
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
|
||||||
iface.localNode.return_value = mocked_node
|
|
||||||
|
iface = MagicMock(autospec=SerialInterface)
|
||||||
|
iface.localNode = mocked_node
|
||||||
|
|
||||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||||
main()
|
main()
|
||||||
@@ -754,8 +804,7 @@ def test_main_setlat(capsys):
|
|||||||
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
||||||
assert re.search(r"Fixing latitude", out, re.MULTILINE)
|
assert re.search(r"Fixing latitude", out, re.MULTILINE)
|
||||||
assert re.search(r"Setting device position", out, re.MULTILINE)
|
assert re.search(r"Setting device position", out, re.MULTILINE)
|
||||||
assert re.search(r"inside mocked sendPosition", out, re.MULTILINE)
|
assert re.search(r"inside mocked setFixedPosition", out, re.MULTILINE)
|
||||||
# TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
|
|
||||||
assert err == ""
|
assert err == ""
|
||||||
mo.assert_called()
|
mo.assert_called()
|
||||||
|
|
||||||
@@ -769,19 +818,14 @@ def test_main_setlon(capsys):
|
|||||||
|
|
||||||
mocked_node = MagicMock(autospec=Node)
|
mocked_node = MagicMock(autospec=Node)
|
||||||
|
|
||||||
def mock_writeConfig():
|
def mock_setFixedPosition(lat, lon, alt):
|
||||||
print("inside mocked writeConfig")
|
print("inside mocked setFixedPosition")
|
||||||
|
|
||||||
mocked_node.writeConfig.side_effect = mock_writeConfig
|
|
||||||
|
|
||||||
iface = MagicMock(autospec=SerialInterface)
|
|
||||||
|
|
||||||
def mock_sendPosition(lat, lon, alt):
|
|
||||||
print("inside mocked sendPosition")
|
|
||||||
print(f"{lat} {lon} {alt}")
|
print(f"{lat} {lon} {alt}")
|
||||||
|
|
||||||
iface.sendPosition.side_effect = mock_sendPosition
|
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
|
||||||
iface.localNode.return_value = mocked_node
|
|
||||||
|
iface = MagicMock(autospec=SerialInterface)
|
||||||
|
iface.localNode = mocked_node
|
||||||
|
|
||||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||||
main()
|
main()
|
||||||
@@ -789,8 +833,7 @@ def test_main_setlon(capsys):
|
|||||||
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
||||||
assert re.search(r"Fixing longitude", out, re.MULTILINE)
|
assert re.search(r"Fixing longitude", out, re.MULTILINE)
|
||||||
assert re.search(r"Setting device position", out, re.MULTILINE)
|
assert re.search(r"Setting device position", out, re.MULTILINE)
|
||||||
assert re.search(r"inside mocked sendPosition", out, re.MULTILINE)
|
assert re.search(r"inside mocked setFixedPosition", out, re.MULTILINE)
|
||||||
# TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
|
|
||||||
assert err == ""
|
assert err == ""
|
||||||
mo.assert_called()
|
mo.assert_called()
|
||||||
|
|
||||||
@@ -804,19 +847,14 @@ def test_main_setalt(capsys):
|
|||||||
|
|
||||||
mocked_node = MagicMock(autospec=Node)
|
mocked_node = MagicMock(autospec=Node)
|
||||||
|
|
||||||
def mock_writeConfig():
|
def mock_setFixedPosition(lat, lon, alt):
|
||||||
print("inside mocked writeConfig")
|
print("inside mocked setFixedPosition")
|
||||||
|
|
||||||
mocked_node.writeConfig.side_effect = mock_writeConfig
|
|
||||||
|
|
||||||
iface = MagicMock(autospec=SerialInterface)
|
|
||||||
|
|
||||||
def mock_sendPosition(lat, lon, alt):
|
|
||||||
print("inside mocked sendPosition")
|
|
||||||
print(f"{lat} {lon} {alt}")
|
print(f"{lat} {lon} {alt}")
|
||||||
|
|
||||||
iface.sendPosition.side_effect = mock_sendPosition
|
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
|
||||||
iface.localNode.return_value = mocked_node
|
|
||||||
|
iface = MagicMock(autospec=SerialInterface)
|
||||||
|
iface.localNode = mocked_node
|
||||||
|
|
||||||
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
|
||||||
main()
|
main()
|
||||||
@@ -824,8 +862,7 @@ def test_main_setalt(capsys):
|
|||||||
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
assert re.search(r"Connected to radio", out, re.MULTILINE)
|
||||||
assert re.search(r"Fixing altitude", out, re.MULTILINE)
|
assert re.search(r"Fixing altitude", out, re.MULTILINE)
|
||||||
assert re.search(r"Setting device position", out, re.MULTILINE)
|
assert re.search(r"Setting device position", out, re.MULTILINE)
|
||||||
assert re.search(r"inside mocked sendPosition", out, re.MULTILINE)
|
assert re.search(r"inside mocked setFixedPosition", out, re.MULTILINE)
|
||||||
# TODO: Why does this not work? assert re.search(r'inside mocked writeConfig', out, re.MULTILINE)
|
|
||||||
assert err == ""
|
assert err == ""
|
||||||
mo.assert_called()
|
mo.assert_called()
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -36,12 +36,15 @@ def test_MeshInterface(capsys):
|
|||||||
"lastHeard": 1640204888,
|
"lastHeard": 1640204888,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
iface.nodes = {NODE_ID: node}
|
iface.nodes = {NODE_ID: node}
|
||||||
iface.nodesByNum = {NODE_NUM: 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()
|
||||||
@@ -585,7 +588,7 @@ def test_getOrCreateByNum_minimal(iface_with_nodes):
|
|||||||
iface = iface_with_nodes
|
iface = iface_with_nodes
|
||||||
iface.myInfo.my_node_num = 2475227164
|
iface.myInfo.my_node_num = 2475227164
|
||||||
tmp = iface._getOrCreateByNum(123)
|
tmp = iface._getOrCreateByNum(123)
|
||||||
assert tmp == {"num": 123}
|
assert tmp == {"num": 123, "user": {"hwModel": "UNSET", "id": "!0000007b", "shortName": "007b", "longName": "Meshtastic 007b"}}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import re
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from hypothesis import given, strategies as st
|
||||||
|
|
||||||
from meshtastic.supported_device import SupportedDevice
|
from meshtastic.supported_device import SupportedDevice
|
||||||
from meshtastic.mesh_pb2 import MyNodeInfo
|
from meshtastic.mesh_pb2 import MyNodeInfo
|
||||||
@@ -33,6 +34,7 @@ from meshtastic.util import (
|
|||||||
stripnl,
|
stripnl,
|
||||||
support_info,
|
support_info,
|
||||||
message_to_json,
|
message_to_json,
|
||||||
|
Acknowledgment
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -63,6 +65,7 @@ def test_fromStr():
|
|||||||
assert fromStr("123") == 123
|
assert fromStr("123") == 123
|
||||||
assert fromStr("abc") == "abc"
|
assert fromStr("abc") == "abc"
|
||||||
assert fromStr("123456789") == 123456789
|
assert fromStr("123456789") == 123456789
|
||||||
|
assert fromStr("base64:Zm9vIGJhciBiYXo=") == b"foo bar baz"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unitslow
|
@pytest.mark.unitslow
|
||||||
@@ -555,3 +558,39 @@ def test_message_to_json_shows_all():
|
|||||||
actual = json.loads(message_to_json(MyNodeInfo()))
|
actual = json.loads(message_to_json(MyNodeInfo()))
|
||||||
expected = { "myNodeNum": 0, "rebootCount": 0, "minAppVersion": 0 }
|
expected = { "myNodeNum": 0, "rebootCount": 0, "minAppVersion": 0 }
|
||||||
assert actual == expected
|
assert actual == expected
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_acknowledgement_reset():
|
||||||
|
"""
|
||||||
|
Test that the reset method can set all fields back to False
|
||||||
|
"""
|
||||||
|
test_ack_obj = Acknowledgment()
|
||||||
|
# everything's set to False; let's set it to True to get a good test
|
||||||
|
test_ack_obj.receivedAck = True
|
||||||
|
test_ack_obj.receivedNak = True
|
||||||
|
test_ack_obj.receivedImplAck = True
|
||||||
|
test_ack_obj.receivedTraceRoute = True
|
||||||
|
test_ack_obj.receivedTelemetry = True
|
||||||
|
test_ack_obj.receivedPosition = True
|
||||||
|
test_ack_obj.reset()
|
||||||
|
assert test_ack_obj.receivedAck is False
|
||||||
|
assert test_ack_obj.receivedNak is False
|
||||||
|
assert test_ack_obj.receivedImplAck is False
|
||||||
|
assert test_ack_obj.receivedTraceRoute is False
|
||||||
|
assert test_ack_obj.receivedTelemetry is False
|
||||||
|
assert test_ack_obj.receivedPosition is False
|
||||||
|
|
||||||
|
@given(a_string=st.text(
|
||||||
|
alphabet=st.characters(
|
||||||
|
codec='ascii',
|
||||||
|
min_codepoint=0x5F,
|
||||||
|
max_codepoint=0x7A,
|
||||||
|
exclude_characters=r'`',
|
||||||
|
)).filter(
|
||||||
|
lambda x: x not in [''] and x[0] not in "_" and x[-1] not in '_' and not re.search(r'__', x)
|
||||||
|
))
|
||||||
|
def test_roundtrip_snake_to_camel_camel_to_snake(a_string):
|
||||||
|
"""Test that snake_to_camel and camel_to_snake roundtrip each other"""
|
||||||
|
value0 = snake_to_camel(a_string=a_string)
|
||||||
|
value1 = camel_to_snake(a_string=value0)
|
||||||
|
assert a_string == value1, (a_string, value1)
|
||||||
|
|||||||
@@ -11,9 +11,10 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from typing import Union
|
from typing import List, NoReturn, Union
|
||||||
|
|
||||||
from google.protobuf.json_format import MessageToJson
|
from google.protobuf.json_format import MessageToJson
|
||||||
|
from google.protobuf.message import Message
|
||||||
|
|
||||||
import packaging.version as pkg_version
|
import packaging.version as pkg_version
|
||||||
import requests
|
import requests
|
||||||
@@ -103,7 +104,7 @@ def pskToString(psk: bytes):
|
|||||||
return "secret"
|
return "secret"
|
||||||
|
|
||||||
|
|
||||||
def stripnl(s):
|
def stripnl(s) -> str:
|
||||||
"""Remove newlines from a string (and remove extra whitespace)"""
|
"""Remove newlines from a string (and remove extra whitespace)"""
|
||||||
s = str(s).replace("\n", " ")
|
s = str(s).replace("\n", " ")
|
||||||
return " ".join(s.split())
|
return " ".join(s.split())
|
||||||
@@ -122,7 +123,7 @@ def catchAndIgnore(reason, closure):
|
|||||||
logging.error(f"Exception thrown in {reason}: {ex}")
|
logging.error(f"Exception thrown in {reason}: {ex}")
|
||||||
|
|
||||||
|
|
||||||
def findPorts(eliminate_duplicates=False):
|
def findPorts(eliminate_duplicates: bool=False) -> List[str]:
|
||||||
"""Find all ports that might have meshtastic devices
|
"""Find all ports that might have meshtastic devices
|
||||||
eliminate_duplicates will run the eliminate_duplicate_port() on the collection
|
eliminate_duplicates will run the eliminate_duplicate_port() on the collection
|
||||||
|
|
||||||
@@ -263,7 +264,7 @@ class DeferredExecution:
|
|||||||
print(traceback.format_exc())
|
print(traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
def our_exit(message, return_value=1):
|
def our_exit(message, return_value=1) -> NoReturn:
|
||||||
"""Print the message and return a value.
|
"""Print the message and return a value.
|
||||||
return_value defaults to 1 (non-successful)
|
return_value defaults to 1 (non-successful)
|
||||||
"""
|
"""
|
||||||
@@ -627,6 +628,8 @@ def check_if_newer_version():
|
|||||||
|
|
||||||
return pypi_version
|
return pypi_version
|
||||||
|
|
||||||
def message_to_json(message):
|
|
||||||
"Return protobuf message as JSON. Always print all fields, even when not present in data."
|
def message_to_json(message: Message, multiline: bool=False) -> str:
|
||||||
return stripnl(MessageToJson(message, always_print_fields_with_no_presence=True))
|
"""Return protobuf message as JSON. Always print all fields, even when not present in data."""
|
||||||
|
json = MessageToJson(message, always_print_fields_with_no_presence=True)
|
||||||
|
return stripnl(json) if not multiline else json
|
||||||
|
|||||||
Submodule protobufs updated: 0d08acd9c5...a45a6154d0
@@ -14,6 +14,7 @@ autopep8
|
|||||||
pylint
|
pylint
|
||||||
pytest
|
pytest
|
||||||
pytest-cov
|
pytest-cov
|
||||||
|
hypothesis
|
||||||
pyyaml
|
pyyaml
|
||||||
pytap2
|
pytap2
|
||||||
pdoc3
|
pdoc3
|
||||||
@@ -22,7 +23,7 @@ bleak
|
|||||||
packaging
|
packaging
|
||||||
mypy
|
mypy
|
||||||
mypy-protobuf
|
mypy-protobuf
|
||||||
types-protobuf
|
types-protobuf>=5.26.0
|
||||||
types-tabulate
|
types-tabulate
|
||||||
types-requests
|
types-requests
|
||||||
types-setuptools
|
types-setuptools
|
||||||
|
|||||||
2
setup.py
2
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.5",
|
version="2.3.10",
|
||||||
description="Python API & client shell for talking to Meshtastic devices",
|
description="Python API & client shell for talking to Meshtastic devices",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
|
|||||||
Reference in New Issue
Block a user