Compare commits

..

87 Commits
2.5.0 ... 2.5.4

Author SHA1 Message Date
github-actions
a4630b53eb bump version to 2.5.4 2024-11-01 16:03:00 +00:00
Ian McEwen
646aa981d5 update readme to link to docs again 2024-10-29 14:22:09 -07:00
Ian McEwen
9381acd6ac Merge pull request #681 from william-stearns/wls_add_types2
Wls add types2
2024-10-29 06:50:52 -07:00
Ian McEwen
384063db19 Fix some remaining mypy complaints 2024-10-29 06:47:16 -07:00
Ian McEwen
20d75d9023 Merge branch 'master' into wls_add_types2 2024-10-29 06:19:16 -07:00
Ian McEwen
0deb1d788f Merge pull request #702 from meshtastic/dependabot/pip/werkzeug-3.0.6
Bump werkzeug from 3.0.4 to 3.0.6
2024-10-28 20:36:24 -07:00
Ian McEwen
1070d9202b Merge pull request #698 from lachesis/lachesis/fix-remote-module-cfg-get
Add missing camel to snake conversion
2024-10-28 20:35:10 -07:00
Ian McEwen
b90de8b73b Merge pull request #701 from fmoessbauer/master
A lot of fixes around setting / retrieving base64 encoded values
2024-10-28 20:29:05 -07:00
dependabot[bot]
2e79ecf759 Bump werkzeug from 3.0.4 to 3.0.6
Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.0.4 to 3.0.6.
- [Release notes](https://github.com/pallets/werkzeug/releases)
- [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/werkzeug/compare/3.0.4...3.0.6)

---
updated-dependencies:
- dependency-name: werkzeug
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-26 00:29:31 +00:00
Felix Moessbauer
578d3e4b24 do not double-print value when setting a repeated value
When clearning or appending to a repeated value, both the "Clearing..."
/ "Adding..." line and the "Set..." line were shown. However, this is
misleading as the only performed operation is the clearing / appending.

We fix this by directly returning from the function in case of clearing
/ appending.
2024-10-25 17:11:06 +02:00
Felix Moessbauer
4ca13bcede fix setting of list item on configure
When setting the whole configuration via --configure, list types like
adminKey need special handling. Previously this failed as a list cannot
be appended to a list. The new code adds dedicated logic to replace the
repeated value when passing a list. Also, all items of that list are
converted into the correct (typed) form before setting them.
2024-10-25 17:11:06 +02:00
Felix Moessbauer
6ceae7c72f setPref: pass typed value instead of string
By passing a typed value we can conserve the type information to later
use that to convert it back into the correct protobuf type. The type
conversion is now done inside setPref.
2024-10-25 17:11:06 +02:00
Felix Moessbauer
4c29d7dd0f refactor camel and snake naming in setPref
Same change as done in getPerf to have less branches (simplifies the
code).
2024-10-25 17:11:06 +02:00
Felix Moessbauer
839bbbcad2 config: correctly print byte and array types on get
When getting config values of type bytes or list (technically a protobuf
repeated container type), these were directly printed on the output.
However, the retrieved values could not be set by --set again, as the
format was different (e.g. python string representation of bytes vs.
base64 prefixed and encoded as expected by --set).

We fix this by adding a toStr utility function (similar to the fromStr)
function to convert byte types correctly to the base64 representation.
Further, we check if the type is repeated and apply this operation to
all values.
2024-10-25 17:11:06 +02:00
Felix Moessbauer
1abb9fb213 refactor getPref to use uniform printing logic
We add a local helper function to print a single setting. This is a
preparation to correctly print non-trivial types. The existing code
in getPref is ported over to use that function. By that, the output
of "wholeField" is changed slightly to always print the full path for
each setting (e.g. "security.serialEnabled" instead of
"security:\nserialEnabled"). This improves support for grepping on the
output.
2024-10-25 17:11:06 +02:00
Felix Moessbauer
7fcbbe9b80 refactor camel and snake naming in getPref
We have a lot of code duplication by checkin over and over again if
field should be named in camel or snake notation. We simplify this by
writing the choosen variant into a variable and just use that name
across the code.

No functional change.
2024-10-25 14:13:50 +02:00
Eric Swanson
073274cb00 Add missing camel to snake conversion 2024-10-21 22:06:43 -04:00
Ian McEwen
92a3986a8f Improve comments for pdoc 2024-10-21 16:39:47 -07:00
github-actions
f08ec1885b bump version to 2.5.3 2024-10-18 17:02:53 +00:00
Ian McEwen
feca49faed Merge pull request #696 from fmoessbauer/master
fix base64 encoding of key field in config
2024-10-18 09:57:13 -07:00
Felix Moessbauer
dfaf1a275d fix base64 encoding of key field in config
The security.privateKey and security.publicKey fields are of type bytes,
but the protobuf MessageToDict converts them to base64 encoded strings.
When importing the config again, this is read as a string, which breaks
the import. Instead, the value needs to be prefixed with "base64:", so
the type handling logic on import kicks in and decodes the value to a
bytes array again.

Fixes: #678
2024-10-18 16:13:25 +02:00
Ian McEwen
da7fa31805 tweak documentation formatting 2024-10-16 20:52:10 -07:00
Ian McEwen
12fd29b203 Merge pull request #694 from ianmcorvidae/configure-fixed-position
Use dedicated fixed position admin message for --configure
2024-10-16 16:01:06 -07:00
Ian McEwen
a43dd201ba Merge pull request #695 from ianmcorvidae/ble-disconnect
Send the meshtastic.connection.lost message from BLEInterface's close method
2024-10-16 16:00:49 -07:00
Ian McEwen
a64a9d203a Send the meshtastic.connection.lost message from BLEInterface's close method 2024-10-16 12:13:30 -07:00
Ian McEwen
10136962d7 Use dedicated fixed position admin message for --configure 2024-10-15 07:28:55 -07:00
Ian McEwen
3afb294f9b Merge pull request #691 from logikal/telemetry_output
autoprint other types of telemetry when returned from --request-telemetry
2024-10-14 17:11:00 -07:00
Sean Kilgore
ee405fec41 autoprint other types of telemetry when returned from --request-telemetry 2024-10-14 16:58:52 -07:00
Ian McEwen
3eabaf91d0 Merge pull request #687 from ianmcorvidae/telemetry-types
Support requesting different telemetry types
2024-10-13 21:50:04 -07:00
Ian McEwen
78b92cecc9 fix type check 2024-10-13 20:40:22 -07:00
Ian McEwen
7088b90514 Support requesting different telemetry types 2024-10-13 20:35:11 -07:00
Ian McEwen
2ae81f8602 Merge pull request #686 from jose1711/typofix
Fix typo.
2024-10-13 18:00:16 -07:00
Jose Riha
923f5e82d0 Fix typo. 2024-10-14 02:48:41 +02:00
Ian McEwen
05731128fa missed a spot 2024-10-12 12:52:36 -07:00
Ian McEwen
0523d4c94f disable R0917 pylint failures 2024-10-12 12:49:14 -07:00
Ian McEwen
90e901de79 Upgrade bleak and therefore also the supported python versions 2024-10-12 12:32:43 -07:00
Ian McEwen
6606851135 Merge pull request #685 from ianmcorvidae/more-telemetry
Add other telemetry variants to automatic handling/adding to node information
2024-10-12 11:59:45 -07:00
Ian McEwen
33fecbd74d Add other telemetry variants to automatic handling/adding to node information 2024-10-12 11:56:55 -07:00
Ian McEwen
6b9db7abd9 2.5.3 setup 2024-10-12 09:33:28 -07:00
github-actions
ece6286d82 bump version to 2.5.2 2024-10-12 16:31:48 +00:00
William Stearns
e335f12a3b attempts to fix mypy issues 2024-10-11 00:59:02 -04:00
William Stearns
0da405168f pylint cleanups 2024-10-10 23:49:20 -04:00
William Stearns
58d9039a04 another missing import 2024-10-10 16:49:06 -04:00
William Stearns
f77e788aa8 fix missing import 2024-10-10 16:33:07 -04:00
William Stearns
aba381fb54 Merge branch 'master' into wls_add_types 2024-10-08 23:28:25 -04:00
Ian McEwen
0bb4b31b6a Merge pull request #679 from ianmcorvidae/argument-groups
Add a number of argument groups to organize the help output
2024-10-03 19:45:13 -07:00
Ian McEwen
915066e0af add metavars for a bunch of arguments for nicer docs 2024-10-03 19:45:03 -07:00
Ian McEwen
6be3969577 Add a number of argument groups to organize the help output 2024-10-01 18:10:14 -07:00
Ian McEwen
b73cc1f499 Make it so wantconfig isn't a 1 in 4294967296 lottery for getting no nodes 2024-10-01 14:00:06 -07:00
Ian McEwen
65305af184 protobufs/alpha version: 2.5.2 2024-09-29 14:34:53 -07:00
github-actions
3fb1e67357 bump version to 2.5.1 2024-09-29 21:30:36 +00:00
Ian McEwen
cbd3c119fe Fix pylint errors 2024-09-28 20:15:08 -07:00
Ian McEwen
bbd6d6a541 Change order of logging and parsing fromRadioBytes, and add a bit more traceback logging at that point 2024-09-28 20:11:52 -07:00
Ian McEwen
6e1217c7ca Merge pull request #677 from ianmcorvidae/pkiencrypted_admin
Default to pkiEncrypted always on for admin messages
2024-09-28 18:40:31 -07:00
Ian McEwen
81db38956b Silence pylint 2024-09-28 18:37:05 -07:00
Ian McEwen
27729995d2 Default to pkiEncrypted always on for admin messages 2024-09-28 11:13:04 -07:00
Ian McEwen
d875a574b6 Merge pull request #676 from ianmcorvidae/set-time
Add a --set-time command that set's the node time using a provided timestamp or the host system clock
2024-09-22 10:06:09 -07:00
Ian McEwen
40019a9712 Add a --set-time command that set's the node time using a provided timestamp or the host system clock. 2024-09-22 09:32:04 -07:00
Ian McEwen
de657bab24 Merge pull request #675 from djholt/feature/remote-config-position
Enable setting and removing fixed position via remote admin
2024-09-19 11:24:23 -07:00
DJ Holt
40353a387e Fix tests for remote position configs 2024-09-19 03:31:01 -06:00
DJ Holt
9949d144a1 Enable setting and removing fixed position via remote admin 2024-09-19 02:39:49 -06:00
Ian McEwen
48a06c6e1e protobufs & alpha version number: v2.5.1 2024-09-18 14:58:05 -07:00
Ian McEwen
c696d59b90 Set list-type keys differently, excluding 0-like values and resetting whole list 2024-09-17 21:52:57 -07:00
Ian McEwen
4fdbcb9679 Fix test_fuzz_fromStr for floats 2024-09-17 21:10:57 -07:00
Ian McEwen
5d6dfb877b Merge pull request #673 from djholt/feature/tcp-port
Allow port number to be specified with tcp hostname
2024-09-17 21:08:03 -07:00
Ian McEwen
951edfe27b Merge pull request #665 from lysol/remote-admin-retry
Retry admin channel setting retrieval and add configurable timeout
2024-09-17 21:06:38 -07:00
DJ Holt
5cc9627e21 Refactor default port number to variable 2024-09-16 23:41:44 -06:00
DJ Holt
bf904c6906 Allow port number to be specified with tcp hostname 2024-09-16 23:24:03 -06:00
Derek Arnold
2026212a00 please pylint with a docstring and newline 2024-09-15 16:27:40 -05:00
Derek Arnold
34f9be255e fix unrelated bug when fromStr input is short hex
For example, 0x0 will generate an unhandled ValueError. This was caught
during a random unit test run for 3.9, so I figure I'd fix it.

This is unrelated to the PR otherwise.
2024-09-15 16:20:50 -05:00
Derek Arnold
e561222ea7 add docstrings 2024-09-15 16:06:44 -05:00
Derek Arnold
73a1bbc7d5 add test coverage for changes to requestChannels 2024-09-15 16:02:59 -05:00
Derek Arnold
8f2c397fbf missed one reference for requestChannelRetries 🤡 2024-09-15 14:01:41 -05:00
Derek Arnold
62f5201a38 Add test covering retry logic 2024-09-15 12:04:22 -05:00
Derek Arnold
9e7d5e96ab Rename "retries" to "attempts"
Otherwise, semantically, it's off-by-one.
2024-09-15 12:03:51 -05:00
Derek Arnold
aa74db46cb update message check in test to reflect new message 2024-09-15 11:59:03 -05:00
Derek Arnold
1967519deb correct type issue during initial assignment 2024-09-15 11:52:27 -05:00
Derek Arnold
662aea049a Merge branch 'master' into remote-admin-retry 2024-09-15 11:46:32 -05:00
Derek Arnold
44cfd72a80 make sure new_index is always an int 2024-09-15 11:39:10 -05:00
Derek Arnold
abe1dd47ca add type to argument 2024-09-15 11:36:23 -05:00
Ian McEwen
584a14f578 Merge pull request #668 from ianmcorvidae/channel-preset-optimize
Change modem preset shortcuts to not request channels, and to request remote config when needed
2024-09-12 16:20:59 -07:00
Ian McEwen
8ba92da7cf Change modem preset shortcuts to not request channels, and to request remote config when needed 2024-09-06 23:38:52 -07:00
Derek Arnold
3811226a61 add a configurable timeout 2024-09-03 22:12:03 -05:00
Derek Arnold
b6547c9737 actually link up the retry args from the commandline to getNode 2024-09-03 22:11:52 -05:00
Derek Arnold
9612aea9b9 Add in a retry mechanism for channel settings
Attempts multiple times to fetch things over the admin channel
before giving up.
2024-09-03 21:58:16 -05:00
William Stearns
60de9dddb1 Remove references to BLEClient breaking CI checks 2024-07-09 19:54:01 -04:00
William Stearns
a29ee840f2 Adding mypy typing 2024-06-15 23:22:43 -04:00
33 changed files with 2806 additions and 2120 deletions

View File

@@ -16,7 +16,7 @@ Events are delivered using a publish-subscribe model, and you can subscribe to o
**[Getting Started Guide](https://meshtastic.org/docs/software/python/cli/installation)** **[Getting Started Guide](https://meshtastic.org/docs/software/python/cli/installation)**
(Documentation/API Reference is currently offline) **[API Documentation](https://python.meshtastic.org)**
## Call for Contributors ## Call for Contributors

View File

@@ -2,41 +2,44 @@
# A library for the Meshtastic Client API # A library for the Meshtastic Client API
Primary interfaces: SerialInterface, TCPInterface, BLEInterface 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)
notable properties of interface classes: notable properties of interface classes:
- 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. As such, includes "unknown" nodes which haven't seen a User packet yet
- myInfo & metadata - Contain 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 - `localNode` - Pointer to a node object for the local node
notable properties of nodes: 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. - `localConfig` - Current radio settings, can be written to the radio with the `writeConfig` method.
- channels - The node's channels, keyed by index. - `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
We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available
topics: topics:
- meshtastic.connection.established - published once we've successfully connected to the radio and downloaded the node DB - `meshtastic.connection.established` - published once we've successfully connected to the radio and downloaded the node DB
- meshtastic.connection.lost - published once we've lost our link to the radio - `meshtastic.connection.lost` - published once we've lost our link to the radio
- meshtastic.receive.text(packet) - delivers a received packet as a dictionary, if you only care about a particular - `meshtastic.receive.text(packet)` - delivers a received packet as a dictionary, if you only care about a particular
type of packet, you should subscribe to the full topic name. If you want to see all packets, simply subscribe to "meshtastic.receive". type of packet, you should subscribe to the full topic name. If you want to see all packets, simply subscribe to "meshtastic.receive".
- meshtastic.receive.position(packet) - `meshtastic.receive.position(packet)`
- meshtastic.receive.user(packet) - `meshtastic.receive.user(packet)`
- meshtastic.receive.data.portnum(packet) (where portnum is an integer or well known PortNum enum) - `meshtastic.receive.data.portnum(packet)` (where portnum is an integer or well known PortNum enum)
- meshtastic.node.updated(node = NodeInfo) - published when a node in the DB changes (appears, location changed, username changed, etc...) - `meshtastic.node.updated(node = NodeInfo)` - published when a node in the DB changes (appears, location changed, username changed, etc...)
- meshtastic.log.line(line) - a raw unparsed log line from the radio - `meshtastic.log.line(line)` - a raw unparsed log line from the radio
We receive position, user, or data packets from the mesh. You probably only care about meshtastic.receive.data. The first argument for We receive position, user, or data packets from the mesh. You probably only care about `meshtastic.receive.data`. The first argument for
that publish will be the packet. Text or binary data packets (from sendData or sendText) will both arrive this way. If you print packet that publish will be the packet. Text or binary data packets (from `sendData` or `sendText`) will both arrive this way. If you print packet
you'll see the fields in the dictionary. decoded.data.payload will contain the raw bytes that were sent. If the packet was sent with you'll see the fields in the dictionary. `decoded.data.payload` will contain the raw bytes that were sent. If the packet was sent with
sendText, decoded.data.text will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for `sendText`, `decoded.data.text` will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for
unicode scripts they can be different. unicode scripts they can be different.
# Example Usage # Example Usage
@@ -108,13 +111,13 @@ from . import (
LOCAL_ADDR = "^local" LOCAL_ADDR = "^local"
"""A special ID that means the local node""" """A special ID that means the local node"""
BROADCAST_NUM = 0xFFFFFFFF BROADCAST_NUM: int = 0xFFFFFFFF
"""if using 8 bit nodenums this will be shortened on the target""" """if using 8 bit nodenums this will be shortened on the target"""
BROADCAST_ADDR = "^all" BROADCAST_ADDR = "^all"
"""A special ID that means broadcast""" """A special ID that means broadcast"""
OUR_APP_VERSION = 20300 OUR_APP_VERSION: int = 20300
"""The numeric buildnumber (shared with android apps) specifying the """The numeric buildnumber (shared with android apps) specifying the
level of device code we are guaranteed to understand level of device code we are guaranteed to understand
@@ -131,7 +134,9 @@ class ResponseHandler(NamedTuple):
"""A pending response callback, waiting for a response to one of our messages""" """A pending response callback, waiting for a response to one of our messages"""
# requestId: int - used only as a key # requestId: int - used only as a key
#: a callable to call when a response is received
callback: Callable callback: Callable
#: Whether ACKs and NAKs should be passed to this handler
ackPermitted: bool = False ackPermitted: bool = False
# FIXME, add timestamp and age out old requests # FIXME, add timestamp and age out old requests
@@ -139,11 +144,11 @@ class ResponseHandler(NamedTuple):
class KnownProtocol(NamedTuple): class KnownProtocol(NamedTuple):
"""Used to automatically decode known protocol payloads""" """Used to automatically decode known protocol payloads"""
#: A descriptive name (e.g. "text", "user", "admin")
name: str name: str
# portnum: int, now a key #: If set, will be called to parse as a protocol buffer
# If set, will be called to prase as a protocol buffer
protobufFactory: Optional[Callable] = None protobufFactory: Optional[Callable] = None
# If set, invoked as onReceive(interface, packet) #: If set, invoked as onReceive(interface, packet)
onReceive: Optional[Callable] = None onReceive: Optional[Callable] = None
@@ -194,13 +199,31 @@ def _onNodeInfoReceive(iface, asDict):
def _onTelemetryReceive(iface, asDict): def _onTelemetryReceive(iface, asDict):
"""Automatically update device metrics on received packets""" """Automatically update device metrics on received packets"""
logging.debug(f"in _onTelemetryReceive() asDict:{asDict}") logging.debug(f"in _onTelemetryReceive() asDict:{asDict}")
deviceMetrics = asDict.get("decoded", {}).get("telemetry", {}).get("deviceMetrics") if "from" not in asDict:
if "from" in asDict and deviceMetrics is not None: return
node = iface._getOrCreateByNum(asDict["from"])
newMetrics = node.get("deviceMetrics", {}) toUpdate = None
newMetrics.update(deviceMetrics)
logging.debug(f"updating metrics for {asDict['from']} to {newMetrics}") telemetry = asDict.get("decoded", {}).get("telemetry", {})
node["deviceMetrics"] = newMetrics node = iface._getOrCreateByNum(asDict["from"])
if "deviceMetrics" in telemetry:
toUpdate = "deviceMetrics"
elif "environmentMetrics" in telemetry:
toUpdate = "environmentMetrics"
elif "airQualityMetrics" in telemetry:
toUpdate = "airQualityMetrics"
elif "powerMetrics" in telemetry:
toUpdate = "powerMetrics"
elif "localStats" in telemetry:
toUpdate = "localStats"
else:
return
updateObj = telemetry.get(toUpdate)
newMetrics = node.get(toUpdate, {})
newMetrics.update(updateObj)
logging.debug(f"updating {toUpdate} metrics for {asDict['from']} to {newMetrics}")
node[toUpdate] = newMetrics
def _receiveInfoUpdate(iface, asDict): def _receiveInfoUpdate(iface, asDict):
if "from" in asDict: if "from" in asDict:

View File

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,7 @@ import atexit
import logging import logging
import struct import struct
import time import time
import io
from threading import Thread from threading import Thread
from typing import List, Optional from typing import List, Optional
@@ -34,9 +35,9 @@ class BLEInterface(MeshInterface):
self, self,
address: Optional[str], address: Optional[str],
noProto: bool = False, noProto: bool = False,
debugOut=None, debugOut: Optional[io.TextIOWrapper]=None,
noNodes: bool = False, noNodes: bool = False,
): ) -> None:
MeshInterface.__init__( MeshInterface.__init__(
self, debugOut=debugOut, noProto=noProto, noNodes=noNodes self, debugOut=debugOut, noProto=noProto, noNodes=noNodes
) )
@@ -82,7 +83,7 @@ class BLEInterface(MeshInterface):
# Note: the on disconnected callback will call our self.close which will make us nicely wait for threads to exit # Note: the on disconnected callback will call our self.close which will make us nicely wait for threads to exit
self._exit_handler = atexit.register(self.client.disconnect) self._exit_handler = atexit.register(self.client.disconnect)
def from_num_handler(self, _, b): # pylint: disable=C0116 def from_num_handler(self, _, b: bytes) -> None: # pylint: disable=C0116
"""Handle callbacks for fromnum notify. """Handle callbacks for fromnum notify.
Note: this method does not need to be async because it is just setting a bool. Note: this method does not need to be async because it is just setting a bool.
""" """
@@ -150,9 +151,12 @@ class BLEInterface(MeshInterface):
) )
return addressed_devices[0] return addressed_devices[0]
def _sanitize_address(address): # pylint: disable=E0213 def _sanitize_address(self, address: Optional[str]) -> Optional[str]: # pylint: disable=E0213
"Standardize BLE address by removing extraneous characters and lowercasing." "Standardize BLE address by removing extraneous characters and lowercasing."
return address.replace("-", "").replace("_", "").replace(":", "").lower() if address is None:
return None
else:
return address.replace("-", "").replace("_", "").replace(":", "").lower()
def connect(self, address: Optional[str] = None) -> "BLEClient": def connect(self, address: Optional[str] = None) -> "BLEClient":
"Connect to a device by address." "Connect to a device by address."
@@ -164,12 +168,16 @@ class BLEInterface(MeshInterface):
client.discover() client.discover()
return client return client
def _receiveFromRadioImpl(self): def _receiveFromRadioImpl(self) -> None:
while self._want_receive: while self._want_receive:
if self.should_read: if self.should_read:
self.should_read = False self.should_read = False
retries = 0 retries: int = 0
while self._want_receive: while self._want_receive:
if self.client is None:
logging.debug(f"BLE client is None, shutting down")
self._want_receive = False
continue
try: try:
b = bytes(self.client.read_gatt_char(FROMRADIO_UUID)) b = bytes(self.client.read_gatt_char(FROMRADIO_UUID))
except BleakDBusError as e: except BleakDBusError as e:
@@ -194,8 +202,8 @@ class BLEInterface(MeshInterface):
else: else:
time.sleep(0.01) time.sleep(0.01)
def _sendToRadioImpl(self, toRadio): def _sendToRadioImpl(self, toRadio) -> None:
b = toRadio.SerializeToString() b: bytes = toRadio.SerializeToString()
if b and self.client: # we silently ignore writes while we are shutting down if b and self.client: # we silently ignore writes while we are shutting down
logging.debug(f"TORADIO write: {b.hex()}") logging.debug(f"TORADIO write: {b.hex()}")
try: try:
@@ -211,14 +219,14 @@ class BLEInterface(MeshInterface):
time.sleep(0.01) time.sleep(0.01)
self.should_read = True self.should_read = True
def close(self): def close(self) -> None:
try: try:
MeshInterface.close(self) MeshInterface.close(self)
except Exception as e: except Exception as e:
logging.error(f"Error closing mesh interface: {e}") logging.error(f"Error closing mesh interface: {e}")
if self._want_receive: if self._want_receive:
self.want_receive = False # Tell the thread we want it to stop self._want_receive = False # Tell the thread we want it to stop
if self._receiveThread: if self._receiveThread:
self._receiveThread.join( self._receiveThread.join(
timeout=2 timeout=2
@@ -230,12 +238,13 @@ class BLEInterface(MeshInterface):
self.client.disconnect() self.client.disconnect()
self.client.close() self.client.close()
self.client = None self.client = None
self._disconnected() # send the disconnected indicator up to clients
class BLEClient: class BLEClient:
"""Client for managing connection to a BLE device""" """Client for managing connection to a BLE device"""
def __init__(self, address=None, **kwargs): def __init__(self, address=None, **kwargs) -> None:
self._eventLoop = asyncio.new_event_loop() self._eventLoop = asyncio.new_event_loop()
self._eventThread = Thread( self._eventThread = Thread(
target=self._run_event_loop, name="BLEClient", daemon=True target=self._run_event_loop, name="BLEClient", daemon=True

View File

@@ -1,5 +1,6 @@
"""Mesh Interface class """Mesh Interface class
""" """
# pylint: disable=R0917
import collections import collections
import json import json
@@ -8,6 +9,7 @@ import random
import sys import sys
import threading import threading
import time import time
import traceback
from datetime import datetime from datetime import datetime
from decimal import Decimal from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Union from typing import Any, Callable, Dict, List, Optional, Union
@@ -139,13 +141,13 @@ class MeshInterface: # pylint: disable=R0902
def __enter__(self): def __enter__(self):
return self return self
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, trace):
if exc_type is not None and exc_value is not None: if exc_type is not None and exc_value is not None:
logging.error( logging.error(
f"An exception of type {exc_type} with value {exc_value} has occurred" f"An exception of type {exc_type} with value {exc_value} has occurred"
) )
if traceback is not None: if trace is not None:
logging.error(f"Traceback: {traceback}") logging.error(f"Traceback: {trace}")
self.close() self.close()
@staticmethod @staticmethod
@@ -315,19 +317,33 @@ class MeshInterface: # pylint: disable=R0902
return table return table
def getNode( def getNode(
self, nodeId: str, requestChannels: bool = True self, nodeId: str, requestChannels: bool = True, requestChannelAttempts: int = 3, timeout: int = 300
) -> meshtastic.node.Node: ) -> meshtastic.node.Node:
"""Return a node object which contains device settings and channel info""" """Return a node object which contains device settings and channel info"""
if nodeId in (LOCAL_ADDR, BROADCAST_ADDR): if nodeId in (LOCAL_ADDR, BROADCAST_ADDR):
return self.localNode return self.localNode
else: else:
n = meshtastic.node.Node(self, nodeId) n = meshtastic.node.Node(self, nodeId, timeout=timeout)
# Only request device settings and channel info when necessary # Only request device settings and channel info when necessary
if requestChannels: if requestChannels:
logging.debug("About to requestChannels") logging.debug("About to requestChannels")
n.requestChannels() n.requestChannels()
if not n.waitForConfig(): retries_left = requestChannelAttempts
our_exit("Error: Timed out waiting for channels") last_index: int = 0
while retries_left > 0:
retries_left -= 1
if not n.waitForConfig():
new_index: int = len(n.partialChannels) if n.partialChannels else 0
# each time we get a new channel, reset the counter
if new_index != last_index:
retries_left = requestChannelAttempts - 1
if retries_left <= 0:
our_exit(f"Error: Timed out waiting for channels, giving up")
print("Timed out trying to retrieve channel info, retrying")
n.requestChannels(startingIndex=new_index)
last_index = new_index
else:
break
return n return n
def sendText( def sendText(
@@ -380,7 +396,9 @@ class MeshInterface: # pylint: disable=R0902
onResponseAckPermitted: bool=False, onResponseAckPermitted: bool=False,
channelIndex: int=0, channelIndex: int=0,
hopLimit: Optional[int]=None, hopLimit: Optional[int]=None,
): pkiEncrypted: Optional[bool]=False,
publicKey: Optional[bytes]=None,
): # pylint: disable=R0913
"""Send a data packet to some other node """Send a data packet to some other node
Keyword Arguments: Keyword Arguments:
@@ -435,7 +453,7 @@ class MeshInterface: # pylint: disable=R0902
if onResponse is not None: if onResponse is not None:
logging.debug(f"Setting a response handler for requestId {meshPacket.id}") logging.debug(f"Setting a response handler for requestId {meshPacket.id}")
self._addResponseHandler(meshPacket.id, onResponse, ackPermitted=onResponseAckPermitted) self._addResponseHandler(meshPacket.id, onResponse, ackPermitted=onResponseAckPermitted)
p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck, hopLimit=hopLimit) p = self._sendPacket(meshPacket, destinationId, wantAck=wantAck, hopLimit=hopLimit, pkiEncrypted=pkiEncrypted, publicKey=publicKey)
return p return p
def sendPosition( def sendPosition(
@@ -588,32 +606,38 @@ class MeshInterface: # pylint: disable=R0902
destinationId: Union[int, str] = BROADCAST_ADDR, destinationId: Union[int, str] = BROADCAST_ADDR,
wantResponse: bool = False, wantResponse: bool = False,
channelIndex: int = 0, channelIndex: int = 0,
telemetryType: str = "device_metrics"
): ):
"""Send telemetry and optionally ask for a response""" """Send telemetry and optionally ask for a response"""
r = telemetry_pb2.Telemetry() r = telemetry_pb2.Telemetry()
if self.nodes is not None: if telemetryType == "environment_metrics":
node = next( r.environment_metrics.CopyFrom(telemetry_pb2.EnvironmentMetrics())
n for n in self.nodes.values() if n["num"] == self.localNode.nodeNum elif telemetryType == "air_quality_metrics":
) r.air_quality_metrics.CopyFrom(telemetry_pb2.AirQualityMetrics())
if node is not None: elif telemetryType == "power_metrics":
metrics = node.get("deviceMetrics") r.power_metrics.CopyFrom(telemetry_pb2.PowerMetrics())
if metrics: else: # fall through to device metrics
batteryLevel = metrics.get("batteryLevel") if self.nodesByNum is not None:
if batteryLevel is not None: node = self.nodesByNum.get(self.localNode.nodeNum)
r.device_metrics.battery_level = batteryLevel if node is not None:
voltage = metrics.get("voltage") metrics = node.get("deviceMetrics")
if voltage is not None: if metrics:
r.device_metrics.voltage = voltage batteryLevel = metrics.get("batteryLevel")
channel_utilization = metrics.get("channelUtilization") if batteryLevel is not None:
if channel_utilization is not None: r.device_metrics.battery_level = batteryLevel
r.device_metrics.channel_utilization = channel_utilization voltage = metrics.get("voltage")
air_util_tx = metrics.get("airUtilTx") if voltage is not None:
if air_util_tx is not None: r.device_metrics.voltage = voltage
r.device_metrics.air_util_tx = air_util_tx channel_utilization = metrics.get("channelUtilization")
uptime_seconds = metrics.get("uptimeSeconds") if channel_utilization is not None:
if uptime_seconds is not None: r.device_metrics.channel_utilization = channel_utilization
r.device_metrics.uptime_seconds = uptime_seconds air_util_tx = metrics.get("airUtilTx")
if air_util_tx is not None:
r.device_metrics.air_util_tx = air_util_tx
uptime_seconds = metrics.get("uptimeSeconds")
if uptime_seconds is not None:
r.device_metrics.uptime_seconds = uptime_seconds
if wantResponse: if wantResponse:
onResponse = self.onResponseTelemetry onResponse = self.onResponseTelemetry
@@ -637,22 +661,32 @@ class MeshInterface: # pylint: disable=R0902
self._acknowledgment.receivedTelemetry = True self._acknowledgment.receivedTelemetry = True
telemetry = telemetry_pb2.Telemetry() telemetry = telemetry_pb2.Telemetry()
telemetry.ParseFromString(p["decoded"]["payload"]) telemetry.ParseFromString(p["decoded"]["payload"])
print("Telemetry received:") print("Telemetry received:")
if telemetry.device_metrics.battery_level is not None: # Check if the telemetry message has the device_metrics field
print(f"Battery level: {telemetry.device_metrics.battery_level:.2f}%") # This is the original code that was the default for --request-telemetry and is kept for compatibility
if telemetry.device_metrics.voltage is not None: if telemetry.HasField("device_metrics"):
print(f"Voltage: {telemetry.device_metrics.voltage:.2f} V") if telemetry.device_metrics.battery_level is not None:
if telemetry.device_metrics.channel_utilization is not None: print(f"Battery level: {telemetry.device_metrics.battery_level:.2f}%")
print( if telemetry.device_metrics.voltage is not None:
f"Total channel utilization: {telemetry.device_metrics.channel_utilization:.2f}%" print(f"Voltage: {telemetry.device_metrics.voltage:.2f} V")
) if telemetry.device_metrics.channel_utilization is not None:
if telemetry.device_metrics.air_util_tx is not None: print(
print( f"Total channel utilization: {telemetry.device_metrics.channel_utilization:.2f}%"
f"Transmit air utilization: {telemetry.device_metrics.air_util_tx:.2f}%" )
) if telemetry.device_metrics.air_util_tx is not None:
if telemetry.device_metrics.uptime_seconds is not None: print(
print(f"Uptime: {telemetry.device_metrics.uptime_seconds} s") f"Transmit air utilization: {telemetry.device_metrics.air_util_tx:.2f}%"
)
if telemetry.device_metrics.uptime_seconds is not None:
print(f"Uptime: {telemetry.device_metrics.uptime_seconds} s")
else:
# this is the new code if --request-telemetry <type> is used.
telemetry_dict = google.protobuf.json_format.MessageToDict(telemetry)
for key, value in telemetry_dict.items():
if key != "time": # protobuf includes a time field that we don't print for device_metrics.
print(f"{key}:")
for sub_key, sub_value in value.items():
print(f" {sub_key}: {sub_value}")
elif p["decoded"]["portnum"] == "ROUTING_APP": elif p["decoded"]["portnum"] == "ROUTING_APP":
if p["decoded"]["routing"]["errorReason"] == "NO_RESPONSE": if p["decoded"]["routing"]["errorReason"] == "NO_RESPONSE":
@@ -675,7 +709,9 @@ class MeshInterface: # pylint: disable=R0902
meshPacket: mesh_pb2.MeshPacket, meshPacket: mesh_pb2.MeshPacket,
destinationId: Union[int,str]=BROADCAST_ADDR, destinationId: Union[int,str]=BROADCAST_ADDR,
wantAck: bool=False, wantAck: bool=False,
hopLimit: Optional[int]=None hopLimit: Optional[int]=None,
pkiEncrypted: Optional[bool]=False,
publicKey: Optional[bytes]=None,
): ):
"""Send a MeshPacket to the specified node (or if unspecified, broadcast). """Send a MeshPacket to the specified node (or if unspecified, broadcast).
You probably don't want this - use sendData instead. You probably don't want this - use sendData instead.
@@ -724,6 +760,12 @@ class MeshInterface: # pylint: disable=R0902
loraConfig = getattr(self.localNode.localConfig, "lora") loraConfig = getattr(self.localNode.localConfig, "lora")
meshPacket.hop_limit = getattr(loraConfig, "hop_limit") meshPacket.hop_limit = getattr(loraConfig, "hop_limit")
if pkiEncrypted:
meshPacket.pki_encrypted = True
if publicKey is not None:
meshPacket.public_key = publicKey
# if the user hasn't set an ID for this packet (likely and recommended), # if the user hasn't set an ID for this packet (likely and recommended),
# we should pick a new unique ID so the message can be tracked. # we should pick a new unique ID so the message can be tracked.
if meshPacket.id == 0: if meshPacket.id == 0:
@@ -889,6 +931,8 @@ class MeshInterface: # pylint: disable=R0902
startConfig = mesh_pb2.ToRadio() startConfig = mesh_pb2.ToRadio()
if self.configId is None or not self.noNodes: if self.configId is None or not self.noNodes:
self.configId = random.randint(0, 0xFFFFFFFF) self.configId = random.randint(0, 0xFFFFFFFF)
if self.configId == NODELESS_WANT_CONFIG_ID:
self.configId = self.configId + 1
startConfig.want_config_id = self.configId startConfig.want_config_id = self.configId
self._sendToRadio(startConfig) self._sendToRadio(startConfig)
@@ -999,10 +1043,17 @@ class MeshInterface: # pylint: disable=R0902
Called by subclasses.""" Called by subclasses."""
fromRadio = mesh_pb2.FromRadio() fromRadio = mesh_pb2.FromRadio()
fromRadio.ParseFromString(fromRadioBytes)
logging.debug( logging.debug(
f"in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}" f"in mesh_interface.py _handleFromRadio() fromRadioBytes: {fromRadioBytes}"
) )
try:
fromRadio.ParseFromString(fromRadioBytes)
except Exception as ex:
logging.error(
f"Error while parsing FromRadio bytes:{fromRadioBytes} {ex}"
)
traceback.print_exc()
raise ex
asDict = google.protobuf.json_format.MessageToDict(fromRadio) asDict = google.protobuf.json_format.MessageToDict(fromRadio)
logging.debug(f"Received from radio: {fromRadio}") logging.debug(f"Received from radio: {fromRadio}")
if fromRadio.HasField("my_info"): if fromRadio.HasField("my_info"):

View File

@@ -13,6 +13,8 @@ with rather more easily once the code is simplified by this change.
""" """
from typing import Any, Optional
def reset(): def reset():
""" """
Restore the namespace to pristine condition. Restore the namespace to pristine condition.
@@ -33,5 +35,5 @@ args = None
parser = None parser = None
channel_index = None channel_index = None
logfile = None logfile = None
tunnelInstance = None tunnelInstance: Optional[Any] = None
camel_case = False camel_case = False

View File

@@ -5,7 +5,7 @@ import base64
import logging import logging
import time import time
from typing import Optional, Union from typing import Optional, Union, List
from meshtastic.protobuf import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, mesh_pb2, portnums_pb2 from meshtastic.protobuf import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, mesh_pb2, portnums_pb2
from meshtastic.util import ( from meshtastic.util import (
@@ -25,15 +25,15 @@ class Node:
Includes methods for localConfig, moduleConfig and channels Includes methods for localConfig, moduleConfig and channels
""" """
def __init__(self, iface, nodeNum, noProto=False): def __init__(self, iface, nodeNum, noProto=False, timeout: int = 300):
"""Constructor""" """Constructor"""
self.iface = iface self.iface = iface
self.nodeNum = nodeNum self.nodeNum = nodeNum
self.localConfig = localonly_pb2.LocalConfig() self.localConfig = localonly_pb2.LocalConfig()
self.moduleConfig = localonly_pb2.LocalModuleConfig() self.moduleConfig = localonly_pb2.LocalModuleConfig()
self.channels = None self.channels = None
self._timeout = Timeout(maxSecs=300) self._timeout = Timeout(maxSecs=timeout)
self.partialChannels = None self.partialChannels: Optional[List] = None
self.noProto = noProto self.noProto = noProto
self.cannedPluginMessage = None self.cannedPluginMessage = None
self.cannedPluginMessageMessages = None self.cannedPluginMessageMessages = None
@@ -77,13 +77,14 @@ class Node:
self.channels = channels self.channels = channels
self._fixupChannels() self._fixupChannels()
def requestChannels(self): def requestChannels(self, startingIndex: int = 0):
"""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}")
self.channels = None # only initialize if we're starting out fresh
self.partialChannels = [] # We keep our channels in a temp array until finished if startingIndex == 0:
self.channels = None
self._requestChannel(0) self.partialChannels = [] # We keep our channels in a temp array until finished
self._requestChannel(startingIndex)
def onResponseRequestSettings(self, p): def onResponseRequestSettings(self, p):
"""Handle the response packets for requesting settings _requestSettings()""" """Handle the response packets for requesting settings _requestSettings()"""
@@ -120,7 +121,7 @@ class Node:
) )
return return
if config_values is not None: if config_values is not None:
raw_config = getattr(getattr(adminMessage['raw'], oneof), field) raw_config = getattr(getattr(adminMessage['raw'], oneof), camel_to_snake(field))
config_values.CopyFrom(raw_config) config_values.CopyFrom(raw_config)
print(f"{str(camel_to_snake(field))}:\n{str(config_values)}") print(f"{str(camel_to_snake(field))}:\n{str(config_values)}")
@@ -684,9 +685,6 @@ class Node:
def setFixedPosition(self, lat: Union[int, float], lon: Union[int, float], alt: int): 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""" """Tell the node to set fixed position to the provided value and enable the fixed position setting"""
self.ensureSessionKey() self.ensureSessionKey()
if self != self.iface.localNode:
logging.error("Setting position of remote nodes is not supported.")
return None
p = mesh_pb2.Position() p = mesh_pb2.Position()
if isinstance(lat, float) and lat != 0.0: if isinstance(lat, float) and lat != 0.0:
@@ -704,7 +702,12 @@ class Node:
a = admin_pb2.AdminMessage() a = admin_pb2.AdminMessage()
a.set_fixed_position.CopyFrom(p) a.set_fixed_position.CopyFrom(p)
return self._sendAdmin(a)
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(a, onResponse=onResponse)
def removeFixedPosition(self): def removeFixedPosition(self):
"""Tell the node to remove the fixed position and set the fixed position setting to false""" """Tell the node to remove the fixed position and set the fixed position setting to false"""
@@ -713,7 +716,26 @@ class Node:
p.remove_fixed_position = True p.remove_fixed_position = True
logging.info(f"Telling node to remove fixed position") logging.info(f"Telling node to remove fixed position")
return self._sendAdmin(p) if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def setTime(self, timeSec: int = 0):
"""Tell the node to set its time to the provided timestamp, or the system's current time if not provided or 0."""
self.ensureSessionKey()
if timeSec == 0:
timeSec = int(time.time())
p = admin_pb2.AdminMessage()
p.set_time_only = timeSec
logging.info(f"Setting node time to {timeSec}")
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def _fixupChannels(self): def _fixupChannels(self):
"""Fixup indexes and add disabled channels as needed""" """Fixup indexes and add disabled channels as needed"""
@@ -872,6 +894,7 @@ class Node:
wantResponse=wantResponse, wantResponse=wantResponse,
onResponse=onResponse, onResponse=onResponse,
channelIndex=adminIndex, channelIndex=adminIndex,
pkiEncrypted=True,
) )
def ensureSessionKey(self): def ensureSessionKey(self):

View File

@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1emeshtastic/protobuf/atak.proto\x12\x13meshtastic.protobuf\"\x93\x02\n\tTAKPacket\x12\x15\n\ris_compressed\x18\x01 \x01(\x08\x12-\n\x07\x63ontact\x18\x02 \x01(\x0b\x32\x1c.meshtastic.protobuf.Contact\x12)\n\x05group\x18\x03 \x01(\x0b\x32\x1a.meshtastic.protobuf.Group\x12+\n\x06status\x18\x04 \x01(\x0b\x32\x1b.meshtastic.protobuf.Status\x12\'\n\x03pli\x18\x05 \x01(\x0b\x32\x18.meshtastic.protobuf.PLIH\x00\x12,\n\x04\x63hat\x18\x06 \x01(\x0b\x32\x1c.meshtastic.protobuf.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\"_\n\x05Group\x12-\n\x04role\x18\x01 \x01(\x0e\x32\x1f.meshtastic.protobuf.MemberRole\x12\'\n\x04team\x18\x02 \x01(\x0e\x32\x19.meshtastic.protobuf.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\x1emeshtastic/protobuf/atak.proto\x12\x13meshtastic.protobuf\"\xa5\x02\n\tTAKPacket\x12\x15\n\ris_compressed\x18\x01 \x01(\x08\x12-\n\x07\x63ontact\x18\x02 \x01(\x0b\x32\x1c.meshtastic.protobuf.Contact\x12)\n\x05group\x18\x03 \x01(\x0b\x32\x1a.meshtastic.protobuf.Group\x12+\n\x06status\x18\x04 \x01(\x0b\x32\x1b.meshtastic.protobuf.Status\x12\'\n\x03pli\x18\x05 \x01(\x0b\x32\x18.meshtastic.protobuf.PLIH\x00\x12,\n\x04\x63hat\x18\x06 \x01(\x0b\x32\x1c.meshtastic.protobuf.GeoChatH\x00\x12\x10\n\x06\x64\x65tail\x18\x07 \x01(\x0cH\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\"_\n\x05Group\x12-\n\x04role\x18\x01 \x01(\x0e\x32\x1f.meshtastic.protobuf.MemberRole\x12\'\n\x04team\x18\x02 \x01(\x0e\x32\x19.meshtastic.protobuf.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')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -21,20 +21,20 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.atak_pb
if _descriptor._USE_C_DESCRIPTORS == False: 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'
_globals['_TEAM']._serialized_start=703 _globals['_TEAM']._serialized_start=721
_globals['_TEAM']._serialized_end=895 _globals['_TEAM']._serialized_end=913
_globals['_MEMBERROLE']._serialized_start=897 _globals['_MEMBERROLE']._serialized_start=915
_globals['_MEMBERROLE']._serialized_end=1024 _globals['_MEMBERROLE']._serialized_end=1042
_globals['_TAKPACKET']._serialized_start=56 _globals['_TAKPACKET']._serialized_start=56
_globals['_TAKPACKET']._serialized_end=331 _globals['_TAKPACKET']._serialized_end=349
_globals['_GEOCHAT']._serialized_start=333 _globals['_GEOCHAT']._serialized_start=351
_globals['_GEOCHAT']._serialized_end=425 _globals['_GEOCHAT']._serialized_end=443
_globals['_GROUP']._serialized_start=427 _globals['_GROUP']._serialized_start=445
_globals['_GROUP']._serialized_end=522 _globals['_GROUP']._serialized_end=540
_globals['_STATUS']._serialized_start=524 _globals['_STATUS']._serialized_start=542
_globals['_STATUS']._serialized_end=549 _globals['_STATUS']._serialized_end=567
_globals['_CONTACT']._serialized_start=551 _globals['_CONTACT']._serialized_start=569
_globals['_CONTACT']._serialized_end=603 _globals['_CONTACT']._serialized_end=621
_globals['_PLI']._serialized_start=605 _globals['_PLI']._serialized_start=623
_globals['_PLI']._serialized_end=700 _globals['_PLI']._serialized_end=718
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -248,10 +248,16 @@ class TAKPacket(google.protobuf.message.Message):
STATUS_FIELD_NUMBER: builtins.int STATUS_FIELD_NUMBER: builtins.int
PLI_FIELD_NUMBER: builtins.int PLI_FIELD_NUMBER: builtins.int
CHAT_FIELD_NUMBER: builtins.int CHAT_FIELD_NUMBER: builtins.int
DETAIL_FIELD_NUMBER: builtins.int
is_compressed: builtins.bool is_compressed: builtins.bool
""" """
Are the payloads strings compressed for LoRA transport? Are the payloads strings compressed for LoRA transport?
""" """
detail: builtins.bytes
"""
Generic CoT detail XML
May be compressed / truncated by the sender
"""
@property @property
def contact(self) -> global___Contact: def contact(self) -> global___Contact:
""" """
@@ -291,10 +297,11 @@ class TAKPacket(google.protobuf.message.Message):
status: global___Status | None = ..., status: global___Status | None = ...,
pli: global___PLI | None = ..., pli: global___PLI | None = ...,
chat: global___GeoChat | None = ..., chat: global___GeoChat | None = ...,
detail: builtins.bytes = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["chat", b"chat", "contact", b"contact", "group", b"group", "payload_variant", b"payload_variant", "pli", b"pli", "status", b"status"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["chat", b"chat", "contact", b"contact", "detail", b"detail", "group", b"group", "payload_variant", b"payload_variant", "pli", b"pli", "status", b"status"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["chat", b"chat", "contact", b"contact", "group", b"group", "is_compressed", b"is_compressed", "payload_variant", b"payload_variant", "pli", b"pli", "status", b"status"]) -> None: ... def ClearField(self, field_name: typing.Literal["chat", b"chat", "contact", b"contact", "detail", b"detail", "group", b"group", "is_compressed", b"is_compressed", "payload_variant", b"payload_variant", "pli", b"pli", "status", b"status"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["pli", "chat"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["pli", "chat", "detail"] | None: ...
global___TAKPacket = TAKPacket global___TAKPacket = TAKPacket

View File

@@ -12,9 +12,10 @@ _sym_db = _symbol_database.Default()
from meshtastic.protobuf import localonly_pb2 as meshtastic_dot_protobuf_dot_localonly__pb2 from meshtastic.protobuf import localonly_pb2 as meshtastic_dot_protobuf_dot_localonly__pb2
from meshtastic.protobuf import mesh_pb2 as meshtastic_dot_protobuf_dot_mesh__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\"\x9f\x02\n\rDeviceProfile\x12\x16\n\tlong_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nshort_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configBe\n\x13\x63om.geeksville.meshB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/clientonly.proto\x12\x13meshtastic.protobuf\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\"\xc4\x03\n\rDeviceProfile\x12\x16\n\tlong_name\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\nshort_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x18\n\x0b\x63hannel_url\x18\x03 \x01(\tH\x02\x88\x01\x01\x12\x35\n\x06\x63onfig\x18\x04 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfigH\x03\x88\x01\x01\x12\x42\n\rmodule_config\x18\x05 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfigH\x04\x88\x01\x01\x12:\n\x0e\x66ixed_position\x18\x06 \x01(\x0b\x32\x1d.meshtastic.protobuf.PositionH\x05\x88\x01\x01\x12\x15\n\x08ringtone\x18\x07 \x01(\tH\x06\x88\x01\x01\x12\x1c\n\x0f\x63\x61nned_messages\x18\x08 \x01(\tH\x07\x88\x01\x01\x42\x0c\n\n_long_nameB\r\n\x0b_short_nameB\x0e\n\x0c_channel_urlB\t\n\x07_configB\x10\n\x0e_module_configB\x11\n\x0f_fixed_positionB\x0b\n\t_ringtoneB\x12\n\x10_canned_messagesBe\n\x13\x63om.geeksville.meshB\x10\x43lientOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -22,6 +23,6 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.cliento
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ClientOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_DEVICEPROFILE']._serialized_start=99 _globals['_DEVICEPROFILE']._serialized_start=131
_globals['_DEVICEPROFILE']._serialized_end=386 _globals['_DEVICEPROFILE']._serialized_end=583
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -7,6 +7,7 @@ import builtins
import google.protobuf.descriptor import google.protobuf.descriptor
import google.protobuf.message import google.protobuf.message
import meshtastic.protobuf.localonly_pb2 import meshtastic.protobuf.localonly_pb2
import meshtastic.protobuf.mesh_pb2
import typing import typing
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
@@ -25,6 +26,9 @@ class DeviceProfile(google.protobuf.message.Message):
CHANNEL_URL_FIELD_NUMBER: builtins.int CHANNEL_URL_FIELD_NUMBER: builtins.int
CONFIG_FIELD_NUMBER: builtins.int CONFIG_FIELD_NUMBER: builtins.int
MODULE_CONFIG_FIELD_NUMBER: builtins.int MODULE_CONFIG_FIELD_NUMBER: builtins.int
FIXED_POSITION_FIELD_NUMBER: builtins.int
RINGTONE_FIELD_NUMBER: builtins.int
CANNED_MESSAGES_FIELD_NUMBER: builtins.int
long_name: builtins.str long_name: builtins.str
""" """
Long name for the node Long name for the node
@@ -37,6 +41,14 @@ class DeviceProfile(google.protobuf.message.Message):
""" """
The url of the channels from our node The url of the channels from our node
""" """
ringtone: builtins.str
"""
Ringtone for ExternalNotification
"""
canned_messages: builtins.str
"""
Predefined messages for CannedMessage
"""
@property @property
def config(self) -> meshtastic.protobuf.localonly_pb2.LocalConfig: def config(self) -> meshtastic.protobuf.localonly_pb2.LocalConfig:
""" """
@@ -49,6 +61,12 @@ class DeviceProfile(google.protobuf.message.Message):
The ModuleConfig of the node The ModuleConfig of the node
""" """
@property
def fixed_position(self) -> meshtastic.protobuf.mesh_pb2.Position:
"""
Fixed position data
"""
def __init__( def __init__(
self, self,
*, *,
@@ -57,18 +75,27 @@ class DeviceProfile(google.protobuf.message.Message):
channel_url: builtins.str | None = ..., channel_url: builtins.str | None = ...,
config: meshtastic.protobuf.localonly_pb2.LocalConfig | None = ..., config: meshtastic.protobuf.localonly_pb2.LocalConfig | None = ...,
module_config: meshtastic.protobuf.localonly_pb2.LocalModuleConfig | None = ..., module_config: meshtastic.protobuf.localonly_pb2.LocalModuleConfig | None = ...,
fixed_position: meshtastic.protobuf.mesh_pb2.Position | None = ...,
ringtone: builtins.str | None = ...,
canned_messages: builtins.str | None = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["_channel_url", b"_channel_url", "_config", b"_config", "_long_name", b"_long_name", "_module_config", b"_module_config", "_short_name", b"_short_name", "channel_url", b"channel_url", "config", b"config", "long_name", b"long_name", "module_config", b"module_config", "short_name", b"short_name"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["_canned_messages", b"_canned_messages", "_channel_url", b"_channel_url", "_config", b"_config", "_fixed_position", b"_fixed_position", "_long_name", b"_long_name", "_module_config", b"_module_config", "_ringtone", b"_ringtone", "_short_name", b"_short_name", "canned_messages", b"canned_messages", "channel_url", b"channel_url", "config", b"config", "fixed_position", b"fixed_position", "long_name", b"long_name", "module_config", b"module_config", "ringtone", b"ringtone", "short_name", b"short_name"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_channel_url", b"_channel_url", "_config", b"_config", "_long_name", b"_long_name", "_module_config", b"_module_config", "_short_name", b"_short_name", "channel_url", b"channel_url", "config", b"config", "long_name", b"long_name", "module_config", b"module_config", "short_name", b"short_name"]) -> None: ... def ClearField(self, field_name: typing.Literal["_canned_messages", b"_canned_messages", "_channel_url", b"_channel_url", "_config", b"_config", "_fixed_position", b"_fixed_position", "_long_name", b"_long_name", "_module_config", b"_module_config", "_ringtone", b"_ringtone", "_short_name", b"_short_name", "canned_messages", b"canned_messages", "channel_url", b"channel_url", "config", b"config", "fixed_position", b"fixed_position", "long_name", b"long_name", "module_config", b"module_config", "ringtone", b"ringtone", "short_name", b"short_name"]) -> None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_canned_messages", b"_canned_messages"]) -> typing.Literal["canned_messages"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_channel_url", b"_channel_url"]) -> typing.Literal["channel_url"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_channel_url", b"_channel_url"]) -> typing.Literal["channel_url"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_config", b"_config"]) -> typing.Literal["config"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_config", b"_config"]) -> typing.Literal["config"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_fixed_position", b"_fixed_position"]) -> typing.Literal["fixed_position"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_long_name", b"_long_name"]) -> typing.Literal["long_name"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_long_name", b"_long_name"]) -> typing.Literal["long_name"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_module_config", b"_module_config"]) -> typing.Literal["module_config"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_module_config", b"_module_config"]) -> typing.Literal["module_config"] | None: ...
@typing.overload @typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_ringtone", b"_ringtone"]) -> typing.Literal["ringtone"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_short_name", b"_short_name"]) -> typing.Literal["short_name"] | None: ... def WhichOneof(self, oneof_group: typing.Literal["_short_name", b"_short_name"]) -> typing.Literal["short_name"] | None: ...
global___DeviceProfile = DeviceProfile global___DeviceProfile = DeviceProfile

View File

@@ -19,7 +19,7 @@ from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config
import nanopb_pb2 as nanopb__pb2 import nanopb_pb2 as nanopb__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/config.proto\x1a\x0cnanopb.proto\"\x99\x01\n\x0cPositionLite\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\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\xe2\x01\n\x08UserLite\x12\x13\n\x07macaddr\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x13\n\x0bis_licensed\x18\x05 \x01(\x08\x12;\n\x04role\x18\x06 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x12\n\npublic_key\x18\x07 \x01(\x0c\"\xa5\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12+\n\x04user\x18\x02 \x01(\x0b\x32\x1d.meshtastic.protobuf.UserLite\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x11\n\thops_away\x18\t \x01(\r\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\"\x82\x04\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x36\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12M\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePin\x12\x63\n\x0cnode_db_lite\x18\x0e \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"N\n\x0b\x43hannelFile\x12.\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\xb2\x02\n\x08OEMStore\x12\x16\n\x0eoem_icon_width\x18\x01 \x01(\r\x12\x17\n\x0foem_icon_height\x18\x02 \x01(\r\x12\x15\n\roem_icon_bits\x18\x03 \x01(\x0c\x12\x32\n\x08oem_font\x18\x04 \x01(\x0e\x32 .meshtastic.protobuf.ScreenFonts\x12\x10\n\x08oem_text\x18\x05 \x01(\t\x12\x13\n\x0boem_aes_key\x18\x06 \x01(\x0c\x12:\n\x10oem_local_config\x18\x07 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfig\x12G\n\x17oem_local_module_config\x18\x08 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig*>\n\x0bScreenFonts\x12\x0e\n\nFONT_SMALL\x10\x00\x12\x0f\n\x0b\x46ONT_MEDIUM\x10\x01\x12\x0e\n\nFONT_LARGE\x10\x02\x42m\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n$meshtastic/protobuf/deviceonly.proto\x12\x13meshtastic.protobuf\x1a!meshtastic/protobuf/channel.proto\x1a#meshtastic/protobuf/localonly.proto\x1a\x1emeshtastic/protobuf/mesh.proto\x1a#meshtastic/protobuf/telemetry.proto\x1a meshtastic/protobuf/config.proto\x1a\x0cnanopb.proto\"\x99\x01\n\x0cPositionLite\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\x0c\n\x04time\x18\x04 \x01(\x07\x12@\n\x0flocation_source\x18\x05 \x01(\x0e\x32\'.meshtastic.protobuf.Position.LocSource\"\xe2\x01\n\x08UserLite\x12\x13\n\x07macaddr\x18\x01 \x01(\x0c\x42\x02\x18\x01\x12\x11\n\tlong_name\x18\x02 \x01(\t\x12\x12\n\nshort_name\x18\x03 \x01(\t\x12\x34\n\x08hw_model\x18\x04 \x01(\x0e\x32\".meshtastic.protobuf.HardwareModel\x12\x13\n\x0bis_licensed\x18\x05 \x01(\x08\x12;\n\x04role\x18\x06 \x01(\x0e\x32-.meshtastic.protobuf.Config.DeviceConfig.Role\x12\x12\n\npublic_key\x18\x07 \x01(\x0c\"\xb8\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12+\n\x04user\x18\x02 \x01(\x0b\x32\x1d.meshtastic.protobuf.UserLite\x12\x33\n\x08position\x18\x03 \x01(\x0b\x32!.meshtastic.protobuf.PositionLite\x12\x0b\n\x03snr\x18\x04 \x01(\x02\x12\x12\n\nlast_heard\x18\x05 \x01(\x07\x12:\n\x0e\x64\x65vice_metrics\x18\x06 \x01(\x0b\x32\".meshtastic.protobuf.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\x12\x10\n\x08via_mqtt\x18\x08 \x01(\x08\x12\x16\n\thops_away\x18\t \x01(\rH\x00\x88\x01\x01\x12\x13\n\x0bis_favorite\x18\n \x01(\x08\x42\x0c\n\n_hops_away\"\x82\x04\n\x0b\x44\x65viceState\x12\x30\n\x07my_node\x18\x02 \x01(\x0b\x32\x1f.meshtastic.protobuf.MyNodeInfo\x12(\n\x05owner\x18\x03 \x01(\x0b\x32\x19.meshtastic.protobuf.User\x12\x36\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12\x38\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12\x13\n\x07no_save\x18\t \x01(\x08\x42\x02\x18\x01\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08\x12\x34\n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.MeshPacket\x12M\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32*.meshtastic.protobuf.NodeRemoteHardwarePin\x12\x63\n\x0cnode_db_lite\x18\x0e \x03(\x0b\x32!.meshtastic.protobuf.NodeInfoLiteB*\x92?\'\x92\x01$std::vector<meshtastic_NodeInfoLite>\"N\n\x0b\x43hannelFile\x12.\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x1c.meshtastic.protobuf.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\xb2\x02\n\x08OEMStore\x12\x16\n\x0eoem_icon_width\x18\x01 \x01(\r\x12\x17\n\x0foem_icon_height\x18\x02 \x01(\r\x12\x15\n\roem_icon_bits\x18\x03 \x01(\x0c\x12\x32\n\x08oem_font\x18\x04 \x01(\x0e\x32 .meshtastic.protobuf.ScreenFonts\x12\x10\n\x08oem_text\x18\x05 \x01(\t\x12\x13\n\x0boem_aes_key\x18\x06 \x01(\x0c\x12:\n\x10oem_local_config\x18\x07 \x01(\x0b\x32 .meshtastic.protobuf.LocalConfig\x12G\n\x17oem_local_module_config\x18\x08 \x01(\x0b\x32&.meshtastic.protobuf.LocalModuleConfig*>\n\x0bScreenFonts\x12\x0e\n\nFONT_SMALL\x10\x00\x12\x0f\n\x0b\x46ONT_MEDIUM\x10\x01\x12\x0e\n\nFONT_LARGE\x10\x02\x42m\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x92?\x0b\xc2\x01\x08<vector>b\x06proto3')
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -33,18 +33,18 @@ if _descriptor._USE_C_DESCRIPTORS == False:
_DEVICESTATE.fields_by_name['no_save']._serialized_options = b'\030\001' _DEVICESTATE.fields_by_name['no_save']._serialized_options = b'\030\001'
_DEVICESTATE.fields_by_name['node_db_lite']._options = None _DEVICESTATE.fields_by_name['node_db_lite']._options = None
_DEVICESTATE.fields_by_name['node_db_lite']._serialized_options = b'\222?\'\222\001$std::vector<meshtastic_NodeInfoLite>' _DEVICESTATE.fields_by_name['node_db_lite']._serialized_options = b'\222?\'\222\001$std::vector<meshtastic_NodeInfoLite>'
_globals['_SCREENFONTS']._serialized_start=1837 _globals['_SCREENFONTS']._serialized_start=1856
_globals['_SCREENFONTS']._serialized_end=1899 _globals['_SCREENFONTS']._serialized_end=1918
_globals['_POSITIONLITE']._serialized_start=251 _globals['_POSITIONLITE']._serialized_start=251
_globals['_POSITIONLITE']._serialized_end=404 _globals['_POSITIONLITE']._serialized_end=404
_globals['_USERLITE']._serialized_start=407 _globals['_USERLITE']._serialized_start=407
_globals['_USERLITE']._serialized_end=633 _globals['_USERLITE']._serialized_end=633
_globals['_NODEINFOLITE']._serialized_start=636 _globals['_NODEINFOLITE']._serialized_start=636
_globals['_NODEINFOLITE']._serialized_end=929 _globals['_NODEINFOLITE']._serialized_end=948
_globals['_DEVICESTATE']._serialized_start=932 _globals['_DEVICESTATE']._serialized_start=951
_globals['_DEVICESTATE']._serialized_end=1446 _globals['_DEVICESTATE']._serialized_end=1465
_globals['_CHANNELFILE']._serialized_start=1448 _globals['_CHANNELFILE']._serialized_start=1467
_globals['_CHANNELFILE']._serialized_end=1526 _globals['_CHANNELFILE']._serialized_end=1545
_globals['_OEMSTORE']._serialized_start=1529 _globals['_OEMSTORE']._serialized_start=1548
_globals['_OEMSTORE']._serialized_end=1835 _globals['_OEMSTORE']._serialized_end=1854
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -248,11 +248,12 @@ class NodeInfoLite(google.protobuf.message.Message):
device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ..., device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ...,
channel: builtins.int = ..., channel: builtins.int = ...,
via_mqtt: builtins.bool = ..., via_mqtt: builtins.bool = ...,
hops_away: builtins.int = ..., hops_away: builtins.int | None = ...,
is_favorite: builtins.bool = ..., is_favorite: builtins.bool = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["device_metrics", b"device_metrics", "position", b"position", "user", b"user"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ... def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ...
global___NodeInfoLite = NodeInfoLite global___NodeInfoLite = NodeInfoLite

View File

File diff suppressed because one or more lines are too long

View File

@@ -129,6 +129,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
""" """
Heltec HRU-3601: https://heltec.org/project/hru-3601/ Heltec HRU-3601: https://heltec.org/project/hru-3601/
""" """
HELTEC_WIRELESS_BRIDGE: _HardwareModel.ValueType # 24
"""
Heltec Wireless Bridge
"""
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
@@ -201,7 +205,7 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
""" """
M5STACK: _HardwareModel.ValueType # 42 M5STACK: _HardwareModel.ValueType # 42
""" """
M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/ M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/
""" """
HELTEC_V3: _HardwareModel.ValueType # 43 HELTEC_V3: _HardwareModel.ValueType # 43
""" """
@@ -359,8 +363,22 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
^^^ short A0 to switch to I2C address 0x3C ^^^ short A0 to switch to I2C address 0x3C
""" """
M5STACK_COREBASIC: _HardwareModel.ValueType # 77 M5STACK_COREBASIC: _HardwareModel.ValueType # 77
"""M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/""" """M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/"""
M5STACK_CORE2: _HardwareModel.ValueType # 78 M5STACK_CORE2: _HardwareModel.ValueType # 78
RPI_PICO2: _HardwareModel.ValueType # 79
"""Pico2 with Waveshare Hat, same as Pico"""
M5STACK_CORES3: _HardwareModel.ValueType # 80
"""M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/"""
SEEED_XIAO_S3: _HardwareModel.ValueType # 81
"""Seeed XIAO S3 DK"""
MS24SF1: _HardwareModel.ValueType # 82
"""
Nordic nRF52840+Semtech SX1262 LoRa BLE Combo Module. nRF52840+SX1262 MS24SF1
"""
TLORA_C6: _HardwareModel.ValueType # 83
"""
Lilygo TLora-C6 with the new ESP32-C6 MCU
"""
PRIVATE_HW: _HardwareModel.ValueType # 255 PRIVATE_HW: _HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
@@ -474,6 +492,10 @@ HELTEC_HRU_3601: HardwareModel.ValueType # 23
""" """
Heltec HRU-3601: https://heltec.org/project/hru-3601/ Heltec HRU-3601: https://heltec.org/project/hru-3601/
""" """
HELTEC_WIRELESS_BRIDGE: HardwareModel.ValueType # 24
"""
Heltec Wireless Bridge
"""
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
@@ -546,7 +568,7 @@ Custom Disaster Radio esp32 v3 device https://github.com/sudomesh/disaster-radio
""" """
M5STACK: HardwareModel.ValueType # 42 M5STACK: HardwareModel.ValueType # 42
""" """
M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/ M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/
""" """
HELTEC_V3: HardwareModel.ValueType # 43 HELTEC_V3: HardwareModel.ValueType # 43
""" """
@@ -704,8 +726,22 @@ https://www.adafruit.com/product/938
^^^ short A0 to switch to I2C address 0x3C ^^^ short A0 to switch to I2C address 0x3C
""" """
M5STACK_COREBASIC: HardwareModel.ValueType # 77 M5STACK_COREBASIC: HardwareModel.ValueType # 77
"""M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, Paper) https://m5stack.com/""" """M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/"""
M5STACK_CORE2: HardwareModel.ValueType # 78 M5STACK_CORE2: HardwareModel.ValueType # 78
RPI_PICO2: HardwareModel.ValueType # 79
"""Pico2 with Waveshare Hat, same as Pico"""
M5STACK_CORES3: HardwareModel.ValueType # 80
"""M5 esp32 based MCU modules with enclosure, TFT and LORA Shields. All Variants (Basic, Core, Fire, Core2, CoreS3, Paper) https://m5stack.com/"""
SEEED_XIAO_S3: HardwareModel.ValueType # 81
"""Seeed XIAO S3 DK"""
MS24SF1: HardwareModel.ValueType # 82
"""
Nordic nRF52840+Semtech SX1262 LoRa BLE Combo Module. nRF52840+SX1262 MS24SF1
"""
TLORA_C6: HardwareModel.ValueType # 83
"""
Lilygo TLora-C6 with the new ESP32-C6 MCU
"""
PRIVATE_HW: HardwareModel.ValueType # 255 PRIVATE_HW: HardwareModel.ValueType # 255
""" """
------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------
@@ -1389,6 +1425,14 @@ class Routing(google.protobuf.message.Message):
""" """
The receiving node does not have a Public Key to decode with The receiving node does not have a Public Key to decode with
""" """
ADMIN_BAD_SESSION_KEY: Routing._Error.ValueType # 36
"""
Admin packet otherwise checks out, but uses a bogus or expired session key
"""
ADMIN_PUBLIC_KEY_UNAUTHORIZED: Routing._Error.ValueType # 37
"""
Admin packet sent using PKC, but not from a public key on the admin key list
"""
class Error(_Error, metaclass=_ErrorEnumTypeWrapper): class Error(_Error, metaclass=_ErrorEnumTypeWrapper):
""" """
@@ -1454,6 +1498,14 @@ class Routing(google.protobuf.message.Message):
""" """
The receiving node does not have a Public Key to decode with The receiving node does not have a Public Key to decode with
""" """
ADMIN_BAD_SESSION_KEY: Routing.Error.ValueType # 36
"""
Admin packet otherwise checks out, but uses a bogus or expired session key
"""
ADMIN_PUBLIC_KEY_UNAUTHORIZED: Routing.Error.ValueType # 37
"""
Admin packet sent using PKC, but not from a public key on the admin key list
"""
ROUTE_REQUEST_FIELD_NUMBER: builtins.int ROUTE_REQUEST_FIELD_NUMBER: builtins.int
ROUTE_REPLY_FIELD_NUMBER: builtins.int ROUTE_REPLY_FIELD_NUMBER: builtins.int
@@ -2080,11 +2132,12 @@ class NodeInfo(google.protobuf.message.Message):
device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ..., device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ...,
channel: builtins.int = ..., channel: builtins.int = ...,
via_mqtt: builtins.bool = ..., via_mqtt: builtins.bool = ...,
hops_away: builtins.int = ..., hops_away: builtins.int | None = ...,
is_favorite: builtins.bool = ..., is_favorite: builtins.bool = ...,
) -> None: ... ) -> None: ...
def HasField(self, field_name: typing.Literal["device_metrics", b"device_metrics", "position", b"position", "user", b"user"]) -> builtins.bool: ... def HasField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "position", b"position", "user", b"user"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ... def ClearField(self, field_name: typing.Literal["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "last_heard", b"last_heard", "num", b"num", "position", b"position", "snr", b"snr", "user", b"user", "via_mqtt", b"via_mqtt"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["_hops_away", b"_hops_away"]) -> typing.Literal["hops_away"] | None: ...
global___NodeInfo = NodeInfo global___NodeInfo = NodeInfo
@@ -2695,6 +2748,7 @@ class DeviceMetadata(google.protobuf.message.Message):
POSITION_FLAGS_FIELD_NUMBER: builtins.int POSITION_FLAGS_FIELD_NUMBER: builtins.int
HW_MODEL_FIELD_NUMBER: builtins.int HW_MODEL_FIELD_NUMBER: builtins.int
HASREMOTEHARDWARE_FIELD_NUMBER: builtins.int HASREMOTEHARDWARE_FIELD_NUMBER: builtins.int
HASPKC_FIELD_NUMBER: builtins.int
firmware_version: builtins.str firmware_version: builtins.str
""" """
Device firmware version string Device firmware version string
@@ -2735,6 +2789,10 @@ class DeviceMetadata(google.protobuf.message.Message):
""" """
Has Remote Hardware enabled Has Remote Hardware enabled
""" """
hasPKC: builtins.bool
"""
Has PKC capabilities
"""
def __init__( def __init__(
self, self,
*, *,
@@ -2748,8 +2806,9 @@ class DeviceMetadata(google.protobuf.message.Message):
position_flags: builtins.int = ..., position_flags: builtins.int = ...,
hw_model: global___HardwareModel.ValueType = ..., hw_model: global___HardwareModel.ValueType = ...,
hasRemoteHardware: builtins.bool = ..., hasRemoteHardware: builtins.bool = ...,
hasPKC: builtins.bool = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["canShutdown", b"canShutdown", "device_state_version", b"device_state_version", "firmware_version", b"firmware_version", "hasBluetooth", b"hasBluetooth", "hasEthernet", b"hasEthernet", "hasRemoteHardware", b"hasRemoteHardware", "hasWifi", b"hasWifi", "hw_model", b"hw_model", "position_flags", b"position_flags", "role", b"role"]) -> None: ... def ClearField(self, field_name: typing.Literal["canShutdown", b"canShutdown", "device_state_version", b"device_state_version", "firmware_version", b"firmware_version", "hasBluetooth", b"hasBluetooth", "hasEthernet", b"hasEthernet", "hasPKC", b"hasPKC", "hasRemoteHardware", b"hasRemoteHardware", "hasWifi", b"hasWifi", "hw_model", b"hw_model", "position_flags", b"position_flags", "role", b"role"]) -> None: ...
global___DeviceMetadata = DeviceMetadata global___DeviceMetadata = DeviceMetadata

View File

File diff suppressed because one or more lines are too long

View File

@@ -250,13 +250,54 @@ class ModuleConfig(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor DESCRIPTOR: google.protobuf.descriptor.Descriptor
class _TriggerType:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _TriggerTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ModuleConfig.DetectionSensorConfig._TriggerType.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
LOGIC_LOW: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 0
"""Event is triggered if pin is low"""
LOGIC_HIGH: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 1
"""Event is triggered if pin is high"""
FALLING_EDGE: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 2
"""Event is triggered when pin goes high to low"""
RISING_EDGE: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 3
"""Event is triggered when pin goes low to high"""
EITHER_EDGE_ACTIVE_LOW: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 4
"""Event is triggered on every pin state change, low is considered to be
"active"
"""
EITHER_EDGE_ACTIVE_HIGH: ModuleConfig.DetectionSensorConfig._TriggerType.ValueType # 5
"""Event is triggered on every pin state change, high is considered to be
"active"
"""
class TriggerType(_TriggerType, metaclass=_TriggerTypeEnumTypeWrapper): ...
LOGIC_LOW: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 0
"""Event is triggered if pin is low"""
LOGIC_HIGH: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 1
"""Event is triggered if pin is high"""
FALLING_EDGE: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 2
"""Event is triggered when pin goes high to low"""
RISING_EDGE: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 3
"""Event is triggered when pin goes low to high"""
EITHER_EDGE_ACTIVE_LOW: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 4
"""Event is triggered on every pin state change, low is considered to be
"active"
"""
EITHER_EDGE_ACTIVE_HIGH: ModuleConfig.DetectionSensorConfig.TriggerType.ValueType # 5
"""Event is triggered on every pin state change, high is considered to be
"active"
"""
ENABLED_FIELD_NUMBER: builtins.int ENABLED_FIELD_NUMBER: builtins.int
MINIMUM_BROADCAST_SECS_FIELD_NUMBER: builtins.int MINIMUM_BROADCAST_SECS_FIELD_NUMBER: builtins.int
STATE_BROADCAST_SECS_FIELD_NUMBER: builtins.int STATE_BROADCAST_SECS_FIELD_NUMBER: builtins.int
SEND_BELL_FIELD_NUMBER: builtins.int SEND_BELL_FIELD_NUMBER: builtins.int
NAME_FIELD_NUMBER: builtins.int NAME_FIELD_NUMBER: builtins.int
MONITOR_PIN_FIELD_NUMBER: builtins.int MONITOR_PIN_FIELD_NUMBER: builtins.int
DETECTION_TRIGGERED_HIGH_FIELD_NUMBER: builtins.int DETECTION_TRIGGER_TYPE_FIELD_NUMBER: builtins.int
USE_PULLUP_FIELD_NUMBER: builtins.int USE_PULLUP_FIELD_NUMBER: builtins.int
enabled: builtins.bool enabled: builtins.bool
""" """
@@ -264,13 +305,15 @@ class ModuleConfig(google.protobuf.message.Message):
""" """
minimum_broadcast_secs: builtins.int minimum_broadcast_secs: builtins.int
""" """
Interval in seconds of how often we can send a message to the mesh when a state change is detected Interval in seconds of how often we can send a message to the mesh when a
trigger event is detected
""" """
state_broadcast_secs: builtins.int state_broadcast_secs: builtins.int
""" """
Interval in seconds of how often we should send a message to the mesh with the current state regardless of changes Interval in seconds of how often we should send a message to the mesh
When set to 0, only state changes will be broadcasted with the current state regardless of trigger events When set to 0, only
Works as a sort of status heartbeat for peace of mind trigger events will be broadcasted Works as a sort of status heartbeat
for peace of mind
""" """
send_bell: builtins.bool send_bell: builtins.bool
""" """
@@ -287,10 +330,9 @@ class ModuleConfig(google.protobuf.message.Message):
""" """
GPIO pin to monitor for state changes GPIO pin to monitor for state changes
""" """
detection_triggered_high: builtins.bool detection_trigger_type: global___ModuleConfig.DetectionSensorConfig.TriggerType.ValueType
""" """
Whether or not the GPIO pin state detection is triggered on HIGH (1) The type of trigger event to be used
Otherwise LOW (0)
""" """
use_pullup: builtins.bool use_pullup: builtins.bool
""" """
@@ -306,10 +348,10 @@ class ModuleConfig(google.protobuf.message.Message):
send_bell: builtins.bool = ..., send_bell: builtins.bool = ...,
name: builtins.str = ..., name: builtins.str = ...,
monitor_pin: builtins.int = ..., monitor_pin: builtins.int = ...,
detection_triggered_high: builtins.bool = ..., detection_trigger_type: global___ModuleConfig.DetectionSensorConfig.TriggerType.ValueType = ...,
use_pullup: builtins.bool = ..., use_pullup: builtins.bool = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["detection_triggered_high", b"detection_triggered_high", "enabled", b"enabled", "minimum_broadcast_secs", b"minimum_broadcast_secs", "monitor_pin", b"monitor_pin", "name", b"name", "send_bell", b"send_bell", "state_broadcast_secs", b"state_broadcast_secs", "use_pullup", b"use_pullup"]) -> None: ... def ClearField(self, field_name: typing.Literal["detection_trigger_type", b"detection_trigger_type", "enabled", b"enabled", "minimum_broadcast_secs", b"minimum_broadcast_secs", "monitor_pin", b"monitor_pin", "name", b"name", "send_bell", b"send_bell", "state_broadcast_secs", b"state_broadcast_secs", "use_pullup", b"use_pullup"]) -> None: ...
@typing.final @typing.final
class AudioConfig(google.protobuf.message.Message): class AudioConfig(google.protobuf.message.Message):

View File

@@ -8,7 +8,7 @@ from meshtastic.protobuf import portnums_pb2, remote_hardware_pb2
from meshtastic.util import our_exit from meshtastic.util import our_exit
def onGPIOreceive(packet, interface): def onGPIOreceive(packet, interface) -> None:
"""Callback for received GPIO responses""" """Callback for received GPIO responses"""
logging.debug(f"packet:{packet} interface:{interface}") logging.debug(f"packet:{packet} interface:{interface}")
gpioValue = 0 gpioValue = 0
@@ -37,7 +37,7 @@ class RemoteHardwareClient:
code for how you can connect to your own custom meshtastic services code for how you can connect to your own custom meshtastic services
""" """
def __init__(self, iface): def __init__(self, iface) -> None:
""" """
Constructor Constructor

View File

@@ -1,10 +1,11 @@
""" Serial interface class """ Serial interface class
""" """
# pylint: disable=R0917
import logging import logging
import platform import platform
import time import time
from typing import Optional from typing import List, Optional
import serial # type: ignore[import-untyped] import serial # type: ignore[import-untyped]
@@ -18,7 +19,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: Optional[str]=None, debugOut=None, noProto=False, connectNow=True, noNodes: bool=False): def __init__(self, devPath: Optional[str]=None, debugOut=None, noProto: bool=False, connectNow: bool=True, noNodes: bool=False) -> None:
"""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
@@ -31,13 +32,13 @@ class SerialInterface(StreamInterface):
self.devPath: Optional[str] = devPath self.devPath: Optional[str] = devPath
if self.devPath is None: if self.devPath is None:
ports = meshtastic.util.findPorts(True) ports: List[str] = meshtastic.util.findPorts(True)
logging.debug(f"ports:{ports}") logging.debug(f"ports:{ports}")
if len(ports) == 0: if len(ports) == 0:
print("No Serial Meshtastic device detected, attempting TCP connection on localhost.") print("No Serial Meshtastic device detected, attempting TCP connection on localhost.")
return return
elif len(ports) > 1: elif len(ports) > 1:
message = "Warning: Multiple serial ports were detected so one serial port must be specified with the '--port'.\n" message: str = "Warning: Multiple serial ports were detected so one serial port must be specified with the '--port'.\n"
message += f" Ports detected:{ports}" message += f" Ports detected:{ports}"
meshtastic.util.our_exit(message) meshtastic.util.our_exit(message)
else: else:
@@ -58,14 +59,14 @@ class SerialInterface(StreamInterface):
self.stream = serial.Serial( self.stream = serial.Serial(
self.devPath, 115200, exclusive=True, timeout=0.5, write_timeout=0 self.devPath, 115200, exclusive=True, timeout=0.5, write_timeout=0
) )
self.stream.flush() self.stream.flush() # type: ignore[attr-defined]
time.sleep(0.1) time.sleep(0.1)
StreamInterface.__init__( StreamInterface.__init__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
) )
def close(self): def close(self) -> None:
"""Close a connection to the device""" """Close a connection to the device"""
if self.stream: # Stream can be null if we were already closed if self.stream: # Stream can be null if we were already closed
self.stream.flush() # FIXME: why are there these two flushes with 100ms sleeps? This shouldn't be necessary self.stream.flush() # FIXME: why are there these two flushes with 100ms sleeps? This shouldn't be necessary

View File

@@ -1,10 +1,13 @@
"""Stream Interface base class """Stream Interface base class
""" """
import io
import logging import logging
import threading import threading
import time import time
import traceback import traceback
from typing import Optional, cast
import serial # type: ignore[import-untyped] import serial # type: ignore[import-untyped]
from meshtastic.mesh_interface import MeshInterface from meshtastic.mesh_interface import MeshInterface
@@ -19,7 +22,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, noNodes=False): def __init__(self, debugOut: Optional[io.TextIOWrapper]=None, noProto: bool=False, connectNow: bool=True, noNodes: bool=False) -> None:
"""Constructor, opens a connection to self.stream """Constructor, opens a connection to self.stream
Keyword Arguments: Keyword Arguments:
@@ -35,6 +38,7 @@ class StreamInterface(MeshInterface):
raise Exception( # pylint: disable=W0719 raise Exception( # pylint: disable=W0719
"StreamInterface is now abstract (to update existing code create SerialInterface instead)" "StreamInterface is now abstract (to update existing code create SerialInterface instead)"
) )
self.stream: Optional[serial.Serial] # only serial uses this, TCPInterface overrides the relevant methods instead
self._rxBuf = bytes() # empty self._rxBuf = bytes() # empty
self._wantExit = False self._wantExit = False
@@ -52,7 +56,7 @@ class StreamInterface(MeshInterface):
if not noProto: if not noProto:
self.waitForConfig() self.waitForConfig()
def connect(self): def connect(self) -> None:
"""Connect to our radio """Connect to our radio
Normally this is called automatically by the constructor, but if you Normally this is called automatically by the constructor, but if you
@@ -63,7 +67,7 @@ class StreamInterface(MeshInterface):
# if the reading statemachine was parsing a bad packet make sure # if the reading statemachine was parsing a bad packet make sure
# we write enough start bytes to force it to resync (we don't use START1 # we write enough start bytes to force it to resync (we don't use START1
# because we want to ensure it is looking for START1) # because we want to ensure it is looking for START1)
p = bytearray([START2] * 32) p: bytes = bytearray([START2] * 32)
self._writeBytes(p) self._writeBytes(p)
time.sleep(0.1) # wait 100ms to give device time to start running time.sleep(0.1) # wait 100ms to give device time to start running
@@ -74,7 +78,7 @@ class StreamInterface(MeshInterface):
if not self.noProto: # Wait for the db download if using the protocol if not self.noProto: # Wait for the db download if using the protocol
self._waitConnected() self._waitConnected()
def _disconnected(self): def _disconnected(self) -> None:
"""We override the superclass implementation to close our port""" """We override the superclass implementation to close our port"""
MeshInterface._disconnected(self) MeshInterface._disconnected(self)
@@ -86,7 +90,7 @@ class StreamInterface(MeshInterface):
# pylint: disable=W0201 # pylint: disable=W0201
self.stream = None self.stream = None
def _writeBytes(self, b): def _writeBytes(self, b: bytes) -> None:
"""Write an array of bytes to our stream and flush""" """Write an array of bytes to our stream and flush"""
if self.stream: # ignore writes when stream is closed if self.stream: # ignore writes when stream is closed
self.stream.write(b) self.stream.write(b)
@@ -98,24 +102,24 @@ class StreamInterface(MeshInterface):
# we sleep here to give the TBeam a chance to work # we sleep here to give the TBeam a chance to work
time.sleep(0.1) time.sleep(0.1)
def _readBytes(self, length): def _readBytes(self, length) -> Optional[bytes]:
"""Read an array of bytes from our stream""" """Read an array of bytes from our stream"""
if self.stream: if self.stream:
return self.stream.read(length) return self.stream.read(length)
else: else:
return None return None
def _sendToRadioImpl(self, toRadio): def _sendToRadioImpl(self, toRadio) -> None:
"""Send a ToRadio protobuf to the device""" """Send a ToRadio protobuf to the device"""
logging.debug(f"Sending: {stripnl(toRadio)}") logging.debug(f"Sending: {stripnl(toRadio)}")
b = toRadio.SerializeToString() b: bytes = toRadio.SerializeToString()
bufLen = len(b) bufLen: int = len(b)
# We convert into a string, because the TCP code doesn't work with byte arrays # We convert into a string, because the TCP code doesn't work with byte arrays
header = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF]) header: bytes = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF])
logging.debug(f"sending header:{header} b:{b}") logging.debug(f"sending header:{header!r} b:{b!r}")
self._writeBytes(header + b) self._writeBytes(header + b)
def close(self): def close(self) -> None:
"""Close a connection to the device""" """Close a connection to the device"""
logging.debug("Closing stream") logging.debug("Closing stream")
MeshInterface.close(self) MeshInterface.close(self)
@@ -142,7 +146,7 @@ class StreamInterface(MeshInterface):
else: else:
self.cur_log_line += utf self.cur_log_line += utf
def __reader(self): def __reader(self) -> None:
"""The reader thread that reads bytes from our stream""" """The reader thread that reads bytes from our stream"""
logging.debug("in __reader()") logging.debug("in __reader()")
empty = bytes() empty = bytes()
@@ -150,13 +154,13 @@ class StreamInterface(MeshInterface):
try: try:
while not self._wantExit: while not self._wantExit:
# logging.debug("reading character") # logging.debug("reading character")
b = self._readBytes(1) b: Optional[bytes] = self._readBytes(1)
# logging.debug("In reader loop") # logging.debug("In reader loop")
# logging.debug(f"read returned {b}") # logging.debug(f"read returned {b}")
if len(b) > 0: if b is not None and len(cast(bytes, b)) > 0:
c = b[0] c: int = b[0]
# logging.debug(f'c:{c}') # logging.debug(f'c:{c}')
ptr = len(self._rxBuf) ptr: int = len(self._rxBuf)
# Assume we want to append this byte, fixme use bytearray instead # Assume we want to append this byte, fixme use bytearray instead
self._rxBuf = self._rxBuf + b self._rxBuf = self._rxBuf + b

View File

@@ -1,6 +1,7 @@
""" Supported Meshtastic Devices - This is a class and collection of Meshtastic devices. """ Supported Meshtastic Devices - This is a class and collection of Meshtastic devices.
It is used for auto detection as to which device might be connected. It is used for auto detection as to which device might be connected.
""" """
# pylint: disable=R0917
# Goal is to detect which device and port to use from the supported devices # Goal is to detect which device and port to use from the supported devices
# without installing any libraries that are not currently in the python meshtastic library # without installing any libraries that are not currently in the python meshtastic library

View File

@@ -1,11 +1,13 @@
"""TCPInterface class for interfacing with http endpoint """TCPInterface class for interfacing with http endpoint
""" """
# pylint: disable=R0917
import logging import logging
import socket import socket
from typing import Optional from typing import Optional, cast
from meshtastic.stream_interface import StreamInterface from meshtastic.stream_interface import StreamInterface
DEFAULT_TCP_PORT = 4403
class TCPInterface(StreamInterface): class TCPInterface(StreamInterface):
"""Interface class for meshtastic devices over a TCP link""" """Interface class for meshtastic devices over a TCP link"""
@@ -14,9 +16,9 @@ class TCPInterface(StreamInterface):
self, self,
hostname: str, hostname: str,
debugOut=None, debugOut=None,
noProto=False, noProto: bool=False,
connectNow=True, connectNow: bool=True,
portNumber=4403, portNumber: int=DEFAULT_TCP_PORT,
noNodes:bool=False, noNodes:bool=False,
): ):
"""Constructor, opens a connection to a specified IP address/hostname """Constructor, opens a connection to a specified IP address/hostname
@@ -27,14 +29,16 @@ class TCPInterface(StreamInterface):
self.stream = None self.stream = None
self.hostname = hostname self.hostname: str = hostname
self.portNumber = portNumber self.portNumber: int = portNumber
self.socket: Optional[socket.socket] = None
if connectNow: if connectNow:
logging.debug(f"Connecting to {hostname}") # type: ignore[str-bytes-safe] logging.debug(f"Connecting to {hostname}") # type: ignore[str-bytes-safe]
server_address = (hostname, portNumber) server_address: tuple[str, int] = (hostname, portNumber)
sock = socket.create_connection(server_address) sock: Optional[socket.socket] = socket.create_connection(server_address)
self.socket: Optional[socket.socket] = sock self.socket = sock
else: else:
self.socket = None self.socket = None
@@ -42,25 +46,26 @@ class TCPInterface(StreamInterface):
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
) )
def _socket_shutdown(self): def _socket_shutdown(self) -> None:
"""Shutdown the socket. """Shutdown the socket.
Note: Broke out this line so the exception could be unit tested. Note: Broke out this line so the exception could be unit tested.
""" """
self.socket.shutdown(socket.SHUT_RDWR) if self.socket: #mian: please check that this should be "if self.socket:"
cast(socket.socket, self.socket).shutdown(socket.SHUT_RDWR)
def myConnect(self): def myConnect(self) -> None:
"""Connect to socket""" """Connect to socket"""
server_address = (self.hostname, self.portNumber) server_address: tuple[str, int] = (self.hostname, self.portNumber)
sock = socket.create_connection(server_address) sock: Optional[socket.socket] = socket.create_connection(server_address)
self.socket = sock self.socket = sock
def close(self): def close(self) -> None:
"""Close a connection to the device""" """Close a connection to the device"""
logging.debug("Closing TCP stream") logging.debug("Closing TCP stream")
StreamInterface.close(self) StreamInterface.close(self)
# Sometimes the socket read might be blocked in the reader thread. # Sometimes the socket read might be blocked in the reader thread.
# Therefore we force the shutdown by closing the socket here # Therefore we force the shutdown by closing the socket here
self._wantExit = True self._wantExit: bool = True
if not self.socket is None: if not self.socket is None:
try: try:
self._socket_shutdown() self._socket_shutdown()
@@ -68,10 +73,14 @@ class TCPInterface(StreamInterface):
pass # Ignore errors in shutdown, because we might have a race with the server pass # Ignore errors in shutdown, because we might have a race with the server
self.socket.close() self.socket.close()
def _writeBytes(self, b): def _writeBytes(self, b: bytes) -> None:
"""Write an array of bytes to our stream and flush""" """Write an array of bytes to our stream and flush"""
self.socket.send(b) if self.socket:
self.socket.send(b)
def _readBytes(self, length): def _readBytes(self, length) -> Optional[bytes]:
"""Read an array of bytes from our stream""" """Read an array of bytes from our stream"""
return self.socket.recv(length) if self.socket:
return self.socket.recv(length)
else:
return None

View File

@@ -5,6 +5,9 @@ import logging
import sys import sys
import time import time
import traceback import traceback
import io
from typing import List, Optional
from dotmap import DotMap # type: ignore[import-untyped] from dotmap import DotMap # type: ignore[import-untyped]
from pubsub import pub # type: ignore[import-untyped] from pubsub import pub # type: ignore[import-untyped]
@@ -15,19 +18,19 @@ from meshtastic.serial_interface import SerialInterface
from meshtastic.tcp_interface import TCPInterface from meshtastic.tcp_interface import TCPInterface
"""The interfaces we are using for our tests""" """The interfaces we are using for our tests"""
interfaces = None interfaces: List = []
"""A list of all packets we received while the current test was running""" """A list of all packets we received while the current test was running"""
receivedPackets = None receivedPackets: Optional[List] = None
testsRunning = False testsRunning: bool = False
testNumber = 0 testNumber: int = 0
sendingInterface = None sendingInterface = None
def onReceive(packet, interface): def onReceive(packet, interface) -> None:
"""Callback invoked when a packet arrives""" """Callback invoked when a packet arrives"""
if sendingInterface == interface: if sendingInterface == interface:
pass pass
@@ -42,20 +45,20 @@ def onReceive(packet, interface):
receivedPackets.append(p) receivedPackets.append(p)
def onNode(node): def onNode(node) -> None:
"""Callback invoked when the node DB changes""" """Callback invoked when the node DB changes"""
print(f"Node changed: {node}") print(f"Node changed: {node}")
def subscribe(): def subscribe() -> None:
"""Subscribe to the topics the user probably wants to see, prints output to stdout""" """Subscribe to the topics the user probably wants to see, prints output to stdout"""
pub.subscribe(onNode, "meshtastic.node") pub.subscribe(onNode, "meshtastic.node")
def testSend( def testSend(
fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False fromInterface, toInterface, isBroadcast: bool=False, asBinary: bool=False, wantAck: bool=False
): ) -> bool:
""" """
Sends one test packet between two nodes and then returns success or failure Sends one test packet between two nodes and then returns success or failure
@@ -93,16 +96,16 @@ def testSend(
return False # Failed to send return False # Failed to send
def runTests(numTests=50, wantAck=False, maxFailures=0): def runTests(numTests: int=50, wantAck: bool=False, maxFailures: int=0) -> bool:
"""Run the tests.""" """Run the tests."""
logging.info(f"Running {numTests} tests with wantAck={wantAck}") logging.info(f"Running {numTests} tests with wantAck={wantAck}")
numFail = 0 numFail: int = 0
numSuccess = 0 numSuccess: int = 0
for _ in range(numTests): for _ in range(numTests):
# pylint: disable=W0603 # pylint: disable=W0603
global testNumber global testNumber
testNumber = testNumber + 1 testNumber = testNumber + 1
isBroadcast = True isBroadcast:bool = True
# asBinary=(i % 2 == 0) # asBinary=(i % 2 == 0)
success = testSend( success = testSend(
interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck
@@ -126,10 +129,10 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
return True return True
def testThread(numTests=50): def testThread(numTests=50) -> bool:
"""Test thread""" """Test thread"""
logging.info("Found devices, starting tests...") logging.info("Found devices, starting tests...")
result = runTests(numTests, wantAck=True) result: bool = runTests(numTests, wantAck=True)
if result: if result:
# Run another test # Run another test
# Allow a few dropped packets # Allow a few dropped packets
@@ -137,25 +140,25 @@ def testThread(numTests=50):
return result return result
def onConnection(topic=pub.AUTO_TOPIC): def onConnection(topic=pub.AUTO_TOPIC) -> None:
"""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 openDebugLog(portName): def openDebugLog(portName) -> io.TextIOWrapper:
"""Open the debug log file""" """Open the debug log file"""
debugname = "log" + portName.replace("/", "_") debugname = "log" + portName.replace("/", "_")
logging.info(f"Writing serial debugging to {debugname}") logging.info(f"Writing serial debugging to {debugname}")
return open(debugname, "w+", buffering=1, encoding="utf8") return open(debugname, "w+", buffering=1, encoding="utf8")
def testAll(numTests=5): def testAll(numTests: int=5) -> bool:
""" """
Run a series of tests using devices we can find. Run a series of tests using devices we can find.
This is called from the cli with the "--test" option. This is called from the cli with the "--test" option.
""" """
ports = meshtastic.util.findPorts(True) ports: List[str] = meshtastic.util.findPorts(True)
if len(ports) < 2: if len(ports) < 2:
meshtastic.util.our_exit( meshtastic.util.our_exit(
"Warning: Must have at least two devices connected to USB." "Warning: Must have at least two devices connected to USB."
@@ -175,7 +178,7 @@ def testAll(numTests=5):
) )
logging.info("Ports opened, starting test") logging.info("Ports opened, starting test")
result = testThread(numTests) result: bool = testThread(numTests)
for i in interfaces: for i in interfaces:
i.close() i.close()
@@ -183,7 +186,7 @@ def testAll(numTests=5):
return result return result
def testSimulator(): def testSimulator() -> None:
""" """
Assume that someone has launched meshtastic-native as a simulated node. Assume that someone has launched meshtastic-native as a simulated node.
Talk to that node over TCP, do some operations and if they are successful Talk to that node over TCP, do some operations and if they are successful
@@ -195,7 +198,7 @@ def testSimulator():
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logging.info("Connecting to simulator on localhost!") logging.info("Connecting to simulator on localhost!")
try: try:
iface = TCPInterface("localhost") iface: meshtastic.tcp_interface.TCPInterface = TCPInterface("localhost")
iface.showInfo() iface.showInfo()
iface.localNode.showInfo() iface.localNode.showInfo()
iface.localNode.exitSimulator() iface.localNode.exitSimulator()

View File

@@ -1,5 +1,5 @@
"""Meshtastic unit tests for __main__.py""" """Meshtastic unit tests for __main__.py"""
# pylint: disable=C0302,W0613 # pylint: disable=C0302,W0613,R0917
import logging import logging
import os import os
@@ -726,8 +726,8 @@ def test_main_sendtext_with_dest(mock_findPorts, mock_serial, mocked_open, mock_
@pytest.mark.unit @pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config") @pytest.mark.usefixtures("reset_mt_config")
def test_main_removeposition_invalid(capsys): def test_main_removeposition_remote(capsys):
"""Test --remove-position with an invalid dest""" """Test --remove-position with a remote dest"""
sys.argv = ["", "--remove-position", "--dest", "!12345678"] sys.argv = ["", "--remove-position", "--dest", "!12345678"]
mt_config.args = sys.argv mt_config.args = sys.argv
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
@@ -735,14 +735,15 @@ def test_main_removeposition_invalid(capsys):
main() main()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r"Connected to radio", out, re.MULTILINE) assert re.search(r"Connected to radio", out, re.MULTILINE)
assert re.search(r"remote nodes is not supported", out, re.MULTILINE) assert re.search(r"Removing fixed position and disabling fixed position setting", out, re.MULTILINE)
assert re.search(r"Waiting for an acknowledgment from remote node", out, re.MULTILINE)
assert err == "" assert err == ""
mo.assert_called() 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_invalid(capsys): def test_main_setlat_remote(capsys):
"""Test --setlat with an invalid dest""" """Test --setlat with a remote dest"""
sys.argv = ["", "--setlat", "37.5", "--dest", "!12345678"] sys.argv = ["", "--setlat", "37.5", "--dest", "!12345678"]
mt_config.args = sys.argv mt_config.args = sys.argv
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
@@ -750,7 +751,8 @@ def test_main_setlat_invalid(capsys):
main() main()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r"Connected to radio", out, re.MULTILINE) assert re.search(r"Connected to radio", out, re.MULTILINE)
assert re.search(r"remote nodes is not supported", out, re.MULTILINE) assert re.search(r"Setting device position and enabling fixed position setting", out, re.MULTILINE)
assert re.search(r"Waiting for an acknowledgment from remote node", out, re.MULTILINE)
assert err == "" assert err == ""
mo.assert_called() mo.assert_called()
@@ -769,7 +771,7 @@ def test_main_removeposition(capsys):
mocked_node.removeFixedPosition.side_effect = mock_removeFixedPosition mocked_node.removeFixedPosition.side_effect = mock_removeFixedPosition
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
iface.localNode = mocked_node iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
main() main()
@@ -796,7 +798,7 @@ def test_main_setlat(capsys):
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
iface.localNode = mocked_node iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
main() main()
@@ -825,7 +827,7 @@ def test_main_setlon(capsys):
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
iface.localNode = mocked_node iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
main() main()
@@ -854,7 +856,7 @@ def test_main_setalt(capsys):
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
iface.localNode = mocked_node iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo: with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
main() main()

View File

@@ -173,7 +173,23 @@ def test_getNode_not_local_timeout(capsys):
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.match(r"Error: Timed out waiting for channels", out) assert re.match(r"Timed out trying to retrieve channel info, retrying", out)
assert err == ""
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_getNode_not_local_timeout_attempts(capsys):
"""Test getNode not local, simulate timeout"""
iface = MeshInterface(noProto=True)
anode = MagicMock(autospec=Node)
anode.waitForConfig.return_value = False
with patch("meshtastic.node.Node", return_value=anode):
with pytest.raises(SystemExit) as pytest_wrapped_e:
iface.getNode("bar2", requestChannelAttempts=2)
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr()
assert out == 'Timed out trying to retrieve channel info, retrying\nError: Timed out waiting for channels, giving up\n'
assert err == "" assert err == ""

View File

@@ -842,6 +842,34 @@ def test_requestChannel_localNode(caplog):
assert re.search(r"Requesting channel 0", caplog.text, re.MULTILINE) assert re.search(r"Requesting channel 0", caplog.text, re.MULTILINE)
assert not re.search(r"from remote node", caplog.text, re.MULTILINE) assert not re.search(r"from remote node", caplog.text, re.MULTILINE)
@pytest.mark.unit
def test_requestChannels_non_localNode(caplog):
"""Test requestChannels() with a starting index of 0"""
iface = MagicMock(autospec=SerialInterface)
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, "bar", noProto=True)
anode.partialChannels = ['0']
with caplog.at_level(logging.DEBUG):
anode.requestChannels(0)
assert re.search(f"Requesting channel 0 info from remote node", caplog.text, re.MULTILINE)
assert anode.partialChannels == []
@pytest.mark.unit
def test_requestChannels_non_localNode_starting_index(caplog):
"""Test requestChannels() with a starting index of non-0"""
iface = MagicMock(autospec=SerialInterface)
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None
mo.myInfo.max_channels = 8
anode = Node(mo, "bar", noProto=True)
anode.partialChannels = ['1']
with caplog.at_level(logging.DEBUG):
anode.requestChannels(3)
assert re.search(f"Requesting channel 3 info from remote node", caplog.text, re.MULTILINE)
# make sure it hasn't been initialized
assert anode.partialChannels == ['1']
# @pytest.mark.unit # @pytest.mark.unit
# def test_onResponseRequestCannedMessagePluginMesagePart1(caplog): # def test_onResponseRequestCannedMessagePluginMesagePart1(caplog):

View File

@@ -1,4 +1,5 @@
"""Meshtastic unit tests for serial_interface.py""" """Meshtastic unit tests for serial_interface.py"""
# pylint: disable=R0917
import re import re
from unittest.mock import mock_open, patch from unittest.mock import mock_open, patch

View File

@@ -649,4 +649,17 @@ def test_fuzz_fromStr(valstr):
int(valstr) int(valstr)
assert isinstance(result, int) assert isinstance(result, int)
except ValueError: except ValueError:
assert isinstance(result, str) try:
float(valstr)
assert isinstance(result, float)
except ValueError:
assert isinstance(result, str)
def test_shorthex():
"""Test the shortest hex string representations"""
result = fromStr('0x0')
assert result == b'\x00'
result = fromStr('0x5')
assert result == b'\x05'
result = fromStr('0xffff')
assert result == b'\xff\xff'

View File

@@ -43,7 +43,7 @@ class Tunnel:
self.message = message self.message = message
super().__init__(self.message) super().__init__(self.message)
def __init__(self, iface, subnet="10.115", netmask="255.255.0.0"): def __init__(self, iface, subnet: str="10.115", netmask: str="255.255.0.0") -> None:
""" """
Constructor Constructor

View File

@@ -11,7 +11,7 @@ import threading
import time import time
import traceback import traceback
from queue import Queue from queue import Queue
from typing import List, NoReturn, Union from typing import Any, Dict, List, NoReturn, Optional, Set, Tuple, Union
from google.protobuf.json_format import MessageToJson from google.protobuf.json_format import MessageToJson
from google.protobuf.message import Message from google.protobuf.message import Message
@@ -31,7 +31,7 @@ from meshtastic.version import get_active_version
0925 Lakeview Research Saleae Logic (logic analyzer) 0925 Lakeview Research Saleae Logic (logic analyzer)
04b4:602a Cypress Semiconductor Corp. Hantek DSO-6022BL (oscilloscope) 04b4:602a Cypress Semiconductor Corp. Hantek DSO-6022BL (oscilloscope)
""" """
blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915, 0x0925, 0x04b4]) blacklistVids: Dict = dict.fromkeys([0x1366, 0x0483, 0x1915, 0x0925, 0x04b4])
"""Some devices are highly likely to be meshtastic. """Some devices are highly likely to be meshtastic.
0x239a RAK4631 0x239a RAK4631
@@ -39,21 +39,21 @@ blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915, 0x0925, 0x04b4])
whitelistVids = dict.fromkeys([0x239a, 0x303a]) whitelistVids = dict.fromkeys([0x239a, 0x303a])
def quoteBooleans(a_string): def quoteBooleans(a_string: str) -> str:
"""Quote booleans """Quote booleans
given a string that contains ": true", replace with ": 'true'" (or false) given a string that contains ": true", replace with ": 'true'" (or false)
""" """
tmp = a_string.replace(": true", ": 'true'") tmp: str = a_string.replace(": true", ": 'true'")
tmp = tmp.replace(": false", ": 'false'") tmp = tmp.replace(": false", ": 'false'")
return tmp return tmp
def genPSK256(): def genPSK256() -> bytes:
"""Generate a random preshared key""" """Generate a random preshared key"""
return os.urandom(32) return os.urandom(32)
def fromPSK(valstr): def fromPSK(valstr: str) -> Any:
"""A special version of fromStr that assumes the user is trying to set a PSK. """A special version of fromStr that assumes the user is trying to set a PSK.
In that case we also allow "none", "default" or "random" (to have python generate one), or simpleN In that case we also allow "none", "default" or "random" (to have python generate one), or simpleN
""" """
@@ -70,7 +70,7 @@ def fromPSK(valstr):
return fromStr(valstr) return fromStr(valstr)
def fromStr(valstr): def fromStr(valstr: str) -> Any:
"""Try to parse as int, float or bool (and fallback to a string as last resort) """Try to parse as int, float or bool (and fallback to a string as last resort)
Returns: an int, bool, float, str or byte array (for strings of hex digits) Returns: an int, bool, float, str or byte array (for strings of hex digits)
@@ -78,11 +78,12 @@ def fromStr(valstr):
Args: Args:
valstr (string): A user provided string valstr (string): A user provided string
""" """
val: Any
if len(valstr) == 0: # Treat an emptystring as an empty bytes if len(valstr) == 0: # Treat an emptystring as an empty bytes
val = bytes() val = bytes()
elif valstr.startswith("0x"): elif valstr.startswith("0x"):
# if needed convert to string with asBytes.decode('utf-8') # if needed convert to string with asBytes.decode('utf-8')
val = bytes.fromhex(valstr[2:]) val = bytes.fromhex(valstr[2:].zfill(2))
elif valstr.startswith("base64:"): elif valstr.startswith("base64:"):
val = base64.b64decode(valstr[7:]) val = base64.b64decode(valstr[7:])
elif valstr.lower() in {"t", "true", "yes"}: elif valstr.lower() in {"t", "true", "yes"}:
@@ -100,7 +101,15 @@ def fromStr(valstr):
return val return val
def pskToString(psk: bytes):
def toStr(raw_value):
"""Convert a value to a string that can be used in a config file"""
if isinstance(raw_value, bytes):
return "base64:" + base64.b64encode(raw_value).decode("utf-8")
return str(raw_value)
def pskToString(psk: bytes) -> str:
"""Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string""" """Given an array of PSK bytes, decode them into a human readable (but privacy protecting) string"""
if len(psk) == 0: if len(psk) == 0:
return "unencrypted" return "unencrypted"
@@ -122,12 +131,12 @@ def stripnl(s) -> str:
return " ".join(s.split()) return " ".join(s.split())
def fixme(message): def fixme(message: str) -> None:
"""Raise an exception for things that needs to be fixed""" """Raise an exception for things that needs to be fixed"""
raise Exception(f"FIXME: {message}") # pylint: disable=W0719 raise Exception(f"FIXME: {message}") # pylint: disable=W0719
def catchAndIgnore(reason, closure): def catchAndIgnore(reason: str, closure) -> None:
"""Call a closure but if it throws an exception print it and continue""" """Call a closure but if it throws an exception print it and continue"""
try: try:
closure() closure()
@@ -145,7 +154,7 @@ def findPorts(eliminate_duplicates: bool=False) -> List[str]:
all_ports = serial.tools.list_ports.comports() all_ports = serial.tools.list_ports.comports()
# look for 'likely' meshtastic devices # look for 'likely' meshtastic devices
ports = list( ports: List = list(
map( map(
lambda port: port.device, lambda port: port.device,
filter( filter(
@@ -184,12 +193,12 @@ class dotdict(dict):
class Timeout: class Timeout:
"""Timeout class""" """Timeout class"""
def __init__(self, maxSecs: int=20): def __init__(self, maxSecs: int=20) -> None:
self.expireTime: Union[int, float] = 0 self.expireTime: Union[int, float] = 0
self.sleepInterval: float = 0.1 self.sleepInterval: float = 0.1
self.expireTimeout: int = maxSecs self.expireTimeout: int = maxSecs
def reset(self): def reset(self) -> None:
"""Restart the waitForSet timer""" """Restart the waitForSet timer"""
self.expireTime = time.time() + self.expireTimeout self.expireTime = time.time() + self.expireTimeout
@@ -248,7 +257,7 @@ class Timeout:
class Acknowledgment: class Acknowledgment:
"A class that records which type of acknowledgment was just received, if any." "A class that records which type of acknowledgment was just received, if any."
def __init__(self): def __init__(self) -> None:
"""initialize""" """initialize"""
self.receivedAck = False self.receivedAck = False
self.receivedNak = False self.receivedNak = False
@@ -257,7 +266,7 @@ class Acknowledgment:
self.receivedTelemetry = False self.receivedTelemetry = False
self.receivedPosition = False self.receivedPosition = False
def reset(self): def reset(self) -> None:
"""reset""" """reset"""
self.receivedAck = False self.receivedAck = False
self.receivedNak = False self.receivedNak = False
@@ -270,18 +279,18 @@ class Acknowledgment:
class DeferredExecution: class DeferredExecution:
"""A thread that accepts closures to run, and runs them as they are received""" """A thread that accepts closures to run, and runs them as they are received"""
def __init__(self, name): def __init__(self, name) -> None:
self.queue = Queue() self.queue: Queue = Queue()
# this thread must be marked as daemon, otherwise it will prevent clients from exiting # this thread must be marked as daemon, otherwise it will prevent clients from exiting
self.thread = threading.Thread(target=self._run, args=(), name=name, daemon=True) self.thread = threading.Thread(target=self._run, args=(), name=name, daemon=True)
self.thread.daemon = True self.thread.daemon = True
self.thread.start() self.thread.start()
def queueWork(self, runnable): def queueWork(self, runnable) -> None:
"""Queue up the work""" """Queue up the work"""
self.queue.put(runnable) self.queue.put(runnable)
def _run(self): def _run(self) -> None:
while True: while True:
try: try:
o = self.queue.get() o = self.queue.get()
@@ -301,7 +310,7 @@ def our_exit(message, return_value=1) -> NoReturn:
sys.exit(return_value) sys.exit(return_value)
def support_info(): def support_info() -> None:
"""Print out info that helps troubleshooting of the cli.""" """Print out info that helps troubleshooting of the cli."""
print("") print("")
print("If having issues with meshtastic cli or python library") print("If having issues with meshtastic cli or python library")
@@ -330,7 +339,7 @@ def support_info():
print("Please add the output from the command: meshtastic --info") print("Please add the output from the command: meshtastic --info")
def remove_keys_from_dict(keys, adict): def remove_keys_from_dict(keys: Union[Tuple, List, Set], adict: Dict) -> Dict:
"""Return a dictionary without some keys in it. """Return a dictionary without some keys in it.
Will removed nested keys. Will removed nested keys.
""" """
@@ -345,33 +354,33 @@ def remove_keys_from_dict(keys, adict):
return adict return adict
def hexstr(barray): def hexstr(barray: bytes) -> str:
"""Print a string of hex digits""" """Print a string of hex digits"""
return ":".join(f"{x:02x}" for x in barray) return ":".join(f"{x:02x}" for x in barray)
def ipstr(barray): def ipstr(barray: bytes) -> str:
"""Print a string of ip digits""" """Print a string of ip digits"""
return ".".join(f"{x}" for x in barray) return ".".join(f"{x}" for x in barray)
def readnet_u16(p, offset): def readnet_u16(p, offset: int) -> int:
"""Read big endian u16 (network byte order)""" """Read big endian u16 (network byte order)"""
return p[offset] * 256 + p[offset + 1] return p[offset] * 256 + p[offset + 1]
def convert_mac_addr(val): def convert_mac_addr(val: str) -> Union[str, bytes]:
"""Convert the base 64 encoded value to a mac address """Convert the base 64 encoded value to a mac address
val - base64 encoded value (ex: '/c0gFyhb')) val - base64 encoded value (ex: '/c0gFyhb'))
returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b') returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b')
""" """
if not re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", val): if not re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", val):
val_as_bytes = base64.b64decode(val) val_as_bytes: bytes = base64.b64decode(val)
return hexstr(val_as_bytes) return hexstr(val_as_bytes)
return val return val
def snake_to_camel(a_string): def snake_to_camel(a_string: str) -> str:
"""convert snake_case to camelCase""" """convert snake_case to camelCase"""
# split underscore using split # split underscore using split
temp = a_string.split("_") temp = a_string.split("_")
@@ -380,16 +389,16 @@ def snake_to_camel(a_string):
return result return result
def camel_to_snake(a_string): def camel_to_snake(a_string: str) -> str:
"""convert camelCase to snake_case""" """convert camelCase to snake_case"""
return "".join(["_" + i.lower() if i.isupper() else i for i in a_string]).lstrip( return "".join(["_" + i.lower() if i.isupper() else i for i in a_string]).lstrip(
"_" "_"
) )
def detect_supported_devices(): def detect_supported_devices() -> Set:
"""detect supported devices based on vendor id""" """detect supported devices based on vendor id"""
system = platform.system() system: str = platform.system()
# print(f'system:{system}') # print(f'system:{system}')
possible_devices = set() possible_devices = set()
@@ -447,9 +456,9 @@ def detect_supported_devices():
return possible_devices return possible_devices
def detect_windows_needs_driver(sd, print_reason=False): def detect_windows_needs_driver(sd, print_reason=False) -> bool:
"""detect if Windows user needs to install driver for a supported device""" """detect if Windows user needs to install driver for a supported device"""
need_to_install_driver = False need_to_install_driver: bool = False
if sd: if sd:
system = platform.system() system = platform.system()
@@ -475,7 +484,7 @@ def detect_windows_needs_driver(sd, print_reason=False):
return need_to_install_driver return need_to_install_driver
def eliminate_duplicate_port(ports): def eliminate_duplicate_port(ports: List) -> List:
"""Sometimes we detect 2 serial ports, but we really only need to use one of the ports. """Sometimes we detect 2 serial ports, but we really only need to use one of the ports.
ports is a list of ports ports is a list of ports
@@ -508,9 +517,9 @@ def eliminate_duplicate_port(ports):
return new_ports return new_ports
def is_windows11(): def is_windows11() -> bool:
"""Detect if Windows 11""" """Detect if Windows 11"""
is_win11 = False is_win11: bool = False
if platform.system() == "Windows": if platform.system() == "Windows":
if float(platform.release()) >= 10.0: if float(platform.release()) >= 10.0:
patch = platform.version().split(".")[2] patch = platform.version().split(".")[2]
@@ -524,7 +533,7 @@ def is_windows11():
return is_win11 return is_win11
def get_unique_vendor_ids(): def get_unique_vendor_ids() -> Set[str]:
"""Return a set of unique vendor ids""" """Return a set of unique vendor ids"""
vids = set() vids = set()
for d in supported_devices: for d in supported_devices:
@@ -533,7 +542,7 @@ def get_unique_vendor_ids():
return vids return vids
def get_devices_with_vendor_id(vid): def get_devices_with_vendor_id(vid: str) -> Set: #Set[SupportedDevice]
"""Return a set of unique devices with the vendor id""" """Return a set of unique devices with the vendor id"""
sd = set() sd = set()
for d in supported_devices: for d in supported_devices:
@@ -542,11 +551,11 @@ def get_devices_with_vendor_id(vid):
return sd return sd
def active_ports_on_supported_devices(sds, eliminate_duplicates=False): def active_ports_on_supported_devices(sds, eliminate_duplicates=False) -> Set[str]:
"""Return a set of active ports based on the supplied supported devices""" """Return a set of active ports based on the supplied supported devices"""
ports = set() ports: Set = set()
baseports = set() baseports: Set = set()
system = platform.system() system: str = platform.system()
# figure out what possible base ports there are # figure out what possible base ports there are
for d in sds: for d in sds:
@@ -604,13 +613,13 @@ def active_ports_on_supported_devices(sds, eliminate_duplicates=False):
for com_port in com_ports: for com_port in com_ports:
ports.add(com_port) ports.add(com_port)
if eliminate_duplicates: if eliminate_duplicates:
ports = eliminate_duplicate_port(list(ports)) portlist: List = eliminate_duplicate_port(list(ports))
ports.sort() portlist.sort()
ports = set(ports) ports = set(portlist)
return ports return ports
def detect_windows_port(sd): def detect_windows_port(sd) -> Set[str]: #"sd" is a SupportedDevice from meshtastic.supported_device
"""detect if Windows port""" """detect if Windows port"""
ports = set() ports = set()
@@ -635,20 +644,26 @@ def detect_windows_port(sd):
return ports return ports
def check_if_newer_version(): def check_if_newer_version() -> Optional[str]:
"""Check pip to see if we are running the latest version.""" """Check pip to see if we are running the latest version."""
pypi_version = None pypi_version: Optional[str] = None
try: try:
url = "https://pypi.org/pypi/meshtastic/json" url: str = "https://pypi.org/pypi/meshtastic/json"
data = requests.get(url, timeout=5).json() data = requests.get(url, timeout=5).json()
pypi_version = data["info"]["version"] pypi_version = data["info"]["version"]
except Exception: except Exception:
pass pass
act_version = get_active_version() act_version = get_active_version()
if pypi_version is None:
return None
try: try:
parsed_act_version = pkg_version.parse(act_version) parsed_act_version = pkg_version.parse(act_version)
parsed_pypi_version = pkg_version.parse(pypi_version) parsed_pypi_version = pkg_version.parse(pypi_version)
#Note: if handed "None" when we can't download the pypi_version,
#this gets a TypeError:
#"TypeError: expected string or bytes-like object, got 'NoneType'"
#Handle that below?
except pkg_version.InvalidVersion: except pkg_version.InvalidVersion:
return pypi_version return pypi_version

2827
poetry.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
[tool.poetry] [tool.poetry]
name = "meshtastic" name = "meshtastic"
version = "2.5.0" version = "2.5.4"
description = "Python API & client shell for talking to Meshtastic devices" description = "Python API & client shell for talking to Meshtastic devices"
authors = ["Meshtastic Developers <contact@meshtastic.org>"] authors = ["Meshtastic Developers <contact@meshtastic.org>"]
license = "GPL-3.0-only" license = "GPL-3.0-only"
readme = "README.md" readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.9,<3.13" # 3.9 is needed for pandas, bleak requires a max of 3.13 for some reason python = "^3.9,<3.14" # 3.9 is needed for pandas, bleak requires <3.14
pyserial = "^3.5" pyserial = "^3.5"
protobuf = ">=5.26.0" protobuf = ">=5.26.0"
dotmap = "^1.3.30" dotmap = "^1.3.30"
@@ -19,13 +19,14 @@ requests = "^2.31.0"
pyparsing = "^3.1.2" pyparsing = "^3.1.2"
pyyaml = "^6.0.1" pyyaml = "^6.0.1"
pypubsub = "^4.0.3" pypubsub = "^4.0.3"
bleak = "^0.21.1" bleak = "^0.22.3"
packaging = "^24.0" packaging = "^24.0"
print-color = "^0.4.6" print-color = "^0.4.6"
dash = { version = "^2.17.1", optional = true } dash = { version = "^2.17.1", optional = true }
pytap2 = { version = "^2.3.0", optional = true } pytap2 = { version = "^2.3.0", optional = true }
dash-bootstrap-components = { version = "^1.6.0", optional = true } dash-bootstrap-components = { version = "^1.6.0", optional = true }
pandas = { version = "^2.2.2", optional = true } pandas = { version = "^2.2.2", optional = true }
pandas-stubs = { version = "^2.2.2.240603", optional = true }
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
hypothesis = "^6.103.2" hypothesis = "^6.103.2"
@@ -43,7 +44,6 @@ types-requests = "^2.31.0.20240406"
types-setuptools = "^69.5.0.20240423" types-setuptools = "^69.5.0.20240423"
types-pyyaml = "^6.0.12.20240311" types-pyyaml = "^6.0.12.20240311"
pyarrow-stubs = "^10.0.1.7" pyarrow-stubs = "^10.0.1.7"
pandas-stubs = "^2.2.2.240603"
[tool.poetry.group.powermon] [tool.poetry.group.powermon]
optional = true optional = true