Compare commits

..

318 Commits

Author SHA1 Message Date
github-actions
872fbef5d6 bump version to 2.5.9 2024-12-29 03:22:52 +00:00
Ian McEwen
ec4fbe3a59 protobufs: v2.5.18 2024-12-28 20:18:13 -07:00
Ian McEwen
6bab385380 Tweak error messaging in the onReceive function for sendtext packets 2024-12-27 10:04:36 -07:00
Ian McEwen
b8178d513a bump alpha version 2024-12-27 09:54:15 -07:00
Ian McEwen
f4c085fc50 Merge pull request #714 from loic-fejoz/feature/waypoint
Waypoint creation/move/deletion
2024-12-27 09:46:45 -07:00
Ian McEwen
57f0598082 Fix some pylint complaints 2024-12-27 09:40:17 -07:00
github-actions
55d3188408 bump version to 2.5.8 2024-12-27 12:06:45 +00:00
Ben Meadors
7b64fbb71b Merge pull request #725 from meshtastic/dependabot/pip/jinja2-3.1.5
Bump jinja2 from 3.1.4 to 3.1.5
2024-12-25 08:42:58 -06:00
dependabot[bot]
7f85eb0285 Bump jinja2 from 3.1.4 to 3.1.5
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.4...3.1.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-24 00:32:14 +00:00
Ben Meadors
d05ef17ab3 Merge pull request #724 from meshtastic/send-alert
Add sendAlert method on mesh interface
2024-12-23 10:48:49 -06:00
Ben Meadors
d161291ca4 Add sendAlert method on mesh interface 2024-12-22 09:09:54 -06:00
Ian McEwen
4e267c75b0 bump to 2.5.8a0 2024-12-21 20:49:32 -07:00
github-actions
f56b9eefa6 bump version to 2.5.7 2024-12-22 03:46:10 +00:00
Ian McEwen
2de1f1921c Merge pull request #723 from ianmcorvidae/add-wcwidth-optional
Add 'wcwidth' as an optional cli dependency
2024-12-21 20:32:47 -07:00
Ian McEwen
227507780e Add 'wcwidth' as an optional cli dependency
Fixes #598 when installed as meshtastic[cli], as should now be
recommended by documentation.
2024-12-21 20:29:07 -07:00
Ian McEwen
9f286c9023 Merge pull request #721 from ianmcorvidae/shell-completion
Add very simple shell completion using argcomplete
2024-12-21 20:26:06 -07:00
Ian McEwen
0b1545393e Make mypy happy with the optional import of argcomplete 2024-12-21 20:23:35 -07:00
Ian McEwen
245a9e40b1 Add very simple shell completion using argcomplete 2024-12-21 20:12:26 -07:00
Ian McEwen
749c6a70bc Merge pull request #720 from ianmcorvidae/winserver-fix
Fix windows-11 detection for non-float platform.release() values
2024-12-21 14:22:55 -07:00
Ian McEwen
afd071c24e Fix windows-11 detection for non-float platform.release() values (fixes #639) 2024-12-21 14:17:41 -07:00
Ian McEwen
29f355bd61 Merge pull request #719 from ianmcorvidae/ignore-nodes
Support setting nodes ignored in nodedb (with 2.5.13+ firmware)
2024-12-21 13:50:44 -07:00
Ian McEwen
4b6d7a8587 Support setting nodes ignored in nodedb (with 2.5.13+ firmware) 2024-12-21 13:46:17 -07:00
Loïc Fejoz
7cc18e9df6 fix(waypoint): Missing methods
Add missing methods callbacks regarding waypoints.
2024-12-21 10:36:36 +01:00
Ian McEwen
a765bccf4d Merge pull request #718 from ianmcorvidae/show-unknown-more-nicely
Display unknown hop count as '?', and header as just 'Hops'
2024-12-20 23:08:19 -07:00
Ian McEwen
f950ecae2d Display unknown hop count as '?', and header as just 'Hops' 2024-12-20 23:04:36 -07:00
Ian McEwen
7c7170a5dd Merge pull request #706 from meshtastic/dependabot/pip/tornado-6.4.2
Bump tornado from 6.4.1 to 6.4.2
2024-12-20 22:50:01 -07:00
Ian McEwen
df191e149b Merge pull request #713 from esev/favorite
Support setting/removing nodes as favorites
2024-12-20 22:36:23 -07:00
Ian McEwen
843abe587f Back out release workflow changes from hotfix 2024-12-20 14:59:26 -07:00
Ian McEwen
ff7dcc3afb Merge branch '2.5.6-hotfix' 2024-12-20 14:47:52 -07:00
Ian McEwen
d66b8fa9dd fix import-related errors 2024-12-20 14:45:54 -07:00
Ian McEwen
f6f8ccfcbc Fix message_to_json_shows_all test for added MyNodeInfo fields 2024-12-20 13:51:27 -07:00
Ian McEwen
cace959ca4 Update protobufs (to 2.5.17) and main python version (to 2.5.7) 2024-12-20 13:45:58 -07:00
github-actions
01ffd83d64 bump version to 2.5.6 2024-12-20 20:42:21 +00:00
Loïc Fejoz
9284a848f2 feat(waypoint): Waypoint creation/deletion
Add methods to send (create or move), delete waypoint.
Add an example script to create, move, delete waypoint.
2024-12-18 17:36:58 +01:00
Eric Severance
18ac0d6d5c Support setting/removing nodes as favorites 2024-12-17 17:38:51 -08:00
Ian McEwen
7c89e231bd Merge pull request #711 from ianmcorvidae/optionalize-deps
Make several dependencies optional (dotmap, print_color, and pyqrcode)
2024-12-12 21:19:15 -07:00
Ian McEwen
4673824236 Add the newly optional dependencies to an extras group 2024-12-12 21:14:58 -07:00
Ian McEwen
d87eddfd33 Make pyqrcode optional 2024-12-12 21:01:38 -07:00
Ian McEwen
31f322f1c2 Make dotmap (via meshtastic.test) and print_color optional 2024-12-12 20:59:22 -07:00
Ian McEwen
89b41c1a19 Remove pexpect too 2024-12-12 20:40:59 -07:00
Ian McEwen
1a5ca789c2 Remove pyparsing and webencodings dependencies, unsure why they're here at all 2024-12-12 19:48:29 -07:00
Ian McEwen
03ac322583 reset mypy-protobuf to non-optional, hopefully without breaking stuff 2024-11-26 14:35:48 -07:00
Ian McEwen
c63814358a prep 2.5.6a0 & protobufs 2024-11-26 14:31:33 -07:00
github-actions
663fabce74 bump version to 2.5.5 2024-11-26 21:22:11 +00:00
Ian McEwen
6243965044 Merge pull request #707 from meshtastic/request-localstats
Allow request-telemetry to elicit LocalStats response
2024-11-24 11:31:01 -07:00
Ben Meadors
b180b6fb15 Allow request-telemetry to illicit LocalStats response 2024-11-24 08:19:40 -06:00
dependabot[bot]
7bb8e4e9dd Bump tornado from 6.4.1 to 6.4.2
Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4.1 to 6.4.2.
- [Changelog](https://github.com/tornadoweb/tornado/blob/v6.4.2/docs/releases.rst)
- [Commits](https://github.com/tornadoweb/tornado/compare/v6.4.1...v6.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-22 22:17:33 +00:00
Ian McEwen
4c7ac60be6 appease mypy too 2024-11-15 11:55:25 -07:00
Ian McEwen
0b086d10f8 appease pylint 2024-11-15 11:50:35 -07:00
Ian McEwen
426795fccd semi-experimentally, add fallback code for message_to_json, allowing older protobuf library versions 2024-11-15 11:47:17 -07:00
Ian McEwen
fb88ee114c bump protobufs to 2.5.5 2024-11-01 09:06:02 -07:00
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
github-actions
f7724295f9 bump version to 2.5.0 2024-09-12 16:27:45 +00:00
Ian McEwen
cc2067b729 clean up settings response to use CopyFrom to be better at repeated and nested fields 2024-09-11 17:10:04 -07:00
Ian McEwen
180ddbcd1a Merge pull request #672 from ianmcorvidae/detect-repeating
Detect repeating fields using field labels, enabling admin key to be set
2024-09-11 16:36:29 -07:00
Ian McEwen
3bbd02c915 Detect repeating fields using field labels, enabling admin key to be set 2024-09-11 16:34:03 -07:00
Ian McEwen
23e6eca056 Merge pull request #671 from ianmcorvidae/factory-reset-update
Split factory reset into two variants
2024-09-11 09:43:16 -07:00
Ian McEwen
21ff4a1a4a Split factory reset into two variants 2024-09-11 09:40:26 -07:00
Ian McEwen
ef6db0e48c Merge branch 'master' into 2.5 2024-09-11 09:21:01 -07:00
Ian McEwen
43b0993aaa Merge branch 'master' into 2.5 2024-09-11 09:19:43 -07:00
github-actions
9423a8a8b9 bump version to 2.4.3 2024-09-11 16:16:12 +00:00
Ian McEwen
33a13f715e 2.4.3 prep (protobufs, alpha version) 2024-09-11 09:12:41 -07:00
github-actions
0aac077ce7 bump version to 2.4.2 2024-09-11 16:05:25 +00: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
Ian McEwen
0d26c26f7e update protobufs to master 2024-09-06 23:13:31 -07:00
Ian McEwen
aa6f09635a Merge pull request #661 from meshtastic/dependabot/pip/jupyterlab-4.2.5
Bump jupyterlab from 4.2.3 to 4.2.5
2024-09-06 11:30:17 -07:00
Ian McEwen
83b0dcad56 Merge pull request #667 from ianmcorvidae/telemetry-updating
Update telemetry in interface.nodes on receiving device metrics packets
2024-09-06 11:10:17 -07:00
Ian McEwen
78d8403bbd Update telemetry in interface.nodes on receiving device metrics packets 2024-09-06 00:08:20 -07:00
Ian McEwen
0813e8dba6 protobufs: v2.4.2; bump to alpha version 2024-09-05 14:27:38 -07:00
github-actions
23bb2e26f9 bump version to 2.4.1 2024-09-05 21:22:03 +00:00
Ian McEwen
399dd477b8 Merge branch 'master' into 2.5 2024-09-05 14:00:35 -07:00
Ian McEwen
b59ecff272 Merge pull request #666 from ianmcorvidae/fix-set-owner
Ensure set-owner combined with set-owner-short sets both values
2024-09-05 13:58:36 -07:00
Ian McEwen
17f3605736 Ensure set-owner combined with set-owner-short sets both values 2024-09-05 13:58:02 -07:00
Ian McEwen
a689fd73a2 Deprecate --no-time and remove behavior, followup to #663 2024-09-05 13:49:28 -07:00
Ian McEwen
da30e1141a Merge pull request #663 from meshtastic/outtatime
Don't automatically set the time from Python
2024-09-05 13:47:36 -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
Ian McEwen
b4bd9568e4 Merge pull request #664 from lysol/master
Reuse node to prevent overwriting channel settings
2024-09-03 16:12:13 -07:00
Derek Arnold
aed4f25cf5 Reuse node to prevent overwriting channel settings to be sent out over the admin channel 2024-09-03 17:41:05 -05:00
Jonathan Bennett
5c312bedc1 Remove assert from test, due to removed position time 2024-08-30 01:15:40 -05:00
Jonathan Bennett
428e9a228c Remove unused time variable 2024-08-29 23:09:04 -05:00
Jonathan Bennett
4500850063 Don't automatically set the time from Python
The Python MO is to do as little as possible beyond what the user has intentionally instructed. So don't try to set the time automatically.
2024-08-29 22:29:20 -05:00
dependabot[bot]
aedaa3748d Bump jupyterlab from 4.2.3 to 4.2.5
Bumps [jupyterlab](https://github.com/jupyterlab/jupyterlab) from 4.2.3 to 4.2.5.
- [Release notes](https://github.com/jupyterlab/jupyterlab/releases)
- [Changelog](https://github.com/jupyterlab/jupyterlab/blob/@jupyterlab/lsp@4.2.5/CHANGELOG.md)
- [Commits](https://github.com/jupyterlab/jupyterlab/compare/@jupyterlab/lsp@4.2.3...@jupyterlab/lsp@4.2.5)

---
updated-dependencies:
- dependency-name: jupyterlab
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-29 18:23:54 +00:00
Ian McEwen
4b60c5b457 Merge pull request #660 from GUVWAF/2way_traceroute
HopStart should be set for route back to be valid
2024-08-26 11:40:08 -07:00
GUVWAF
c92474cf36 HopStart should be set for route back to be valid 2024-08-26 20:32:51 +02:00
Ian McEwen
b2acc84717 remove trailing whitespace 2024-08-25 18:27:34 -07:00
Ian McEwen
1981f0e899 Merge pull request #654 from GUVWAF/2way_traceroute
Show two-way traceroute result with SNR if available
2024-08-25 16:54:36 -07:00
Ian McEwen
bcce5687c5 Merge pull request #659 from wnagele/unreachable_code
Code block unreachable
2024-08-25 13:18:06 -07:00
Ian McEwen
b9d805057f Merge pull request #658 from wnagele/master
Add method to be able to send heartbeat
2024-08-25 13:17:51 -07:00
Wolfgang Nagele
d77335caa7 Add sendHeartbeat doc-string 2024-08-25 22:13:08 +02:00
Wolfgang Nagele
b692ef4cfb Add method to be able to send heartbeat 2024-08-25 14:47:41 +02:00
Wolfgang Nagele
e725292ee0 Code block unreachable 2024-08-25 14:39:37 +02:00
Ian McEwen
84dff75399 allow for underscore-only stuff in camel_to_snake, silly as that is 2024-08-24 23:38:48 -07:00
Ian McEwen
df12b8a659 appease the pylint beast 2024-08-24 23:34:28 -07:00
Ian McEwen
eedf42b904 add more fuzzing tests for utility functions 2024-08-24 22:25:18 -07:00
Ian McEwen
e7ed254d9d appease pylint, tests, mypy 2024-08-24 22:00:29 -07:00
Ian McEwen
dfa29bbb7c Add ensureSessionKey to setURL and setFixedPosition calls 2024-08-24 21:50:44 -07:00
Ian McEwen
5a06888cc7 Merge pull request #656 from meshtastic/more-admin
More admin
2024-08-24 21:48:19 -07:00
Jonathan Bennett
a9e2168f1d Refactor to add ensureSessionKey function 2024-08-24 20:18:57 -05:00
Jonathan Bennett
eec745c861 Add missed colon in if statement 2024-08-24 19:29:18 -05:00
Jonathan Bennett
4cc283d004 Add the admin sessionkey_only request 2024-08-24 17:27:42 -05:00
Ian McEwen
bc508ff9e6 cleanup admin stuff a little 2024-08-24 14:15:37 -07:00
Ian McEwen
ff72fc4804 Merge pull request #655 from meshtastic/show-pubkey
Adds admin session_passkey handling for 2.5 remote admin
2024-08-24 14:12:41 -07:00
Jonathan Bennett
78399503c5 Adds handlers to get and set admin session_key for remote admin 2024-08-24 15:32:44 -05:00
GUVWAF
216fd7ddc4 Show two-way traceroute result with SNR if available 2024-08-24 10:07:48 +02:00
Ian McEwen
688693d2fb Merge pull request #653 from meshtastic/show-pubkey
Adds handler function for public key, and adds to nodedb display
2024-08-23 20:41:40 -07:00
Jonathan Bennett
15b5e93563 Correctly format public key and add to node list 2024-08-23 22:21:54 -05:00
Ian McEwen
1bbcc452ae precalculate bit-shifts and don't generate too-large random numbers for packet ID generation 2024-08-23 15:34:25 -07:00
Ben Meadors
1abe00d0b2 Merge pull request #652 from ianmcorvidae/randomized-packet-ids
randomize packet IDs as firmware does
2024-08-23 17:30:07 -05:00
Ian McEwen
c8cf8094c3 randomize packet IDs as firmware does 2024-08-23 13:18:23 -07:00
Ian McEwen
477690edde mark protobufs folder as generated code for github 2024-08-21 22:27:18 -07:00
Ian McEwen
58466f2ab7 Add wiring for security config in node.py 2024-08-21 22:11:45 -07:00
Ian McEwen
bb6f51eb43 protobufs: 2.5 (in progress, untagged) 2024-08-21 21:58:56 -07:00
Ian McEwen
48987c38e2 set alpha version 2024-08-21 21:55:43 -07:00
Ian McEwen
abf9e96d3d Set release tag to the version update commit sha, not the pre-update one 2024-08-21 20:07:12 -07:00
Ian McEwen
740f0f0961 set prerelease version to match protobufs 2024-08-21 19:53:23 -07:00
Ian McEwen
abb00251c0 protobufs: v2.4.1 2024-08-21 19:52:56 -07:00
github-actions
3335b3d651 bump version to 2.4.0 2024-08-22 02:23:53 +00:00
Ian McEwen
4ad776f219 revert poetry version minor change -- with alpha version set, it's unnecessary 2024-08-21 19:20:10 -07:00
Ian McEwen
d5f732263a set alpha 2.4.0a0 version 2024-08-21 19:18:48 -07:00
Ian McEwen
c59583e4bd Make version-bump commit include the version being bumped to 2024-08-21 19:17:58 -07:00
Ian McEwen
28d8355547 Use poetry version minor, to revert after 2.4.0 release 2024-08-21 19:11:12 -07:00
Ian McEwen
d57186d1e4 protobufs: v2.4.0 2024-08-21 19:07:35 -07:00
github-actions
a8d86dee2d bump version 2024-08-11 17:22:31 +00:00
Ian McEwen
40d03a6ea1 mess with more stuff to try to make powermon optional 2024-08-11 10:19:38 -07:00
Ian McEwen
6757f5cdb5 slog depends on powermon, so move it within the optional block as well 2024-08-11 10:16:22 -07:00
Ian McEwen
b8c0a62b27 Add powermon group to ci install 2024-08-11 10:11:35 -07:00
Ian McEwen
72de803195 Attempt to make powermon stuff optional, hopefully allowing pypi release 2024-08-11 10:08:40 -07:00
Ian McEwen
84ffdcdb8c Merge pull request #647 from ianmcorvidae/improve-mypy
Add/update some types to be at least as backwards-compatible as we can be
2024-08-08 09:47:02 -07:00
Ian McEwen
5366ddf770 Add/update some types to be at least as backwards-compatible as we can be 2024-08-08 09:43:43 -07:00
Ian McEwen
fd4282b401 Merge pull request #636 from geeksville/pr-powermon2
final powermon / power analysis reporting changes
2024-08-08 09:36:16 -07:00
Ian McEwen
e84a3cb468 appease pylint 2024-08-04 12:26:20 -07:00
Ian McEwen
2ae18c1903 Clarify documentation of position options slightly 2024-08-04 12:22:57 -07:00
Kevin Hester
8096d10276 Do code coverage testing on analysis (using stored device data) 2024-08-01 10:07:48 -07:00
Kevin Hester
b0e1d961fd add vscode config for auto running python tests 2024-08-01 09:50:41 -07:00
Kevin Hester
dfa3d46a34 add pandas as an optional dependancy (for analytics only) 2024-07-31 17:03:44 -07:00
Kevin Hester
de29bf34ef install all extras when running poetry inside of CI 2024-07-31 16:03:22 -07:00
Kevin Hester
bf71e09091 get test coverage on powermon and slog stuff 2024-07-31 15:46:37 -07:00
Kevin Hester
4906f79be5 fix linter warnings (and alas: reformat __main__.py)
main.py's only real change is
        log_set: Optional[LogSet] = None  # type: ignore[annotation-unchecked]
Everything else is the automated reformatting to match our trunk formatting
rules.
2024-07-31 15:19:16 -07:00
Kevin Hester
a4715171e4 Add basic arg parsing to the meshtastic analysis stuff 2024-07-31 14:41:47 -07:00
Kevin Hester
c8eb202c15 cleanup and document analysis stuff 2024-07-31 13:40:29 -07:00
Kevin Hester
ea0c7abc3d Merge remote-tracking branch 'root/master' into pr-powermon2
# Conflicts:
#	meshtastic/mesh_interface.py
2024-07-23 16:57:08 -07:00
Kevin Hester
66f83835d9 use bootstrap for layout 2024-07-11 16:56:01 -07:00
Kevin Hester
39e03dbad8 add beginnings of analysis viewer (and fix poetry extras usage for tunnel) 2024-07-11 16:39:05 -07:00
Kevin Hester
4dbf9b94e9 do a new power measurement every 2ms(ish) 2024-07-11 12:27:16 -07:00
Kevin Hester
b464e90368 make ppk2 power meter threadsafe 2024-07-11 12:19:16 -07:00
Kevin Hester
3c76e19c33 poll for power readings much more rapidly - traces now look great 2024-07-11 11:49:12 -07:00
Kevin Hester
7e007e7e24 make ArrowWriter thread safe 2024-07-11 11:48:53 -07:00
Ian McEwen
d996965f0f Merge pull request #638 from ianmcorvidae/request-telemetry-uptime
Include uptime_seconds in sendTelemetry and print upon response
2024-07-10 17:31:45 -07:00
Ian McEwen
fd9b691b74 Include uptime_seconds in sendTelemetry and print upon response 2024-07-10 17:29:42 -07:00
Kevin Hester
628a4cb9be Always use IDENTICAL timestamps so the power and slog reports can match 2024-07-10 16:44:56 -07:00
Kevin Hester
d0db5cae13 Store much higher (time) res power readings any time we've just fetched
new readings.  This allows for better plotting/analysis but still keeping
runtime polling low.
2024-07-10 16:44:27 -07:00
Kevin Hester
0bc608d8cf fix analysis imports to import less 2024-07-10 16:43:24 -07:00
William Stearns
60de9dddb1 Remove references to BLEClient breaking CI checks 2024-07-09 19:54:01 -04:00
Kevin Hester
043530afca fix linter warnings 2024-07-08 09:17:52 -07:00
Kevin Hester
eb45c16f89 Merge remote-tracking branch 'root/master' into pr-powermon2
# Conflicts:
#	meshtastic/mesh_interface.py
2024-07-08 08:53:45 -07:00
Ian McEwen
3c772b5a31 Attempt adding python 3.12 to CI 2024-07-07 21:15:29 -07:00
Ian McEwen
865bb6a497 Remove python 3.8 warning. protobufs: v2.3.15 (brought in by merge) 2024-07-07 21:00:26 -07:00
Ian McEwen
c04943308a Merge pull request #607 from geeksville/pr-powermon
PowerMon/StructuredLogging support
2024-07-07 20:56:28 -07:00
github-actions
62cfe2d7fe bump version 2024-07-08 03:52:47 +00:00
Kevin Hester
8c63f4dec6 always write using correct schema for the file 2024-07-07 15:17:26 -07:00
Kevin Hester
9297732806 fix possible race with thread shutdown. somehow receiveThread can be null 2024-07-07 15:17:09 -07:00
Kevin Hester
a6c3e5cba8 properly parse all structured log messages 2024-07-07 14:58:30 -07:00
Kevin Hester
d35423a816 strip \n if it was incorrectly added by the device
+        # Devices should _not_ be including a newline at the end of each log-line str (especially when
+        # encapsulated as a LogRecord).  But to cope with old device loads, we check for that and fix it here:
+        if line.endswith("\n"):
+            line = line[:-1]

Also: auto reformatting per our trunk formatting rules.
2024-07-07 14:57:44 -07:00
Kevin Hester
84b4188211 Gracefully cope with exceptions during power-stress test 2024-07-07 14:07:27 -07:00
Kevin Hester
72e0f2a92b Don't silently ingnore malformed protobufs (the \0 in the device side
was at fault)
2024-07-07 13:47:02 -07:00
Kevin Hester
ecbda74bd6 make PPK2 power supply monitor work in supply-mode 2024-07-06 16:41:33 -07:00
Kevin Hester
fb191092fb gracefully shutdown when BLE device connect fails 2024-07-06 16:27:20 -07:00
Kevin Hester
1e447cb52a also store raw log messages in the slog file. 2024-07-06 15:26:15 -07:00
Kevin Hester
462d9a83df Automatically extract and store all known structured-logs 2024-07-06 15:07:13 -07:00
Kevin Hester
4c02114b75 fix null pointer if closing an interface which was already shutting down 2024-07-06 13:43:19 -07:00
Kevin Hester
42e069455e transition through power stress states and capture data
meshtastic-py3.12kevinh@kdesktop:~/development/meshtastic/meshtastic-python$  cd /home/kevinh/development/meshtastic/meshtastic-python ; /usr/bin/env /home/kevinh/.cache/pypoetry/virtualenvs/meshtastic-l6tP90xw-py3.12/bin/python /home/kevinh/.vscode/extensions/ms-python.debugpy-2024.6.0-linux-x64/bundled/libs/debugpy/adapter/../../debugpy/launcher 52521 -- -m meshtastic --slog --power-ppk2-meter --power-stress --power-voltage 3.3
INFO file:ppk2.py __init__ line:52 Connected to Power Profiler Kit II (PPK2)
INFO file:__main__.py create_power_meter line:1022 Setting power supply to 3.3 volts
Connected to radio
INFO file:slog.py __init__ line:183 Writing slogs to /home/kevinh/.local/share/meshtastic/slogs/20240706-123803
INFO file:stress.py syncPowerStress line:68 Sending power stress command PRINT_INFO
INFO file:stress.py run line:105 Running power stress test 48 for 5.0 seconds
INFO file:stress.py syncPowerStress line:68 Sending power stress command LED_ON
INFO file:stress.py run line:105 Running power stress test 80 for 5.0 seconds
INFO file:stress.py syncPowerStress line:68 Sending power stress command BT_OFF
INFO file:stress.py run line:105 Running power stress test 81 for 5.0 seconds
INFO file:stress.py syncPowerStress line:68 Sending power stress command BT_ON
INFO file:stress.py run line:105 Running power stress test 34 for 5.0 seconds
INFO file:stress.py syncPowerStress line:68 Sending power stress command CPU_FULLON
INFO file:stress.py run line:105 Running power stress test 32 for 5.0 seconds
INFO file:stress.py syncPowerStress line:68 Sending power stress command CPU_IDLE
INFO file:stress.py run line:105 Running power stress test 33 for 5.0 seconds
INFO file:stress.py syncPowerStress line:68 Sending power stress command CPU_DEEPSLEEP
INFO file:stress.py run line:108 Power stress test complete.
INFO file:slog.py close line:201 Closing slogs in /home/kevinh/.local/share/meshtastic/slogs/20240706-123803
WARNING file:arrow.py close line:67 Discarding empty file: /home/kevinh/.local/share/meshtastic/slogs/20240706-123803/slog.arrow
INFO file:arrow.py close line:70 Compressing log data into /home/kevinh/.local/share/meshtastic/slogs/20240706-123803/power.feather
meshtastic-py3.12kevinh@kdesktop:~/development/meshtastic/meshtastic-python$
2024-07-06 12:43:34 -07:00
Kevin Hester
63327986b4 fix incorrect Vid - thanks @ianmcorvidae for notixing.
0x04b4 is cypress semi but commonly used in Chinese oscopes (like mine).
So it was supposed to be a blacklist not a whitelist!
2024-07-03 20:11:59 -07:00
Kevin Hester
5695ec7102 change --slog to use nargs 2024-07-03 11:00:42 -07:00
Kevin Hester
ae2ef78560 fix linter warnings
(note: the linter test for min/max is buggy so disabled)
2024-07-03 09:57:24 -07:00
Kevin Hester
2f5a736e1f Merge remote-tracking branch 'root/master' into pr-powermon
# Conflicts:
#	meshtastic/ble_interface.py
#	meshtastic/protobuf/mesh_pb2.py
#	meshtastic/protobuf/powermon_pb2.py
#	meshtastic/protobuf/powermon_pb2.pyi
2024-07-03 09:22:45 -07:00
Kevin Hester
6da04f7a15 Merge branch 'pr-bletweak' into pr-powermon
# Conflicts:
#	.vscode/launch.json
2024-06-30 13:03:33 -07:00
Kevin Hester
8f98878cac Merge branch 'ble-logging' into pr-powermon
# Conflicts:
#	.vscode/launch.json
#	meshtastic/protobuf/config_pb2.py
#	meshtastic/protobuf/mesh_pb2.py
#	meshtastic/protobuf/mesh_pb2.pyi
2024-06-30 07:06:32 -07:00
Kevin Hester
13ca8fd681 debug launch configs 2024-06-30 06:31:09 -07:00
Kevin Hester
1da687cf2d move @thebentern spiffy logging so it is shared with !ble log sources 2024-06-29 16:15:32 -07:00
Kevin Hester
42236f2de8 Merge branch 'ble-logging' into pr-powermon
# Conflicts:
#	poetry.lock
#	pyproject.toml
2024-06-29 15:59:03 -07:00
Kevin Hester
821d3e95f1 remvoe unneeded paren 2024-06-29 08:40:16 -07:00
Kevin Hester
542f99b28f handle the new LogRecord protobufs
(backwards/forwards compatible) with old firmware
2024-06-28 09:41:17 -07:00
Kevin Hester
dabb4ea44c PowerStress client approximately works 2024-06-28 09:40:33 -07:00
Kevin Hester
119be81000 PowerStress WIP 2024-06-27 16:37:58 -07:00
Kevin Hester
c9351236e6 blacklist hantek oscilliscope 2024-06-26 17:28:55 -07:00
Kevin Hester
2294546560 fix bogus high current reading on first ppk2 read 2024-06-26 16:43:14 -07:00
Kevin Hester
67bb6665f2 the stock PPK2 API is super inefficient, remove lots of buffering 2024-06-26 15:29:18 -07:00
Kevin Hester
1587c31d18 Merge remote-tracking branch 'root/master' into pr-powermon 2024-06-26 13:33:34 -07:00
Kevin Hester
715a085183 add more dataviz tooling 2024-06-26 12:59:52 -07:00
Kevin Hester
047a56d554 speed up file writing 2024-06-26 12:59:28 -07:00
Kevin Hester
320bb30d29 Use .feather files as our long-term representation 2024-06-26 11:12:02 -07:00
Kevin Hester
f2c427430c Update protobufs to master (required for powermon stuff) 2024-06-26 10:21:47 -07:00
Kevin Hester
ef4b534396 Merge branch 'pr-moveproto' into pr-powermon 2024-06-26 10:19:04 -07:00
Kevin Hester
b063d33d77 don't git jupyter temp directories 2024-06-25 18:58:32 -07:00
Kevin Hester
8761b3270a Merge remote-tracking branch 'root/master' into pr-powermon
# Conflicts:
#	meshtastic/mesh_interface.py
#	poetry.lock
#	pyproject.toml
2024-06-25 18:54:38 -07:00
Kevin Hester
4ca9aa29c2 beginnings of meshtastic.analysis 2024-06-25 18:49:48 -07:00
Kevin Hester
231bc25255 PPK2 based power measurements seem to approximately work 2024-06-25 15:19:21 -07:00
Kevin Hester
ff20ad5d05 group power options in --help. add --power-wait to support some boards 2024-06-25 13:48:14 -07:00
Kevin Hester
f8ad4fef7c deferred execution thread should be named and marked as daemon 2024-06-25 12:23:38 -07:00
Kevin Hester
d1aadf0c8e close power meter gracefully 2024-06-25 12:22:47 -07:00
Kevin Hester
d448ea5767 keep a symbolic link "latest" that points to the latest slog dir 2024-06-25 12:03:35 -07:00
Kevin Hester
402622f427 fix type warnings 2024-06-25 11:25:07 -07:00
Kevin Hester
220241448f more fighting with trunk 2024-06-25 11:08:47 -07:00
Kevin Hester
9b61f11c88 temporarily suppress warning about main.py being too long 2024-06-25 11:08:35 -07:00
Kevin Hester
8d94458e55 flake8 has different settings than trunk, don't confict in vscode 2024-06-25 11:02:58 -07:00
Kevin Hester
1b045bec88 fix linter warnings 2024-06-25 11:02:24 -07:00
Kevin Hester
07fc991f4e clean up slog closing 2024-06-25 10:39:44 -07:00
Kevin Hester
c6561713db don't let daemon keep process alive 2024-06-25 10:27:36 -07:00
Kevin Hester
9cdfde47ec store slogs in correct default directory (OS dependent) 2024-06-25 10:27:35 -07:00
Kevin Hester
91066f6aed add powermon_sim support 2024-06-25 10:26:45 -07:00
Kevin Hester
7ce7d73e89 Switch from pandas to apache arrow for live data logging (better streaming) 2024-06-25 10:26:45 -07:00
Kevin Hester
43e1f65a75 "python" is deprecated vscode now wants "pydebug" 2024-06-25 10:26:45 -07:00
Kevin Hester
dc8348b99e add (optional) poe tool config for easy running of external commands 2024-06-25 10:26:45 -07:00
Kevin Hester
26a672ed58 ppk2 tweaks 2024-06-25 10:26:45 -07:00
Kevin Hester
ea18057c1f Add support for NRF PPK2 power testing board. 2024-06-25 10:26:44 -07:00
Kevin Hester
5ff4025ed6 add NordicSemi Power Profiler Kit 2 device to the USB blacklist 2024-06-25 10:25:58 -07:00
geeksville
1add293414 Add a whitelist of known meshtastic USB VIDs to use a default serial ports.
Initially only RAK4631 and heltec tracker are listed
2024-06-25 10:25:58 -07:00
Kevin Hester
8b781d3245 fix #610: bump nanopb to 0.4.8
Including in the Poetry changes because it touches the same lines
and I want to avoid hand merging ;-)
2024-06-25 10:25:58 -07:00
Kevin Hester
6c0e978470 debugging config tweaks 2024-06-25 10:25:58 -07:00
Kevin Hester
b7f7a40192 document why using python 3.9 2024-06-25 10:25:58 -07:00
Kevin Hester
7b18fd599c remove observable - switch because we are already using pubsub elsewhere 2024-06-25 10:25:58 -07:00
Kevin Hester
46edd78f92 For poetry change: need to put venv in our path so mypy protobuf plugin works
# Conflicts:
#	bin/regen-protobufs.sh
2024-06-25 10:25:52 -07:00
Kevin Hester
67e1e7c318 move mypy and type info into dev-time only dependencies thx @njh
# Conflicts:
#	poetry.lock
#	pyproject.toml
2024-06-25 10:23:34 -07:00
Kevin Hester
51c6c2cae1 The github action for building protobufs is using 0.4.6 so we should match 2024-06-25 10:21:11 -07:00
Kevin Hester
9c657c6c8a switch to latest protobufs 2024-06-25 10:21:11 -07:00
Kevin Hester
79c65c1706 make slog nicely add new rows 2024-06-25 10:21:11 -07:00
Kevin Hester
0e45637f2c generalize the powermon stuff to become structured logging 2024-06-25 10:21:11 -07:00
Kevin Hester
be74c3eea0 fix linter warnings 2024-06-25 10:21:11 -07:00
Kevin Hester
362c1f3d2a remove python 3.8 2024-06-25 10:21:11 -07:00
Kevin Hester
cc60f3ebc0 begin support for multiple power meter types 2024-06-25 10:21:11 -07:00
Kevin Hester
a1f86a351a add typing hints 2024-06-25 10:21:11 -07:00
Kevin Hester
21e5601b23 run linter as part of prerelease tests 2024-06-25 10:21:11 -07:00
Kevin Hester
338f00a64a minor cleanup on observable 2024-06-25 10:21:11 -07:00
Kevin Hester
a3462e0209 didn't mean to check in built protobufs 2024-06-25 10:21:02 -07:00
Kevin Hester
b41cb7d8df Merge branch 'pr-poetry' into powermon 2024-06-22 10:49:42 -07:00
William Stearns
a29ee840f2 Adding mypy typing 2024-06-15 23:22:43 -04:00
71 changed files with 9424 additions and 1970 deletions

1
.gitattributes vendored
View File

@@ -2,3 +2,4 @@
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
*.{sh,[sS][hH]} text eol=lf
meshtastic/protobuf/* linguist-generated=true

View File

@@ -13,10 +13,10 @@ jobs:
strategy:
matrix:
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
steps:
- uses: actions/checkout@v4
- name: Install Python 3
@@ -30,7 +30,7 @@ jobs:
pip3 install poetry
- name: Install meshtastic from local
run: |
poetry install
poetry install --all-extras --with dev,powermon
poetry run meshtastic --version
- name: Run pylint
run: poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$"
@@ -56,10 +56,10 @@ jobs:
strategy:
matrix:
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
steps:
- uses: actions/checkout@v4
- name: Install Python 3

View File

@@ -28,6 +28,11 @@ jobs:
run: >-
poetry version patch
- name: Get version
id: get_version
run: >-
poetry version --short | sed 's/^/::set-output name=version::/'
- name: Commit updated version.
id: commit_updated
run: |
@@ -35,14 +40,9 @@ jobs:
git config --global user.email 'bot@noreply.github.com'
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git add pyproject.toml
git commit -m "bump version" && git push || echo "No changes to commit"
git commit -m "bump version to ${{ steps.get_version.outputs.version }}" && git push || echo "No changes to commit"
git log -n 1 --pretty=format:"%H" | tail -n 1 | awk '{print "::set-output name=sha::"$0}'
- name: Get version
id: get_version
run: >-
poetry version --short | sed 's/^/::set-output name=version::/'
- name: Create GitHub release
uses: actions/create-release@v1
id: create_release
@@ -52,6 +52,7 @@ jobs:
prerelease: true
release_name: Meshtastic Python ${{ steps.get_version.outputs.version }}
tag_name: ${{ steps.get_version.outputs.version }}
commitish: ${{ steps.commit_updated.outputs.sha }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
env:

3
.gitignore vendored
View File

@@ -16,4 +16,5 @@ __pycache__
examples/__pycache__
meshtastic.spec
.hypothesis/
coverage.xml
coverage.xml
.ipynb_checkpoints

View File

@@ -23,7 +23,7 @@ ignore-patterns=mqtt_pb2.py,channel_pb2.py,telemetry_pb2.py,admin_pb2.py,config_
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
#
disable=invalid-name,fixme,logging-fstring-interpolation,too-many-statements,too-many-branches,too-many-locals,no-member,f-string-without-interpolation,protected-access,pointless-string-statement,too-few-public-methods,broad-except,no-else-return,no-else-raise,bare-except,too-many-public-methods
disable=invalid-name,fixme,logging-fstring-interpolation,too-many-statements,too-many-branches,too-many-locals,no-member,f-string-without-interpolation,protected-access,pointless-string-statement,too-few-public-methods,broad-except,no-else-return,no-else-raise,bare-except,too-many-public-methods,nested-min-max
[BASIC]

88
.vscode/launch.json vendored
View File

@@ -6,11 +6,11 @@
"configurations": [
{
"name": "meshtastic BLE",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": false,
"args": ["--ble", "Meshtastic_9f6e"]
"args": ["--ble", "--info", "--seriallog", "stdout"]
},
{
"name": "meshtastic BLE scan",
@@ -22,7 +22,7 @@
},
{
"name": "meshtastic admin",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -30,15 +30,23 @@
},
{
"name": "meshtastic tunnel",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--tunnel", "--debug"]
},
{
"name": "meshtastic analysis",
"type": "debugpy",
"request": "launch",
"module": "meshtastic.analysis",
"justMyCode": false,
"args": []
},
{
"name": "meshtastic set chan",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -46,7 +54,7 @@
},
{
"name": "meshtastic debug",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -54,7 +62,7 @@
},
{
"name": "meshtastic listen",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -62,7 +70,7 @@
},
{
"name": "meshtastic debug getPref",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -70,7 +78,7 @@
},
{
"name": "meshtastic debug getPref telemetry",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -78,7 +86,7 @@
},
{
"name": "meshtastic debug info",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -94,7 +102,7 @@
},
{
"name": "meshtastic debug set region",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -102,7 +110,7 @@
},
{
"name": "meshtastic debug set bluetooth fixed pin",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -110,7 +118,7 @@
},
{
"name": "meshtastic debug get bluetooth fixed pin",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -118,15 +126,15 @@
},
{
"name": "meshtastic debug setPref",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "power.is_power_saving", "1"]
"args": ["--set", "power.powermon_enables", "65527"]
},
{
"name": "meshtastic debug setPref telemetry.environment_measurement_enabled",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -134,7 +142,7 @@
},
{
"name": "meshtastic debug setPref telemetry.environment_screen_enabled",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -142,7 +150,7 @@
},
{
"name": "meshtastic debug setPref telemetry",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -150,7 +158,7 @@
},
{
"name": "meshtastic setpref",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -158,7 +166,7 @@
},
{
"name": "meshtastic --ch-set",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -167,7 +175,7 @@
{
"name": "meshtastic seturl",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -176,15 +184,39 @@
},
{
"name": "meshtastic shell",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--seriallog"]
"justMyCode": false,
"args": ["--noproto", "--seriallog", "stdout"]
},
{
"name": "meshtastic powermon sim",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": false,
"args": ["--slog-out", "default", "--power-sim", "--power-voltage", "3.3", "--port", "/dev/ttyUSB0", "--noproto", "--seriallog", "stdout"]
},
{
"name": "meshtastic powermon ppk2",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": false,
"args": ["--slog-out", "default", "--power-ppk2-meter", "--power-wait", "--power-voltage", "3.3", "--noproto", "--seriallog", "stdout"]
},
{
"name": "meshtastic stress ppk2",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": false,
"args": ["--slog", "--power-ppk2-supply", "--power-stress", "--power-voltage", "3.3", "--ble"]
},
{
"name": "meshtastic test",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -192,7 +224,7 @@
},
{
"name": "meshtastic settime",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -200,7 +232,7 @@
},
{
"name": "meshtastic sendtext",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
@@ -208,7 +240,7 @@
},
{
"name": "meshtastic showNodes",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,

16
.vscode/settings.json vendored
View File

@@ -1,8 +1,22 @@
{
"cSpell.words": [
"bitmask",
"boardid",
"DEEPSLEEP",
"Meshtastic",
"milliwatt",
"portnums",
"powermon",
"POWERSTRESS",
"pyarrow",
"TORADIO",
"Vids"
],
"python.pythonPath": "/usr/bin/python3"
"python.pythonPath": "/usr/bin/python3",
"flake8.enabled": false,
"python.testing.pytestArgs": [
"meshtastic/tests"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true // we are using trunk for formatting/linting rules, don't yell at us about line length
}

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)**
(Documentation/API Reference is currently offline)
**[API Documentation](https://python.meshtastic.org)**
## Call for Contributors

View File

@@ -2,6 +2,12 @@ set -e
# You may consider running: "pytest -m smoke1" instead of this test.
echo "Linting"
poetry run pylint meshtastic examples/ --ignore-patterns ".*_pb2.pyi?$"
echo "Checking types"
poetry run mypy meshtastic/
echo "Running (crude) prerelease tests to verify sanity"
# Use the python environment created by poetry

55
examples/waypoint.py Normal file
View File

@@ -0,0 +1,55 @@
"""Program to create and delete waypoint
To run:
python3 examples/waypoint.py --port /dev/ttyUSB0 create 45 test the_desc_2 '2024-12-18T23:05:23' 48.74 7.35
python3 examples/waypoint.py delete 45
"""
import argparse
import datetime
import sys
import meshtastic
import meshtastic.serial_interface
parser = argparse.ArgumentParser(
prog='waypoint',
description='Create and delete Meshtastic waypoint')
parser.add_argument('--port', default=None)
parser.add_argument('--debug', default=False, action='store_true')
subparsers = parser.add_subparsers(dest='cmd')
parser_delete = subparsers.add_parser('delete', help='Delete a waypoint')
parser_delete.add_argument('id', help="id of the waypoint")
parser_create = subparsers.add_parser('create', help='Create a new waypoint')
parser_create.add_argument('id', help="id of the waypoint")
parser_create.add_argument('name', help="name of the waypoint")
parser_create.add_argument('description', help="description of the waypoint")
parser_create.add_argument('expire', help="expiration date of the waypoint as interpreted by datetime.fromisoformat")
parser_create.add_argument('latitude', help="latitude of the waypoint")
parser_create.add_argument('longitude', help="longitude of the waypoint")
args = parser.parse_args()
print(args)
# By default will try to find a meshtastic device,
# otherwise provide a device path like /dev/ttyUSB0
if args.debug:
d = sys.stderr
else:
d = None
with meshtastic.serial_interface.SerialInterface(args.port, debugOut=d) as iface:
if args.cmd == 'create':
p = iface.sendWaypoint(
waypoint_id=int(args.id),
name=args.name,
description=args.description,
expire=int(datetime.datetime.fromisoformat(args.expire).timestamp()),
latitude=float(args.latitude),
longitude=float(args.longitude),
)
else:
p = iface.deleteWaypoint(int(args.id))
print(p)
# iface.close()

View File

@@ -2,40 +2,44 @@
# A library for the Meshtastic Client API
Primary interfaces: SerialInterface, TCPInterface, BLEInterface
Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
Source code on [github](https://github.com/meshtastic/python)
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.
- nodesByNum - like "nodes" but keyed by nodeNum instead of nodeId
- 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
- `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)
- `localNode` - Pointer to a node object for the local node
notable properties of nodes:
- localConfig - Current radio settings, can be written to the radio with the `writeConfig` method.
- moduleConfig - Current module settings, can be written to the radio with the `writeConfig` method.
- channels - The node's channels, keyed by index.
- `localConfig` - Current radio settings, can be written to the radio with the `writeConfig` method.
- `moduleConfig` - Current module settings, can be written to the radio with the `writeConfig` method.
- `channels` - The node's channels, keyed by index.
# Published PubSub topics
We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available
topics:
- 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.receive.text(packet) - delivers a received packet as a dictionary, if you only care about a particular
- `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.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".
- meshtastic.receive.position(packet)
- meshtastic.receive.user(packet)
- 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.receive.position(packet)`
- `meshtastic.receive.user(packet)`
- `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.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
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
sendText, decoded.data.text will **also** be populated with the decoded string. For ASCII these two strings will be the same, but 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
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
unicode scripts they can be different.
# Example Usage
@@ -76,7 +80,6 @@ from typing import *
import google.protobuf.json_format
import serial # type: ignore[import-untyped]
from dotmap import DotMap # type: ignore[import-untyped]
from google.protobuf.json_format import MessageToJson
from pubsub import pub # type: ignore[import-untyped]
from tabulate import tabulate
@@ -96,6 +99,7 @@ from .protobuf import (
remote_hardware_pb2,
storeforward_pb2,
telemetry_pb2,
powermon_pb2
)
from . import (
util,
@@ -106,13 +110,13 @@ from . import (
LOCAL_ADDR = "^local"
"""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"""
BROADCAST_ADDR = "^all"
"""A special ID that means broadcast"""
OUR_APP_VERSION = 20300
OUR_APP_VERSION: int = 20300
"""The numeric buildnumber (shared with android apps) specifying the
level of device code we are guaranteed to understand
@@ -129,7 +133,9 @@ class ResponseHandler(NamedTuple):
"""A pending response callback, waiting for a response to one of our messages"""
# requestId: int - used only as a key
#: a callable to call when a response is received
callback: Callable
#: Whether ACKs and NAKs should be passed to this handler
ackPermitted: bool = False
# FIXME, add timestamp and age out old requests
@@ -137,11 +143,11 @@ class ResponseHandler(NamedTuple):
class KnownProtocol(NamedTuple):
"""Used to automatically decode known protocol payloads"""
#: A descriptive name (e.g. "text", "user", "admin")
name: str
# portnum: int, now a key
# If set, will be called to prase as a protocol buffer
#: If set, will be called to parse as a protocol buffer
protobufFactory: Optional[Callable] = None
# If set, invoked as onReceive(interface, packet)
#: If set, invoked as onReceive(interface, packet)
onReceive: Optional[Callable] = None
@@ -189,6 +195,34 @@ def _onNodeInfoReceive(iface, asDict):
iface.nodes[p["id"]] = n
_receiveInfoUpdate(iface, asDict)
def _onTelemetryReceive(iface, asDict):
"""Automatically update device metrics on received packets"""
logging.debug(f"in _onTelemetryReceive() asDict:{asDict}")
if "from" not in asDict:
return
toUpdate = None
telemetry = asDict.get("decoded", {}).get("telemetry", {})
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):
if "from" in asDict:
@@ -197,6 +231,12 @@ def _receiveInfoUpdate(iface, asDict):
iface._getOrCreateByNum(asDict["from"])["snr"] = asDict.get("rxSnr")
iface._getOrCreateByNum(asDict["from"])["hopLimit"] = asDict.get("hopLimit")
def _onAdminReceive(iface, asDict):
"""Special auto parsing for received messages"""
logging.debug(f"in _onAdminReceive() asDict:{asDict}")
if "decoded" in asDict and "from" in asDict and "admin" in asDict["decoded"]:
adminMessage = asDict["decoded"]["admin"]["raw"]
iface._getOrCreateByNum(asDict["from"])["adminSessionPassKey"] = adminMessage.session_passkey
"""Well known message payloads can register decoders for automatic protobuf parsing"""
protocols = {
@@ -216,10 +256,12 @@ protocols = {
portnums_pb2.PortNum.NODEINFO_APP: KnownProtocol(
"user", mesh_pb2.User, _onNodeInfoReceive
),
portnums_pb2.PortNum.ADMIN_APP: KnownProtocol("admin", admin_pb2.AdminMessage),
portnums_pb2.PortNum.ADMIN_APP: KnownProtocol(
"admin", admin_pb2.AdminMessage, _onAdminReceive
),
portnums_pb2.PortNum.ROUTING_APP: KnownProtocol("routing", mesh_pb2.Routing),
portnums_pb2.PortNum.TELEMETRY_APP: KnownProtocol(
"telemetry", telemetry_pb2.Telemetry
"telemetry", telemetry_pb2.Telemetry, _onTelemetryReceive
),
portnums_pb2.PortNum.REMOTE_HARDWARE_APP: KnownProtocol(
"remotehw", remote_hardware_pb2.HardwareMessage
@@ -228,6 +270,9 @@ protocols = {
portnums_pb2.PortNum.TRACEROUTE_APP: KnownProtocol(
"traceroute", mesh_pb2.RouteDiscovery
),
portnums_pb2.PortNum.POWERSTRESS_APP: KnownProtocol(
"powerstress", powermon_pb2.PowerStressMessage
),
portnums_pb2.PortNum.WAYPOINT_APP: KnownProtocol("waypoint", mesh_pb2.Waypoint),
portnums_pb2.PortNum.PAXCOUNTER_APP: KnownProtocol("paxcounter", paxcount_pb2.Paxcount),
portnums_pb2.PortNum.STORE_FORWARD_APP: KnownProtocol("storeforward", storeforward_pb2.StoreAndForward),

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
"""Post-run analysis tools for meshtastic."""

View File

@@ -0,0 +1,206 @@
"""Post-run analysis tools for meshtastic."""
import argparse
import logging
from typing import cast, List
import dash_bootstrap_components as dbc # type: ignore[import-untyped]
import numpy as np
import pandas as pd
import plotly.express as px # type: ignore[import-untyped]
import plotly.graph_objects as go # type: ignore[import-untyped]
import pyarrow as pa
from dash import Dash, dcc, html # type: ignore[import-untyped]
from pyarrow import feather
from .. import mesh_pb2, powermon_pb2
from ..slog import root_dir
# Configure panda options
pd.options.mode.copy_on_write = True
def to_pmon_names(arr) -> List[str]:
"""Convert the power monitor state numbers to their corresponding names.
arr (list): List of power monitor state numbers.
Returns the List of corresponding power monitor state names.
"""
def to_pmon_name(n):
try:
s = powermon_pb2.PowerMon.State.Name(int(n))
return s if s != "None" else None
except ValueError:
return None
return [to_pmon_name(x) for x in arr]
def read_pandas(filepath: str) -> pd.DataFrame:
"""Read a feather file and convert it to a pandas DataFrame.
filepath (str): Path to the feather file.
Returns the pandas DataFrame.
"""
# per https://arrow.apache.org/docs/python/pandas.html#reducing-memory-use-in-table-to-pandas
# use this to get nullable int fields treated as ints rather than floats in pandas
dtype_mapping = {
pa.int8(): pd.Int8Dtype(),
pa.int16(): pd.Int16Dtype(),
pa.int32(): pd.Int32Dtype(),
pa.int64(): pd.Int64Dtype(),
pa.uint8(): pd.UInt8Dtype(),
pa.uint16(): pd.UInt16Dtype(),
pa.uint32(): pd.UInt32Dtype(),
pa.uint64(): pd.UInt64Dtype(),
pa.bool_(): pd.BooleanDtype(),
pa.float32(): pd.Float32Dtype(),
pa.float64(): pd.Float64Dtype(),
pa.string(): pd.StringDtype(),
}
return cast(pd.DataFrame, feather.read_table(filepath).to_pandas(types_mapper=dtype_mapping.get)) # type: ignore[arg-type]
def get_pmon_raises(dslog: pd.DataFrame) -> pd.DataFrame:
"""Get the power monitor raises from the slog DataFrame.
dslog (pd.DataFrame): The slog DataFrame.
Returns the DataFrame containing the power monitor raises.
"""
pmon_events = dslog[dslog["pm_mask"].notnull()]
pm_masks = pd.Series(pmon_events["pm_mask"]).to_numpy()
# possible to do this with pandas rolling windows if I was smarter?
pm_changes = [
(pm_masks[i - 1] ^ x if i != 0 else x) for i, x in enumerate(pm_masks)
]
pm_raises = [(pm_masks[i] & x) for i, x in enumerate(pm_changes)]
pm_falls = [(~pm_masks[i] & x if i != 0 else 0) for i, x in enumerate(pm_changes)]
pmon_events["pm_raises"] = to_pmon_names(pm_raises)
pmon_events["pm_falls"] = to_pmon_names(pm_falls)
pmon_raises = pmon_events[pmon_events["pm_raises"].notnull()][["time", "pm_raises"]]
pmon_falls = pmon_events[pmon_events["pm_falls"].notnull()]
# pylint: disable=unused-variable
def get_endtime(row):
"""Find the corresponding fall event."""
following = pmon_falls[
(pmon_falls["pm_falls"] == row["pm_raises"])
& (pmon_falls["time"] > row["time"])
]
return following.iloc[0] if not following.empty else None
# HMM - setting end_time doesn't work yet - leave off for now
# pmon_raises['end_time'] = pmon_raises.apply(get_endtime, axis=1)
return pmon_raises
def get_board_info(dslog: pd.DataFrame) -> tuple:
"""Get the board information from the slog DataFrame.
dslog (pd.DataFrame): The slog DataFrame.
Returns a tuple containing the board ID and software version.
"""
board_info = dslog[dslog["sw_version"].notnull()]
sw_version = board_info.iloc[0]["sw_version"]
board_id = mesh_pb2.HardwareModel.Name(board_info.iloc[0]["board_id"])
return (board_id, sw_version)
def create_argparser() -> argparse.ArgumentParser:
"""Create the argument parser for the script."""
parser = argparse.ArgumentParser(description="Meshtastic power analysis tools")
group = parser
group.add_argument(
"--slog",
help="Specify the structured-logs directory (defaults to latest log directory)",
)
group.add_argument(
"--no-server",
action="store_true",
help="Exit immediately, without running the visualization web server",
)
return parser
def create_dash(slog_path: str) -> Dash:
"""Create a Dash application for visualizing power consumption data.
slog_path (str): Path to the slog directory.
Returns the Dash application.
"""
app = Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
dpwr = read_pandas(f"{slog_path}/power.feather")
dslog = read_pandas(f"{slog_path}/slog.feather")
pmon_raises = get_pmon_raises(dslog)
def set_legend(f, name):
f["data"][0]["showlegend"] = True
f["data"][0]["name"] = name
return f
avg_pwr_lines = px.line(dpwr, x="time", y="average_mW").update_traces(
line_color="red"
)
set_legend(avg_pwr_lines, "avg power")
max_pwr_points = px.scatter(dpwr, x="time", y="max_mW").update_traces(
marker_color="blue"
)
set_legend(max_pwr_points, "max power")
min_pwr_points = px.scatter(dpwr, x="time", y="min_mW").update_traces(
marker_color="green"
)
set_legend(min_pwr_points, "min power")
fake_y = np.full(len(pmon_raises), 10.0)
pmon_points = px.scatter(pmon_raises, x="time", y=fake_y, text="pm_raises")
fig = go.Figure(data=max_pwr_points.data + avg_pwr_lines.data + pmon_points.data)
fig.update_layout(
legend={"yanchor": "top", "y": 0.99, "xanchor": "left", "x": 0.01}
)
# App layout
app.layout = [
html.Div(children="Meshtastic power analysis tool testing..."),
dcc.Graph(figure=fig),
]
return app
def main():
"""Entry point of the script."""
parser = create_argparser()
args = parser.parse_args()
if not args.slog:
args.slog = f"{root_dir()}/latest"
app = create_dash(slog_path=args.slog)
port = 8051
logging.info(f"Running Dash visualization of {args.slog} (publicly accessible)")
if not args.no_server:
app.run_server(debug=True, host="0.0.0.0", port=port)
else:
logging.info("Exiting without running visualization server")
if __name__ == "__main__":
main()

View File

@@ -5,20 +5,18 @@ import atexit
import logging
import struct
import time
import io
from threading import Thread
from typing import List, Optional
import print_color # type: ignore[import-untyped]
import google.protobuf
from bleak import BleakClient, BleakScanner, BLEDevice
from bleak.exc import BleakDBusError, BleakError
import google.protobuf
from meshtastic.mesh_interface import MeshInterface
from .protobuf import (
mesh_pb2,
)
from .protobuf import mesh_pb2
SERVICE_UUID = "6ba1b218-15a8-461f-9fa8-5dcae273eafd"
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
FROMRADIO_UUID = "2c55e69e-4993-11ed-b878-0242ac120002"
@@ -37,9 +35,9 @@ class BLEInterface(MeshInterface):
self,
address: Optional[str],
noProto: bool = False,
debugOut=None,
debugOut: Optional[io.TextIOWrapper]=None,
noNodes: bool = False,
):
) -> None:
MeshInterface.__init__(
self, debugOut=debugOut, noProto=noProto, noNodes=noNodes
)
@@ -54,16 +52,19 @@ class BLEInterface(MeshInterface):
self._receiveThread.start()
logging.debug("Threads running")
self.client: Optional[BLEClient] = None
try:
logging.debug(f"BLE connecting to: {address if address else 'any'}")
self.client: Optional[BLEClient] = self.connect(address)
self.client = self.connect(address)
logging.debug("BLE connected")
except BLEInterface.BLEError as e:
self.close()
raise e
if self.client.has_characteristic(LEGACY_LOGRADIO_UUID):
self.client.start_notify(LEGACY_LOGRADIO_UUID, self.legacy_log_radio_handler)
self.client.start_notify(
LEGACY_LOGRADIO_UUID, self.legacy_log_radio_handler
)
if self.client.has_characteristic(LOGRADIO_UUID):
self.client.start_notify(LOGRADIO_UUID, self.log_radio_handler)
@@ -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
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.
Note: this method does not need to be async because it is just setting a bool.
"""
@@ -94,34 +95,19 @@ class BLEInterface(MeshInterface):
log_record = mesh_pb2.LogRecord()
try:
log_record.ParseFromString(bytes(b))
message = (
f"[{log_record.source}] {log_record.message}"
if log_record.source
else log_record.message
)
self._handleLogLine(message)
except google.protobuf.message.DecodeError:
return
message = f'[{log_record.source}] {log_record.message}' if log_record.source else log_record.message
if log_record.DEBUG:
print_color.print(message, color="cyan", end=None)
elif log_record.INFO:
print_color.print(message, color="white", end=None)
elif log_record.WARNING:
print_color.print(message, color="yellow", end=None)
elif log_record.ERROR:
print_color.print(message, color="red", end=None)
else:
print_color.print(message, end=None)
logging.warning("Malformed LogRecord received. Skipping.")
async def legacy_log_radio_handler(self, _, b): # pylint: disable=C0116
log_radio = b.decode("utf-8").replace("\n", "")
if log_radio.startswith("DEBUG"):
print_color.print(log_radio, color="cyan", end=None)
elif log_radio.startswith("INFO"):
print_color.print(log_radio, color="white", end=None)
elif log_radio.startswith("WARN"):
print_color.print(log_radio, color="yellow", end=None)
elif log_radio.startswith("ERROR"):
print_color.print(log_radio, color="red", end=None)
else:
print_color.print(log_radio, end=None)
self._handleLogLine(log_radio)
@staticmethod
def scan() -> List[BLEDevice]:
@@ -165,9 +151,12 @@ class BLEInterface(MeshInterface):
)
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."
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":
"Connect to a device by address."
@@ -179,12 +168,16 @@ class BLEInterface(MeshInterface):
client.discover()
return client
def _receiveFromRadioImpl(self):
def _receiveFromRadioImpl(self) -> None:
while self._want_receive:
if self.should_read:
self.should_read = False
retries = 0
retries: int = 0
while self._want_receive:
if self.client is None:
logging.debug(f"BLE client is None, shutting down")
self._want_receive = False
continue
try:
b = bytes(self.client.read_gatt_char(FROMRADIO_UUID))
except BleakDBusError as e:
@@ -209,9 +202,9 @@ class BLEInterface(MeshInterface):
else:
time.sleep(0.01)
def _sendToRadioImpl(self, toRadio):
b = toRadio.SerializeToString()
if b:
def _sendToRadioImpl(self, toRadio) -> None:
b: bytes = toRadio.SerializeToString()
if b and self.client: # we silently ignore writes while we are shutting down
logging.debug(f"TORADIO write: {b.hex()}")
try:
self.client.write_gatt_char(
@@ -226,28 +219,32 @@ class BLEInterface(MeshInterface):
time.sleep(0.01)
self.should_read = True
def close(self):
atexit.unregister(self._exit_handler)
def close(self) -> None:
try:
MeshInterface.close(self)
except Exception as e:
logging.error(f"Error closing mesh interface: {e}")
if self._want_receive:
self.want_receive = False # Tell the thread we want it to stop
self._receiveThread.join(timeout=2) # If bleak is hung, don't wait for the thread to exit (it is critical we disconnect)
self._receiveThread = None
self._want_receive = False # Tell the thread we want it to stop
if self._receiveThread:
self._receiveThread.join(
timeout=2
) # If bleak is hung, don't wait for the thread to exit (it is critical we disconnect)
self._receiveThread = None
if self.client:
atexit.unregister(self._exit_handler)
self.client.disconnect()
self.client.close()
self.client = None
self._disconnected() # send the disconnected indicator up to clients
class BLEClient:
"""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._eventThread = Thread(
target=self._run_event_loop, name="BLEClient", daemon=True

View File

@@ -1,29 +1,31 @@
"""Mesh Interface class
"""
# pylint: disable=R0917
import collections
import json
import logging
import math
import random
import secrets
import sys
import threading
import time
import traceback
from datetime import datetime
from decimal import Decimal
from typing import Any, Callable, Dict, List, Optional, Union
import google.protobuf.json_format
from pubsub import pub # type: ignore[import-untyped]
try:
import print_color # type: ignore[import-untyped]
except ImportError as e:
print_color = None
from pubsub import pub # type: ignore[import-untyped]
from tabulate import tabulate
import meshtastic.node
from meshtastic.protobuf import (
mesh_pb2,
portnums_pb2,
telemetry_pb2,
)
from meshtastic import (
BROADCAST_ADDR,
BROADCAST_NUM,
@@ -33,14 +35,15 @@ from meshtastic import (
protocols,
publishingThread,
)
from meshtastic.protobuf import mesh_pb2, portnums_pb2, telemetry_pb2
from meshtastic.util import (
Acknowledgment,
Timeout,
convert_mac_addr,
message_to_json,
our_exit,
remove_keys_from_dict,
stripnl,
message_to_json,
)
@@ -67,7 +70,7 @@ def _timeago(delta_secs: int) -> str:
return "now"
class MeshInterface: # pylint: disable=R0902
class MeshInterface: # pylint: disable=R0902
"""Interface class for meshtastic devices
Properties:
@@ -79,11 +82,14 @@ class MeshInterface: # pylint: disable=R0902
class MeshInterfaceError(Exception):
"""An exception class for general mesh interface errors"""
def __init__(self, message):
self.message = message
super().__init__(self.message)
def __init__(self, debugOut=None, noProto: bool=False, noNodes: bool=False) -> None:
def __init__(
self, debugOut=None, noProto: bool = False, noNodes: bool = False
) -> None:
"""Constructor
Keyword Arguments:
@@ -93,13 +99,21 @@ class MeshInterface: # pylint: disable=R0902
on startup, just other configuration information.
"""
self.debugOut = debugOut
self.nodes: Optional[Dict[str,Dict]] = None # FIXME
self.nodes: Optional[Dict[str, Dict]] = None # FIXME
self.isConnected: threading.Event = threading.Event()
self.noProto: bool = noProto
self.localNode: meshtastic.node.Node = meshtastic.node.Node(self, -1) # We fixup nodenum later
self.myInfo: Optional[mesh_pb2.MyNodeInfo] = None # We don't have device info yet
self.metadata: Optional[mesh_pb2.DeviceMetadata] = None # We don't have device metadata yet
self.responseHandlers: Dict[int,ResponseHandler] = {} # A map from request ID to the handler
self.localNode: meshtastic.node.Node = meshtastic.node.Node(
self, -1
) # We fixup nodenum later
self.myInfo: Optional[
mesh_pb2.MyNodeInfo
] = None # We don't have device info yet
self.metadata: Optional[
mesh_pb2.DeviceMetadata
] = None # We don't have device metadata yet
self.responseHandlers: Dict[
int, ResponseHandler
] = {} # A map from request ID to the handler
self.failure = (
None # If we've encountered a fatal exception it will be kept here
)
@@ -117,6 +131,12 @@ class MeshInterface: # pylint: disable=R0902
self.queue: collections.OrderedDict = collections.OrderedDict()
self._localChannels = None
# We could have just not passed in debugOut to MeshInterface, and instead told consumers to subscribe to
# the meshtastic.log.line publish instead. Alas though changing that now would be a breaking API change
# for any external consumers of the library.
if debugOut:
pub.subscribe(MeshInterface._printLogLine, "meshtastic.log.line")
def close(self):
"""Shutdown this interface"""
if self.heartbeatTimer:
@@ -127,15 +147,48 @@ class MeshInterface: # pylint: disable=R0902
def __enter__(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:
logging.error(
f"An exception of type {exc_type} with value {exc_value} has occurred"
)
if traceback is not None:
logging.error(f"Traceback: {traceback}")
if trace is not None:
logging.error(f"Traceback: {trace}")
self.close()
@staticmethod
def _printLogLine(line, interface):
"""Print a line of log output."""
if print_color is not None and interface.debugOut == sys.stdout:
# this isn't quite correct (could cause false positives), but currently our formatting differs between different log representations
if "DEBUG" in line:
print_color.print(line, color="cyan", end=None)
elif "INFO" in line:
print_color.print(line, color="white", end=None)
elif "WARN" in line:
print_color.print(line, color="yellow", end=None)
elif "ERR" in line:
print_color.print(line, color="red", end=None)
else:
print_color.print(line, end=None)
else:
interface.debugOut.write(line + "\n")
def _handleLogLine(self, line: str) -> None:
"""Handle a line of log output from the device."""
# Devices should _not_ be including a newline at the end of each log-line str (especially when
# encapsulated as a LogRecord). But to cope with old device loads, we check for that and fix it here:
if line.endswith("\n"):
line = line[:-1]
pub.sendMessage("meshtastic.log.line", line=line, interface=self)
def _handleLogRecord(self, record: mesh_pb2.LogRecord) -> None:
"""Handle a log record which was received encapsulated in a protobuf."""
# For now we just try to format the line as if it had come in over the serial port
self._handleLogLine(record.message)
def showInfo(self, file=sys.stdout) -> str: # pylint: disable=W0613
"""Show human readable summary about this object"""
owner = f"Owner: {self.getLongName()} ({self.getShortName()})"
@@ -168,7 +221,9 @@ class MeshInterface: # pylint: disable=R0902
print(infos)
return infos
def showNodes(self, includeSelf: bool=True, file=sys.stdout) -> str: # pylint: disable=W0613
def showNodes(
self, includeSelf: bool = True
) -> str: # pylint: disable=W0613
"""Show table summary of nodes in mesh"""
def formatFloat(value, precision=2, unit="") -> Optional[str]:
@@ -199,7 +254,11 @@ class MeshInterface: # pylint: disable=R0902
continue
presumptive_id = f"!{node['num']:08x}"
row = {"N": 0, "User": f"Meshtastic {presumptive_id[-4:]}", "ID": presumptive_id}
row = {
"N": 0,
"User": f"Meshtastic {presumptive_id[-4:]}",
"ID": presumptive_id,
}
user = node.get("user")
if user:
@@ -208,7 +267,8 @@ class MeshInterface: # pylint: disable=R0902
"User": user.get("longName", "N/A"),
"AKA": user.get("shortName", "N/A"),
"ID": user["id"],
"Hardware": user.get("hwModel", "UNSET")
"Hardware": user.get("hwModel", "UNSET"),
"Pubkey": user.get("publicKey", "UNSET"),
}
)
@@ -245,7 +305,7 @@ class MeshInterface: # pylint: disable=R0902
row.update(
{
"SNR": formatFloat(node.get("snr"), 2, " dB"),
"Hops Away": node.get("hopsAway", "0/unknown"),
"Hops": node.get("hopsAway", "?"),
"Channel": node.get("channel", 0),
"LastHeard": getLH(node.get("lastHeard")),
"Since": getTimeAgo(node.get("lastHeard")),
@@ -262,28 +322,44 @@ class MeshInterface: # pylint: disable=R0902
print(table)
return table
def getNode(self, nodeId: str, requestChannels: bool=True) -> meshtastic.node.Node:
def getNode(
self, nodeId: str, requestChannels: bool = True, requestChannelAttempts: int = 3, timeout: int = 300
) -> meshtastic.node.Node:
"""Return a node object which contains device settings and channel info"""
if nodeId in (LOCAL_ADDR, BROADCAST_ADDR):
return self.localNode
else:
n = meshtastic.node.Node(self, nodeId)
n = meshtastic.node.Node(self, nodeId, timeout=timeout)
# Only request device settings and channel info when necessary
if requestChannels:
logging.debug("About to requestChannels")
n.requestChannels()
if not n.waitForConfig():
our_exit("Error: Timed out waiting for channels")
retries_left = requestChannelAttempts
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
def sendText(
self,
text: str,
destinationId: Union[int, str]=BROADCAST_ADDR,
wantAck: bool=False,
wantResponse: bool=False,
onResponse: Optional[Callable[[dict], Any]]=None,
channelIndex: int=0,
destinationId: Union[int, str] = BROADCAST_ADDR,
wantAck: bool = False,
wantResponse: bool = False,
onResponse: Optional[Callable[[dict], Any]] = None,
channelIndex: int = 0,
):
"""Send a utf8 string to some other node, if the node has a display it
will also be shown on the device.
@@ -315,6 +391,40 @@ class MeshInterface: # pylint: disable=R0902
channelIndex=channelIndex,
)
def sendAlert(
self,
text: str,
destinationId: Union[int, str] = BROADCAST_ADDR,
onResponse: Optional[Callable[[dict], Any]] = None,
channelIndex: int = 0,
):
"""Send an alert text to some other node. This is similar to a text message,
but carries a higher priority and is capable of generating special notifications
on certain clients.
Arguments:
text {string} -- The text of the alert to send
Keyword Arguments:
destinationId {nodeId or nodeNum} -- where to send this
message (default: {BROADCAST_ADDR})
Returns the sent packet. The id field will be populated in this packet
and can be used to track future message acks/naks.
"""
return self.sendData(
text.encode("utf-8"),
destinationId,
portNum=portnums_pb2.PortNum.ALERT_APP,
wantAck=False,
wantResponse=False,
onResponse=onResponse,
channelIndex=channelIndex,
priority=mesh_pb2.MeshPacket.Priority.ALERT
)
def sendData(
self,
data,
@@ -326,7 +436,10 @@ class MeshInterface: # pylint: disable=R0902
onResponseAckPermitted: bool=False,
channelIndex: int=0,
hopLimit: Optional[int]=None,
):
pkiEncrypted: Optional[bool]=False,
publicKey: Optional[bytes]=None,
priority: mesh_pb2.MeshPacket.Priority.ValueType=mesh_pb2.MeshPacket.Priority.RELIABLE,
): # pylint: disable=R0913
"""Send a data packet to some other node
Keyword Arguments:
@@ -377,23 +490,24 @@ class MeshInterface: # pylint: disable=R0902
meshPacket.decoded.portnum = portNum
meshPacket.decoded.want_response = wantResponse
meshPacket.id = self._generatePacketId()
if priority is not None:
meshPacket.priority = priority
if onResponse is not None:
logging.debug(f"Setting a response handler for requestId {meshPacket.id}")
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
def sendPosition(
self,
latitude: float=0.0,
longitude: float=0.0,
altitude: int=0,
timeSec: int=0,
destinationId: Union[int, str]=BROADCAST_ADDR,
wantAck: bool=False,
wantResponse: bool=False,
channelIndex: int=0,
latitude: float = 0.0,
longitude: float = 0.0,
altitude: int = 0,
destinationId: Union[int, str] = BROADCAST_ADDR,
wantAck: bool = False,
wantResponse: bool = False,
channelIndex: int = 0,
):
"""
Send a position packet to some other node (normally a broadcast)
@@ -401,8 +515,6 @@ class MeshInterface: # pylint: disable=R0902
Also, the device software will notice this packet and use it to automatically
set its notion of the local position.
If timeSec is not specified (recommended), we will use the local machine time.
Returns the sent packet. The id field will be populated in this packet and
can be used to track future message acks/naks.
"""
@@ -419,11 +531,6 @@ class MeshInterface: # pylint: disable=R0902
p.altitude = int(altitude)
logging.debug(f"p.altitude:{p.altitude}")
if timeSec == 0:
timeSec = int(time.time()) # returns unix timestamp in seconds
p.time = timeSec
logging.debug(f"p.time:{p.time}")
if wantResponse:
onResponse = self.onResponsePosition
else:
@@ -444,20 +551,22 @@ class MeshInterface: # pylint: disable=R0902
def onResponsePosition(self, p):
"""on response for position"""
if p["decoded"]["portnum"] == 'POSITION_APP':
if p["decoded"]["portnum"] == "POSITION_APP":
self._acknowledgment.receivedPosition = True
position = mesh_pb2.Position()
position.ParseFromString(p["decoded"]["payload"])
ret = "Position received: "
if position.latitude_i != 0 and position.longitude_i != 0:
ret += f"({position.latitude_i * 10**-7}, {position.longitude_i * 10**-7})"
ret += (
f"({position.latitude_i * 10**-7}, {position.longitude_i * 10**-7})"
)
else:
ret += "(unknown)"
if position.altitude != 0:
ret += f" {position.altitude}m"
if position.precision_bits not in [0,32]:
if position.precision_bits not in [0, 32]:
ret += f" precision:{position.precision_bits}"
elif position.precision_bits == 32:
ret += " full precision"
@@ -466,11 +575,15 @@ class MeshInterface: # pylint: disable=R0902
print(ret)
elif p["decoded"]["portnum"] == 'ROUTING_APP':
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
elif p["decoded"]["portnum"] == "ROUTING_APP":
if p["decoded"]["routing"]["errorReason"] == "NO_RESPONSE":
our_exit(
"No response from node. At least firmware 2.1.22 is required on the destination node."
)
def sendTraceRoute(self, dest: Union[int, str], hopLimit: int, channelIndex: int=0):
def sendTraceRoute(
self, dest: Union[int, str], hopLimit: int, channelIndex: int = 0
):
"""Send the trace route"""
r = mesh_pb2.RouteDiscovery()
self.sendData(
@@ -488,41 +601,88 @@ class MeshInterface: # pylint: disable=R0902
def onResponseTraceRoute(self, p: dict):
"""on response for trace route"""
UNK_SNR = -128 # Value representing unknown SNR
routeDiscovery = mesh_pb2.RouteDiscovery()
routeDiscovery.ParseFromString(p["decoded"]["payload"])
asDict = google.protobuf.json_format.MessageToDict(routeDiscovery)
print("Route traced:")
routeStr = self._nodeNumToId(p["to"]) or f"{p['to']:08x}"
if "route" in asDict:
for nodeNum in asDict["route"]:
routeStr += " --> " + (self._nodeNumToId(nodeNum) or f"{nodeNum:08x}")
routeStr += " --> " + (self._nodeNumToId(p["from"]) or f"{p['from']:08x}")
print(routeStr)
print("Route traced towards destination:")
routeStr = self._nodeNumToId(p["to"], False) or f"{p['to']:08x}" # Start with destination of response
# SNR list should have one more entry than the route, as the final destination adds its SNR also
lenTowards = 0 if "route" not in asDict else len(asDict["route"])
snrTowardsValid = "snrTowards" in asDict and len(asDict["snrTowards"]) == lenTowards + 1
if lenTowards > 0: # Loop through hops in route and add SNR if available
for idx, nodeNum in enumerate(asDict["route"]):
routeStr += " --> " + (self._nodeNumToId(nodeNum, False) or f"{nodeNum:08x}") \
+ " (" + (str(asDict["snrTowards"][idx] / 4) if snrTowardsValid and asDict["snrTowards"][idx] != UNK_SNR else "?") + "dB)"
# End with origin of response
routeStr += " --> " + (self._nodeNumToId(p["from"], False) or f"{p['from']:08x}") \
+ " (" + (str(asDict["snrTowards"][-1] / 4) if snrTowardsValid and asDict["snrTowards"][-1] != UNK_SNR else "?") + "dB)"
print(routeStr) # Print the route towards destination
# Only if hopStart is set and there is an SNR entry (for the origin) it's valid, even though route might be empty (direct connection)
lenBack = 0 if "routeBack" not in asDict else len(asDict["routeBack"])
backValid = "hopStart" in p and "snrBack" in asDict and len(asDict["snrBack"]) == lenBack + 1
if backValid:
print("Route traced back to us:")
routeStr = self._nodeNumToId(p["from"], False) or f"{p['from']:08x}" # Start with origin of response
if lenBack > 0: # Loop through hops in routeBack and add SNR if available
for idx, nodeNum in enumerate(asDict["routeBack"]):
routeStr += " --> " + (self._nodeNumToId(nodeNum, False) or f"{nodeNum:08x}") \
+ " (" + (str(asDict["snrBack"][idx] / 4) if asDict["snrBack"][idx] != UNK_SNR else "?") + "dB)"
# End with destination of response (us)
routeStr += " --> " + (self._nodeNumToId(p["to"], False) or f"{p['to']:08x}") \
+ " (" + (str(asDict["snrBack"][-1] / 4) if asDict["snrBack"][-1] != UNK_SNR else "?") + "dB)"
print(routeStr) # Print the route back to us
self._acknowledgment.receivedTraceRoute = True
def sendTelemetry(self, destinationId: Union[int,str]=BROADCAST_ADDR, wantResponse: bool=False, channelIndex: int=0):
def sendTelemetry(
self,
destinationId: Union[int, str] = BROADCAST_ADDR,
wantResponse: bool = False,
channelIndex: int = 0,
telemetryType: str = "device_metrics"
):
"""Send telemetry and optionally ask for a response"""
r = telemetry_pb2.Telemetry()
if self.nodes is not None:
node = next(n for n in self.nodes.values() if n["num"] == self.localNode.nodeNum)
if node is not None:
metrics = node.get("deviceMetrics")
if metrics:
batteryLevel = metrics.get("batteryLevel")
if batteryLevel is not None:
r.device_metrics.battery_level = batteryLevel
voltage = metrics.get("voltage")
if voltage is not None:
r.device_metrics.voltage = voltage
channel_utilization = metrics.get("channelUtilization")
if channel_utilization is not None:
r.device_metrics.channel_utilization = channel_utilization
air_util_tx = metrics.get("airUtilTx")
if air_util_tx is not None:
r.device_metrics.air_util_tx = air_util_tx
if telemetryType == "environment_metrics":
r.environment_metrics.CopyFrom(telemetry_pb2.EnvironmentMetrics())
elif telemetryType == "air_quality_metrics":
r.air_quality_metrics.CopyFrom(telemetry_pb2.AirQualityMetrics())
elif telemetryType == "power_metrics":
r.power_metrics.CopyFrom(telemetry_pb2.PowerMetrics())
elif telemetryType == "local_stats":
r.local_stats.CopyFrom(telemetry_pb2.LocalStats())
else: # fall through to device metrics
if self.nodesByNum is not None:
node = self.nodesByNum.get(self.localNode.nodeNum)
if node is not None:
metrics = node.get("deviceMetrics")
if metrics:
batteryLevel = metrics.get("batteryLevel")
if batteryLevel is not None:
r.device_metrics.battery_level = batteryLevel
voltage = metrics.get("voltage")
if voltage is not None:
r.device_metrics.voltage = voltage
channel_utilization = metrics.get("channelUtilization")
if channel_utilization is not None:
r.device_metrics.channel_utilization = channel_utilization
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:
onResponse = self.onResponseTelemetry
@@ -542,36 +702,168 @@ class MeshInterface: # pylint: disable=R0902
def onResponseTelemetry(self, p: dict):
"""on response for telemetry"""
if p["decoded"]["portnum"] == 'TELEMETRY_APP':
if p["decoded"]["portnum"] == "TELEMETRY_APP":
self._acknowledgment.receivedTelemetry = True
telemetry = telemetry_pb2.Telemetry()
telemetry.ParseFromString(p["decoded"]["payload"])
print("Telemetry received:")
if telemetry.device_metrics.battery_level is not None:
print(f"Battery level: {telemetry.device_metrics.battery_level:.2f}%")
if telemetry.device_metrics.voltage is not None:
print(f"Voltage: {telemetry.device_metrics.voltage:.2f} V")
if telemetry.device_metrics.channel_utilization is not None:
print(
f"Total channel utilization: {telemetry.device_metrics.channel_utilization:.2f}%"
# Check if the telemetry message has the device_metrics field
# This is the original code that was the default for --request-telemetry and is kept for compatibility
if telemetry.HasField("device_metrics"):
if telemetry.device_metrics.battery_level is not None:
print(f"Battery level: {telemetry.device_metrics.battery_level:.2f}%")
if telemetry.device_metrics.voltage is not None:
print(f"Voltage: {telemetry.device_metrics.voltage:.2f} V")
if telemetry.device_metrics.channel_utilization is not None:
print(
f"Total channel utilization: {telemetry.device_metrics.channel_utilization:.2f}%"
)
if telemetry.device_metrics.air_util_tx is not None:
print(
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":
if p["decoded"]["routing"]["errorReason"] == "NO_RESPONSE":
our_exit(
"No response from node. At least firmware 2.1.22 is required on the destination node."
)
if telemetry.device_metrics.air_util_tx is not None:
print(f"Transmit air utilization: {telemetry.device_metrics.air_util_tx:.2f}%")
elif p["decoded"]["portnum"] == 'ROUTING_APP':
if p["decoded"]["routing"]["errorReason"] == 'NO_RESPONSE':
our_exit("No response from node. At least firmware 2.1.22 is required on the destination node.")
def onResponseWaypoint(self, p: dict):
"""on response for waypoint"""
if p["decoded"]["portnum"] == "WAYPOINT_APP":
self._acknowledgment.receivedWaypoint = True
w = mesh_pb2.Waypoint()
w.ParseFromString(p["decoded"]["payload"])
print(f"Waypoint received: {w}")
elif p["decoded"]["portnum"] == "ROUTING_APP":
if p["decoded"]["routing"]["errorReason"] == "NO_RESPONSE":
our_exit(
"No response from node. At least firmware 2.1.22 is required on the destination node."
)
def _addResponseHandler(self, requestId: int, callback: Callable[[dict], Any], ackPermitted: bool=False):
self.responseHandlers[requestId] = ResponseHandler(callback=callback, ackPermitted=ackPermitted)
def sendWaypoint(
self,
name,
description,
expire: int,
waypoint_id: Optional[int] = None,
latitude: float = 0.0,
longitude: float = 0.0,
destinationId: Union[int, str] = BROADCAST_ADDR,
wantAck: bool = True,
wantResponse: bool = False,
channelIndex: int = 0,
): # pylint: disable=R0913
"""
Send a waypoint packet to some other node (normally a broadcast)
Returns the sent packet. The id field will be populated in this packet and
can be used to track future message acks/naks.
"""
w = mesh_pb2.Waypoint()
w.name = name
w.description = description
w.expire = expire
if waypoint_id is None:
# Generate a waypoint's id, NOT a packet ID.
# same algorithm as https://github.com/meshtastic/js/blob/715e35d2374276a43ffa93c628e3710875d43907/src/meshDevice.ts#L791
seed = secrets.randbits(32)
w.id = math.floor(seed * math.pow(2, -32) * 1e9)
logging.debug(f"w.id:{w.id}")
else:
w.id = waypoint_id
if latitude != 0.0:
w.latitude_i = int(latitude * 1e7)
logging.debug(f"w.latitude_i:{w.latitude_i}")
if longitude != 0.0:
w.longitude_i = int(longitude * 1e7)
logging.debug(f"w.longitude_i:{w.longitude_i}")
if wantResponse:
onResponse = self.onResponseWaypoint
else:
onResponse = None
d = self.sendData(
w,
destinationId,
portNum=portnums_pb2.PortNum.WAYPOINT_APP,
wantAck=wantAck,
wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=channelIndex,
)
if wantResponse:
self.waitForWaypoint()
return d
def deleteWaypoint(
self,
waypoint_id: int,
destinationId: Union[int, str] = BROADCAST_ADDR,
wantAck: bool = True,
wantResponse: bool = False,
channelIndex: int = 0,
):
"""
Send a waypoint deletion packet to some other node (normally a broadcast)
NB: The id must be the waypoint's id and not the id of the packet creation.
Returns the sent packet. The id field will be populated in this packet and
can be used to track future message acks/naks.
"""
p = mesh_pb2.Waypoint()
p.id = waypoint_id
p.expire = 0
if wantResponse:
onResponse = self.onResponseWaypoint
else:
onResponse = None
d = self.sendData(
p,
destinationId,
portNum=portnums_pb2.PortNum.WAYPOINT_APP,
wantAck=wantAck,
wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=channelIndex,
)
if wantResponse:
self.waitForWaypoint()
return d
def _addResponseHandler(
self,
requestId: int,
callback: Callable[[dict], Any],
ackPermitted: bool = False,
):
self.responseHandlers[requestId] = ResponseHandler(
callback=callback, ackPermitted=ackPermitted
)
def _sendPacket(
self,
meshPacket: mesh_pb2.MeshPacket,
destinationId: Union[int,str]=BROADCAST_ADDR,
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).
You probably don't want this - use sendData instead.
@@ -620,6 +912,12 @@ class MeshInterface: # pylint: disable=R0902
loraConfig = getattr(self.localNode.localConfig, "lora")
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),
# we should pick a new unique ID so the message can be tracked.
if meshPacket.id == 0:
@@ -642,13 +940,17 @@ class MeshInterface: # pylint: disable=R0902
and self.localNode.waitForConfig()
)
if not success:
raise MeshInterface.MeshInterfaceError("Timed out waiting for interface config")
raise MeshInterface.MeshInterfaceError(
"Timed out waiting for interface config"
)
def waitForAckNak(self):
"""Wait for the ack/nak"""
success = self._timeout.waitForAckNak(self._acknowledgment)
if not success:
raise MeshInterface.MeshInterfaceError("Timed out waiting for an acknowledgment")
raise MeshInterface.MeshInterfaceError(
"Timed out waiting for an acknowledgment"
)
def waitForTraceRoute(self, waitFactor):
"""Wait for trace route"""
@@ -668,6 +970,12 @@ class MeshInterface: # pylint: disable=R0902
if not success:
raise MeshInterface.MeshInterfaceError("Timed out waiting for position")
def waitForWaypoint(self):
"""Wait for waypoint"""
success = self._timeout.waitForWaypoint(self._acknowledgment)
if not success:
raise MeshInterface.MeshInterfaceError("Timed out waiting for waypoint")
def getMyNodeInfo(self) -> Optional[Dict]:
"""Get info about my node."""
if self.myInfo is None or self.nodesByNum is None:
@@ -696,12 +1004,21 @@ class MeshInterface: # pylint: disable=R0902
return user.get("shortName", None)
return None
def getPublicKey(self):
"""Get Public Key"""
user = self.getMyUser()
if user is not None:
return user.get("publicKey", None)
return None
def _waitConnected(self, timeout=30.0):
"""Block until the initial node db download is complete, or timeout
and raise an exception"""
if not self.noProto:
if not self.isConnected.wait(timeout): # timeout after x seconds
raise MeshInterface.MeshInterfaceError("Timed out waiting for connection completion")
raise MeshInterface.MeshInterfaceError(
"Timed out waiting for connection completion"
)
# If we failed while connecting, raise the connection to the client
if self.failure:
@@ -710,9 +1027,14 @@ class MeshInterface: # pylint: disable=R0902
def _generatePacketId(self) -> int:
"""Get a new unique packet ID"""
if self.currentPacketId is None:
raise MeshInterface.MeshInterfaceError("Not connected yet, can not generate packet")
raise MeshInterface.MeshInterfaceError(
"Not connected yet, can not generate packet"
)
else:
self.currentPacketId = (self.currentPacketId + 1) & 0xFFFFFFFF
nextPacketId = (self.currentPacketId + 1) & 0xFFFFFFFF
nextPacketId = nextPacketId & 0x3FF # == (0xFFFFFFFF >> 22), masks upper 22 bits
randomPart = (random.randint(0, 0x3FFFFF) << 10) & 0xFFFFFFFF # generate number with 10 zeros at end
self.currentPacketId = nextPacketId | randomPart # combine
return self.currentPacketId
def _disconnected(self):
@@ -722,19 +1044,22 @@ class MeshInterface: # pylint: disable=R0902
lambda: pub.sendMessage("meshtastic.connection.lost", interface=self)
)
def sendHeartbeat(self):
"""Sends a heartbeat to the radio. Can be used to verify the connection is healthy."""
p = mesh_pb2.ToRadio()
p.heartbeat.CopyFrom(mesh_pb2.Heartbeat())
self._sendToRadio(p)
def _startHeartbeat(self):
"""We need to send a heartbeat message to the device every X seconds"""
def callback():
self.heartbeatTimer = None
i = 300
logging.debug(f"Sending heartbeat, interval {i} seconds")
if i != 0:
self.heartbeatTimer = threading.Timer(i, callback)
self.heartbeatTimer.start()
p = mesh_pb2.ToRadio()
p.heartbeat.CopyFrom(mesh_pb2.Heartbeat())
self._sendToRadio(p)
interval = 300
logging.debug(f"Sending heartbeat, interval {interval} seconds")
self.heartbeatTimer = threading.Timer(interval, callback)
self.heartbeatTimer.start()
self.sendHeartbeat()
callback() # run our periodic callback now, it will make another timer if necessary
@@ -757,11 +1082,15 @@ class MeshInterface: # pylint: disable=R0902
self.myInfo = None
self.nodes = {} # nodes keyed by ID
self.nodesByNum = {} # nodes keyed by nodenum
self._localChannels = [] # empty until we start getting channels pushed from the device (during config)
self._localChannels = (
[]
) # empty until we start getting channels pushed from the device (during config)
startConfig = mesh_pb2.ToRadio()
if self.configId is None or not self.noNodes:
self.configId = random.randint(0, 0xFFFFFFFF)
if self.configId == NODELESS_WANT_CONFIG_ID:
self.configId = self.configId + 1
startConfig.want_config_id = self.configId
self._sendToRadio(startConfig)
@@ -872,10 +1201,17 @@ class MeshInterface: # pylint: disable=R0902
Called by subclasses."""
fromRadio = mesh_pb2.FromRadio()
fromRadio.ParseFromString(fromRadioBytes)
logging.debug(
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)
logging.debug(f"Received from radio: {fromRadio}")
if fromRadio.HasField("my_info"):
@@ -883,13 +1219,6 @@ class MeshInterface: # pylint: disable=R0902
self.localNode.nodeNum = self.myInfo.my_node_num
logging.debug(f"Received myinfo: {stripnl(fromRadio.my_info)}")
failmsg = None
if failmsg:
self.failure = MeshInterface.MeshInterfaceError(failmsg)
self.isConnected.set() # let waitConnected return this exception
self.close()
elif fromRadio.HasField("metadata"):
self.metadata = fromRadio.metadata
logging.debug(f"Received device metadata: {stripnl(fromRadio.metadata)}")
@@ -906,7 +1235,7 @@ class MeshInterface: # pylint: disable=R0902
logging.debug("Node without position")
# no longer necessary since we're mutating directly in nodesByNum via _getOrCreateByNum
#self.nodesByNum[node["num"]] = node
# self.nodesByNum[node["num"]] = node
if "user" in node: # Some nodes might not have user/ids assigned yet
if "id" in node["user"]:
self.nodes[node["user"]["id"]] = node
@@ -924,21 +1253,26 @@ class MeshInterface: # pylint: disable=R0902
self._handleChannel(fromRadio.channel)
elif fromRadio.HasField("packet"):
self._handlePacketFromRadio(fromRadio.packet)
elif fromRadio.HasField("log_record"):
self._handleLogRecord(fromRadio.log_record)
elif fromRadio.HasField("queueStatus"):
self._handleQueueStatusFromRadio(fromRadio.queueStatus)
elif fromRadio.HasField("mqttClientProxyMessage"):
publishingThread.queueWork(
lambda: pub.sendMessage(
"meshtastic.mqttclientproxymessage", proxymessage=fromRadio.mqttClientProxyMessage, interface=self
"meshtastic.mqttclientproxymessage",
proxymessage=fromRadio.mqttClientProxyMessage,
interface=self,
)
)
elif fromRadio.HasField("xmodemPacket"):
publishingThread.queueWork(
lambda: pub.sendMessage(
"meshtastic.xmodempacket", packet=fromRadio.xmodemPacket, interface=self
"meshtastic.xmodempacket",
packet=fromRadio.xmodemPacket,
interface=self,
)
)
@@ -966,7 +1300,10 @@ class MeshInterface: # pylint: disable=R0902
self.localNode.localConfig.bluetooth.CopyFrom(
fromRadio.config.bluetooth
)
elif fromRadio.config.HasField("security"):
self.localNode.localConfig.security.CopyFrom(
fromRadio.config.security
)
elif fromRadio.moduleConfig.HasField("mqtt"):
self.localNode.moduleConfig.mqtt.CopyFrom(fromRadio.moduleConfig.mqtt)
elif fromRadio.moduleConfig.HasField("serial"):
@@ -1032,20 +1369,24 @@ class MeshInterface: # pylint: disable=R0902
position["longitude"] = float(position["longitudeI"] * Decimal("1e-7"))
return position
def _nodeNumToId(self, num: int) -> Optional[str]:
def _nodeNumToId(self, num: int, isDest = True) -> Optional[str]:
"""Map a node node number to a node ID
Arguments:
num {int} -- Node number
isDest {bool} -- True if the node number is a destination (to show broadcast address or unknown node)
Returns:
string -- Node ID
"""
if num == BROADCAST_NUM:
return BROADCAST_ADDR
if isDest:
return BROADCAST_ADDR
else:
return "Unknown"
try:
return self.nodesByNum[num]["user"]["id"] #type: ignore[index]
return self.nodesByNum[num]["user"]["id"] # type: ignore[index]
except:
logging.debug(f"Node {num} not found for fromId")
return None
@@ -1053,7 +1394,9 @@ class MeshInterface: # pylint: disable=R0902
def _getOrCreateByNum(self, nodeNum):
"""Given a nodenum find the NodeInfo in the DB (or create if necessary)"""
if nodeNum == BROADCAST_NUM:
raise MeshInterface.MeshInterfaceError("Can not create/find nodenum by the broadcast num")
raise MeshInterface.MeshInterfaceError(
"Can not create/find nodenum by the broadcast num"
)
if nodeNum in self.nodesByNum:
return self.nodesByNum[nodeNum]
@@ -1065,9 +1408,9 @@ class MeshInterface: # pylint: disable=R0902
"id": presumptive_id,
"longName": f"Meshtastic {presumptive_id[-4:]}",
"shortName": f"{presumptive_id[-4:]}",
"hwModel": "UNSET"
}
} # Create a minimal node db entry
"hwModel": "UNSET",
},
} # Create a minimal node db entry
self.nodesByNum[nodeNum] = n
return n
@@ -1113,7 +1456,7 @@ class MeshInterface: # pylint: disable=R0902
# /add fromId and toId fields based on the node ID
try:
asDict["fromId"] = self._nodeNumToId(asDict["from"])
asDict["fromId"] = self._nodeNumToId(asDict["from"], False)
except Exception as ex:
logging.warning(f"Not populating fromId {ex}")
try:
@@ -1176,13 +1519,21 @@ class MeshInterface: # pylint: disable=R0902
# or the handler is set as ackPermitted, but send NAKs and
# other, data-containing responses to the handlers
routing = decoded.get("routing")
isAck = routing is not None and ("errorReason" not in routing or routing["errorReason"] == "NONE")
isAck = routing is not None and (
"errorReason" not in routing or routing["errorReason"] == "NONE"
)
# we keep the responseHandler in dict until we actually call it
handler = self.responseHandlers.get(requestId, None)
if handler is not None:
if (not isAck) or handler.callback.__name__ == "onAckNak" or handler.ackPermitted:
if (
(not isAck)
or handler.callback.__name__ == "onAckNak"
or handler.ackPermitted
):
handler = self.responseHandlers.pop(requestId, None)
logging.debug(f"Calling response handler for requestId {requestId}")
logging.debug(
f"Calling response handler for requestId {requestId}"
)
handler.callback(asDict)
logging.debug(f"Publishing {topic}: packet={stripnl(asDict)} ")

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():
"""
Restore the namespace to pristine condition.
@@ -33,5 +35,5 @@ args = None
parser = None
channel_index = None
logfile = None
tunnelInstance = None
tunnelInstance: Optional[Any] = None
camel_case = False

View File

@@ -5,7 +5,7 @@ import base64
import logging
import time
from typing import 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.util import (
@@ -25,15 +25,15 @@ class Node:
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"""
self.iface = iface
self.nodeNum = nodeNum
self.localConfig = localonly_pb2.LocalConfig()
self.moduleConfig = localonly_pb2.LocalModuleConfig()
self.channels = None
self._timeout = Timeout(maxSecs=300)
self.partialChannels = None
self._timeout = Timeout(maxSecs=timeout)
self.partialChannels: Optional[List] = None
self.noProto = noProto
self.cannedPluginMessage = None
self.cannedPluginMessageMessages = None
@@ -77,17 +77,19 @@ class Node:
self.channels = channels
self._fixupChannels()
def requestChannels(self):
def requestChannels(self, startingIndex: int = 0):
"""Send regular MeshPackets to ask channels."""
logging.debug(f"requestChannels for nodeNum:{self.nodeNum}")
self.channels = None
self.partialChannels = [] # We keep our channels in a temp array until finished
self._requestChannel(0)
# only initialize if we're starting out fresh
if startingIndex == 0:
self.channels = None
self.partialChannels = [] # We keep our channels in a temp array until finished
self._requestChannel(startingIndex)
def onResponseRequestSettings(self, p):
"""Handle the response packets for requesting settings _requestSettings()"""
logging.debug(f"onResponseRequestSetting() p:{p}")
config_values = None
if "routing" in p["decoded"]:
if p["decoded"]["routing"]["errorReason"] != "NONE":
print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}')
@@ -97,13 +99,16 @@ class Node:
print("")
adminMessage = p["decoded"]["admin"]
if "getConfigResponse" in adminMessage:
oneof = "get_config_response"
resp = adminMessage["getConfigResponse"]
field = list(resp.keys())[0]
config_type = self.localConfig.DESCRIPTOR.fields_by_name.get(
camel_to_snake(field)
)
config_values = getattr(self.localConfig, config_type.name)
if config_type is not None:
config_values = getattr(self.localConfig, config_type.name)
elif "getModuleConfigResponse" in adminMessage:
oneof = "get_module_config_response"
resp = adminMessage["getModuleConfigResponse"]
field = list(resp.keys())[0]
config_type = self.moduleConfig.DESCRIPTOR.fields_by_name.get(
@@ -115,9 +120,10 @@ class Node:
"Did not receive a valid response. Make sure to have a shared channel named 'admin'."
)
return
for key, value in resp[field].items():
setattr(config_values, camel_to_snake(key), value)
print(f"{str(camel_to_snake(field))}:\n{str(config_values)}")
if config_values is not None:
raw_config = getattr(getattr(adminMessage['raw'], oneof), camel_to_snake(field))
config_values.CopyFrom(raw_config)
print(f"{str(camel_to_snake(field))}:\n{str(config_values)}")
def requestConfig(self, configType):
"""Request the config from the node via admin message"""
@@ -126,16 +132,18 @@ class Node:
else:
onResponse = self.onResponseRequestSettings
print("Requesting current config from remote node (this can take a while).")
p = admin_pb2.AdminMessage()
if isinstance(configType, int):
p.get_config_request = configType
msgIndex = configType.index
if configType.containing_type.name == "LocalConfig":
p = admin_pb2.AdminMessage()
p.get_config_request = msgIndex
self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
else:
p = admin_pb2.AdminMessage()
p.get_module_config_request = msgIndex
self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
msgIndex = configType.index
if configType.containing_type.name == "LocalConfig":
p.get_config_request = msgIndex
else:
p.get_module_config_request = msgIndex
self._sendAdmin(p, wantResponse=True, onResponse=onResponse)
if onResponse:
self.iface.waitForAckNak()
@@ -170,6 +178,8 @@ class Node:
p.set_config.lora.CopyFrom(self.localConfig.lora)
elif config_name == "bluetooth":
p.set_config.bluetooth.CopyFrom(self.localConfig.bluetooth)
elif config_name == "security":
p.set_config.security.CopyFrom(self.localConfig.security)
elif config_name == "mqtt":
p.set_module_config.mqtt.CopyFrom(self.moduleConfig.mqtt)
elif config_name == "serial":
@@ -214,7 +224,7 @@ class Node:
def writeChannel(self, channelIndex, adminIndex=0):
"""Write the current (edited) channel to the device"""
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
p.set_channel.CopyFrom(self.channels[channelIndex])
self._sendAdmin(p, adminIndex=adminIndex)
@@ -279,9 +289,10 @@ class Node:
return c.index
return 0
def setOwner(self, long_name=None, short_name=None, is_licensed=False):
def setOwner(self, long_name: Optional[str]=None, short_name: Optional[str]=None, is_licensed: bool=False):
"""Set device owner name"""
logging.debug(f"in setOwner nodeNum:{self.nodeNum}")
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
nChars = 4
@@ -367,6 +378,7 @@ class Node:
p = admin_pb2.AdminMessage()
p.set_config.lora.CopyFrom(channelSet.lora_config)
self.ensureSessionKey()
self._sendAdmin(p)
def onResponseRequestRingtone(self, p):
@@ -415,7 +427,7 @@ class Node:
if len(ringtone) > 230:
our_exit("Warning: The ringtone must be less than 230 characters.")
self.ensureSessionKey()
# split into chunks
chunks = []
chunks_size = 230
@@ -491,7 +503,7 @@ class Node:
if len(message) > 200:
our_exit("Warning: The canned message must be less than 200 characters.")
self.ensureSessionKey()
# split into chunks
chunks = []
chunks_size = 200
@@ -518,6 +530,7 @@ class Node:
def exitSimulator(self):
"""Tell a simulator node to exit (this message
is ignored for other nodes)"""
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
p.exit_simulator = True
logging.debug("in exitSimulator()")
@@ -526,6 +539,7 @@ class Node:
def reboot(self, secs: int = 10):
"""Tell the node to reboot."""
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
p.reboot_seconds = secs
logging.info(f"Telling node to reboot in {secs} seconds")
@@ -539,6 +553,7 @@ class Node:
def beginSettingsTransaction(self):
"""Tell the node to open a transaction to edit settings."""
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
p.begin_edit_settings = True
logging.info(f"Telling open a transaction to edit settings")
@@ -552,6 +567,7 @@ class Node:
def commitSettingsTransaction(self):
"""Tell the node to commit the open transaction for editing settings."""
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
p.commit_edit_settings = True
logging.info(f"Telling node to commit open transaction for editing settings")
@@ -565,6 +581,7 @@ class Node:
def rebootOTA(self, secs: int = 10):
"""Tell the node to reboot into factory firmware."""
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
p.reboot_ota_seconds = secs
logging.info(f"Telling node to reboot to OTA in {secs} seconds")
@@ -578,6 +595,7 @@ class Node:
def enterDFUMode(self):
"""Tell the node to enter DFU mode (NRF52)."""
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
p.enter_dfu_mode_request = True
logging.info(f"Telling node to enable DFU mode")
@@ -591,6 +609,7 @@ class Node:
def shutdown(self, secs: int = 10):
"""Tell the node to shutdown."""
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
p.shutdown_seconds = secs
logging.info(f"Telling node to shutdown in {secs} seconds")
@@ -613,11 +632,16 @@ class Node:
)
self.iface.waitForAckNak()
def factoryReset(self):
def factoryReset(self, full: bool = False):
"""Tell the node to factory reset."""
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
p.factory_reset = True
logging.info(f"Telling node to factory reset")
if full:
p.factory_reset_device = True
logging.info(f"Telling node to factory reset (full device reset)")
else:
p.factory_reset_config = True
logging.info(f"Telling node to factory reset (config reset)")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
@@ -628,6 +652,7 @@ class Node:
def removeNode(self, nodeId: Union[int, str]):
"""Tell the node to remove a specific node by ID"""
self.ensureSessionKey()
if isinstance(nodeId, str):
if nodeId.startswith("!"):
nodeId = int(nodeId[1:], 16)
@@ -643,8 +668,81 @@ class Node:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def setFavorite(self, nodeId: Union[int, str]):
"""Tell the node to set the specified node ID to be favorited on the NodeDB on the device"""
self.ensureSessionKey()
if isinstance(nodeId, str):
if nodeId.startswith("!"):
nodeId = int(nodeId[1:], 16)
else:
nodeId = int(nodeId)
p = admin_pb2.AdminMessage()
p.set_favorite_node = nodeId
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def removeFavorite(self, nodeId: Union[int, str]):
"""Tell the node to set the specified node ID to be un-favorited on the NodeDB on the device"""
self.ensureSessionKey()
if isinstance(nodeId, str):
if nodeId.startswith("!"):
nodeId = int(nodeId[1:], 16)
else:
nodeId = int(nodeId)
p = admin_pb2.AdminMessage()
p.remove_favorite_node = nodeId
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def setIgnored(self, nodeId: Union[int, str]):
"""Tell the node to set the specified node ID to be ignored on the NodeDB on the device"""
self.ensureSessionKey()
if isinstance(nodeId, str):
if nodeId.startswith("!"):
nodeId = int(nodeId[1:], 16)
else:
nodeId = int(nodeId)
p = admin_pb2.AdminMessage()
p.set_ignored_node = nodeId
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def removeIgnored(self, nodeId: Union[int, str]):
"""Tell the node to set the specified node ID to be un-ignored on the NodeDB on the device"""
self.ensureSessionKey()
if isinstance(nodeId, str):
if nodeId.startswith("!"):
nodeId = int(nodeId[1:], 16)
else:
nodeId = int(nodeId)
p = admin_pb2.AdminMessage()
p.remove_ignored_node = nodeId
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def resetNodeDb(self):
"""Tell the node to reset its list of nodes."""
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
p.nodedb_reset = True
logging.info(f"Telling node to reset the NodeDB")
@@ -658,9 +756,7 @@ class Node:
def setFixedPosition(self, lat: Union[int, float], lon: Union[int, float], alt: int):
"""Tell the node to set fixed position to the provided value and enable the fixed position setting"""
if self != self.iface.localNode:
logging.error("Setting position of remote nodes is not supported.")
return None
self.ensureSessionKey()
p = mesh_pb2.Position()
if isinstance(lat, float) and lat != 0.0:
@@ -678,15 +774,40 @@ class Node:
a = admin_pb2.AdminMessage()
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):
"""Tell the node to remove the fixed position and set the fixed position setting to false"""
self.ensureSessionKey()
p = admin_pb2.AdminMessage()
p.remove_fixed_position = True
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):
"""Fixup indexes and add disabled channels as needed"""
@@ -831,7 +952,12 @@ class Node:
): # unless a special channel index was used, we want to use the admin index
adminIndex = self.iface.localNode._getAdminChannelIndex()
logging.debug(f"adminIndex:{adminIndex}")
if isinstance(self.nodeNum, int):
nodeid = self.nodeNum
else: # assume string starting with !
nodeid = int(self.nodeNum[1:],16)
if "adminSessionPassKey" in self.iface._getOrCreateByNum(nodeid):
p.session_passkey = self.iface._getOrCreateByNum(nodeid).get("adminSessionPassKey")
return self.iface.sendData(
p,
self.nodeNum,
@@ -840,4 +966,19 @@ class Node:
wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=adminIndex,
pkiEncrypted=True,
)
def ensureSessionKey(self):
"""If our entry in iface.nodesByNum doesn't already have an adminSessionPassKey, make a request to get one"""
if self.noProto:
logging.warning(
f"Not ensuring session key, because protocol use is disabled by noProto"
)
else:
if isinstance(self.nodeNum, int):
nodeid = self.nodeNum
else: # assume string starting with !
nodeid = int(self.nodeNum[1:],16)
if self.iface._getOrCreateByNum(nodeid).get("adminSessionPassKey") is None:
self.requestConfig(admin_pb2.AdminMessage.SESSIONKEY_CONFIG)

View File

@@ -0,0 +1,7 @@
"""Support for logging from power meters/supplies."""
from .power_supply import PowerError, PowerMeter, PowerSupply
from .ppk2 import PPK2PowerSupply
from .riden import RidenPowerSupply
from .sim import SimPowerSupply
from .stress import PowerStress

View File

@@ -0,0 +1,52 @@
"""code logging power consumption of meshtastic devices."""
import math
from datetime import datetime
class PowerError(Exception):
"""An exception class for powermon errors"""
def __init__(self, message):
self.message = message
super().__init__(self.message)
class PowerMeter:
"""Abstract class for power meters."""
def __init__(self):
"""Initialize the PowerMeter object."""
self.prevPowerTime = datetime.now()
def close(self) -> None:
"""Close the power meter."""
def get_average_current_mA(self) -> float:
"""Returns average current of last measurement in mA (since last call to this method)"""
return math.nan
def get_min_current_mA(self):
"""Returns max current in mA (since last call to this method)."""
# Subclasses must override for a better implementation
return self.get_average_current_mA()
def get_max_current_mA(self):
"""Returns max current in mA (since last call to this method)."""
# Subclasses must override for a better implementation
return self.get_average_current_mA()
def reset_measurements(self):
"""Reset current measurements."""
class PowerSupply(PowerMeter):
"""Abstract class for power supplies."""
def __init__(self):
"""Initialize the PowerSupply object."""
super().__init__()
self.v = 0.0
def powerOn(self):
"""Turn on the power supply (using the voltage set in self.v)."""

182
meshtastic/powermon/ppk2.py Normal file
View File

@@ -0,0 +1,182 @@
"""Classes for logging power consumption of meshtastic devices."""
import logging
import threading
import time
from typing import Optional
from ppk2_api import ppk2_api # type: ignore[import-untyped]
from .power_supply import PowerError, PowerSupply
class PPK2PowerSupply(PowerSupply):
"""Interface for talking with the NRF PPK2 high-resolution micro-power supply.
Power Profiler Kit II is what you should google to find it for purchase.
"""
def __init__(self, portName: Optional[str] = None):
"""Initialize the PowerSupply object.
portName (str, optional): The port name of the power supply. Defaults to "/dev/ttyACM0".
"""
if not portName:
devs = ppk2_api.PPK2_API.list_devices()
if not devs or len(devs) == 0:
raise PowerError("No PPK2 devices found")
elif len(devs) > 1:
raise PowerError(
"Multiple PPK2 devices found, please specify the portName"
)
else:
portName = devs[0]
self.measuring = False
self.current_max = 0
self.current_min = 0
self.current_sum = 0
self.current_num_samples = 0
self.current_average = 0
# for tracking avera data read length (to determine if we are sleeping efficiently in measurement_loop)
self.total_data_len = 0
self.num_data_reads = 0
self.max_data_len = 0
# Normally we just sleep with a timeout on this condition (polling the power measurement data repeatedly)
# but any time our measurements have been fully consumed (via reset_measurements) we notify() this condition
# to trigger a new reading ASAP.
self._want_measurement = threading.Condition()
# To guard against a brief window while updating measured values
self._result_lock = threading.Condition()
self.r = r = ppk2_api.PPK2_API(
portName
) # serial port will be different for you
r.get_modifiers()
self.measurement_thread = threading.Thread(
target=self.measurement_loop, daemon=True, name="ppk2 measurement"
)
logging.info("Connected to Power Profiler Kit II (PPK2)")
super().__init__() # we call this late so that the port is already open and _getRawWattHour callback works
def measurement_loop(self):
"""Endless measurement loop will run in a thread."""
while self.measuring:
with self._want_measurement:
self._want_measurement.wait(
0.0001 if self.num_data_reads == 0 else 0.001
)
# normally we poll using this timeout, but sometimes
# reset_measurement() will notify us to read immediately
# always reads 4096 bytes, even if there is no new samples - or possibly the python single thread (because of global interpreter lock)
# is always behind and thefore we are inherently dropping samples semi randomly!!!
read_data = self.r.get_data()
if read_data != b"":
samples, _ = self.r.get_samples(read_data)
# update invariants
if len(samples) > 0:
if self.current_num_samples == 0:
# First set of new reads, reset min/max
self.current_max = 0
self.current_min = samples[0]
# we need at least one sample to get an initial min
# The following operations could be expensive, so do outside of the lock
# FIXME - change all these lists into numpy arrays to use lots less CPU
self.current_max = max(self.current_max, max(samples))
self.current_min = min(self.current_min, min(samples))
latest_sum = sum(samples)
with self._result_lock:
self.current_sum += latest_sum
self.current_num_samples += len(samples)
# logging.debug(f"PPK2 data_len={len(read_data)}, sample_len={len(samples)}")
self.num_data_reads += 1
self.total_data_len += len(read_data)
self.max_data_len = max(self.max_data_len, len(read_data))
def get_min_current_mA(self):
"""Return the min current in mA."""
return self.current_min / 1000
def get_max_current_mA(self):
"""Return the max current in mA."""
return self.current_max / 1000
def get_average_current_mA(self):
"""Return the average current in mA."""
with self._result_lock:
if self.current_num_samples != 0:
# If we have new samples, calculate a new average
self.current_average = self.current_sum / self.current_num_samples
# Even if we don't have new samples, return the last calculated average
# measurements are in microamperes, divide by 1000
return self.current_average / 1000
def reset_measurements(self):
"""Reset current measurements."""
# Use the last reading as the new only reading (to ensure we always have a valid current reading)
self.current_sum = 0
self.current_num_samples = 0
# if self.num_data_reads:
# logging.debug(f"max data len = {self.max_data_len},avg {self.total_data_len/self.num_data_reads}, num reads={self.num_data_reads}")
# Summary stats for performance monitoring
self.num_data_reads = 0
self.total_data_len = 0
self.max_data_len = 0
with self._want_measurement:
self._want_measurement.notify() # notify the measurement loop to read immediately
def close(self) -> None:
"""Close the power meter."""
self.measuring = False
self.r.stop_measuring() # send command to ppk2
self.measurement_thread.join() # wait for our thread to finish
super().close()
def setIsSupply(self, is_supply: bool):
"""If in supply mode we will provide power ourself, otherwise we are just an amp meter."""
assert self.v > 0.8 # We must set a valid voltage before calling this method
self.r.set_source_voltage(
int(self.v * 1000)
) # set source voltage in mV BEFORE setting source mode
# Note: source voltage must be set even if we are using the amp meter mode
# must be after setting source voltage and before setting mode
self.r.start_measuring() # send command to ppk2
if (
not is_supply
): # min power outpuf of PPK2. If less than this assume we want just meter mode.
self.r.use_ampere_meter()
else:
self.r.use_source_meter() # set source meter mode
if not self.measurement_thread.is_alive():
self.measuring = True
self.reset_measurements()
# We can't start reading from the thread until vdd is set, so start running the thread now
self.measurement_thread.start()
time.sleep(
0.2
) # FIXME - crufty way to ensure we do one set of reads to discard bogus fake power readings in the FIFO
self.reset_measurements()
def powerOn(self):
"""Power on the supply."""
self.r.toggle_DUT_power("ON")
def powerOff(self):
"""Power off the supply."""
self.r.toggle_DUT_power("OFF")

View File

@@ -0,0 +1,57 @@
"""code logging power consumption of meshtastic devices."""
import logging
from datetime import datetime
from riden import Riden
from .power_supply import PowerSupply
class RidenPowerSupply(PowerSupply):
"""Interface for talking to Riden programmable bench-top power supplies.
Only RD6006 tested but others should be similar.
"""
def __init__(self, portName: str = "/dev/ttyUSB0"):
"""Initialize the RidenPowerSupply object.
portName (str, optional): The port name of the power supply. Defaults to "/dev/ttyUSB0".
"""
self.r = r = Riden(port=portName, baudrate=115200, address=1)
logging.info(
f"Connected to Riden power supply: model {r.type}, sn {r.sn}, firmware {r.fw}. Date/time updated."
)
r.set_date_time(datetime.now())
self.prevWattHour = self._getRawWattHour()
self.nowWattHour = self.prevWattHour
super().__init__() # we call this late so that the port is already open and _getRawWattHour callback works
def setMaxCurrent(self, i: float):
"""Set the maximum current the supply will provide."""
self.r.set_i_set(i)
def powerOn(self):
"""Power on the supply, with reasonable defaults for meshtastic devices."""
self.r.set_v_set(
self.v
) # my WM1110 devboard header is directly connected to the 3.3V rail
self.r.set_output(1)
def get_average_current_mA(self) -> float:
"""Returns average current of last measurement in mA (since last call to this method)"""
now = datetime.now()
nowWattHour = self._getRawWattHour()
watts = (
(nowWattHour - self.prevWattHour)
/ (now - self.prevPowerTime).total_seconds()
* 3600
)
self.prevPowerTime = now
self.prevWattHour = nowWattHour
return watts / 1000
def _getRawWattHour(self) -> float:
"""Get the current watt-hour reading."""
self.r.update()
return self.r.wh

View File

@@ -0,0 +1,16 @@
"""code logging power consumption of meshtastic devices."""
import math
import time
from .power_supply import PowerSupply
class SimPowerSupply(PowerSupply):
"""A simulated power supply for testing."""
def get_average_current_mA(self) -> float:
"""Returns average current of last measurement in mA (since last call to this method)"""
# Sim a 20mW load that varies sinusoidally
return (20.0 + 5 * math.sin(time.time()))

View File

@@ -0,0 +1,117 @@
"""Power stress testing support.
"""
import logging
import time
from ..protobuf import portnums_pb2, powermon_pb2
def onPowerStressResponse(packet, interface):
"""Delete me? FIXME"""
logging.debug(f"packet:{packet} interface:{interface}")
# interface.gotResponse = True
class PowerStressClient:
"""
The client stub for talking to the firmware PowerStress module.
"""
def __init__(self, iface, node_id=None):
"""
Create a new PowerStressClient instance.
iface is the already open MeshInterface instance
"""
self.iface = iface
if not node_id:
node_id = iface.myInfo.my_node_num
self.node_id = node_id
# No need to subscribe - because we
# pub.subscribe(onGPIOreceive, "meshtastic.receive.powerstress")
def sendPowerStress(
self,
cmd: powermon_pb2.PowerStressMessage.Opcode.ValueType,
num_seconds: float = 0.0,
onResponse=None,
):
"""Client goo for talking with the device side agent."""
r = powermon_pb2.PowerStressMessage()
r.cmd = cmd
r.num_seconds = num_seconds
return self.iface.sendData(
r,
self.node_id,
portnums_pb2.POWERSTRESS_APP,
wantAck=True,
wantResponse=True,
onResponse=onResponse,
onResponseAckPermitted=True,
)
def syncPowerStress(
self,
cmd: powermon_pb2.PowerStressMessage.Opcode.ValueType,
num_seconds: float = 0.0,
):
"""Send a power stress command and wait for the ack."""
gotAck = False
def onResponse(packet: dict): # pylint: disable=unused-argument
nonlocal gotAck
gotAck = True
logging.info(
f"Sending power stress command {powermon_pb2.PowerStressMessage.Opcode.Name(cmd)}"
)
self.sendPowerStress(cmd, onResponse=onResponse, num_seconds=num_seconds)
if num_seconds == 0.0:
# Wait for the response and then continue
while not gotAck:
time.sleep(0.1)
else:
# we wait a little bit longer than the time the UUT would be waiting (to make sure all of its messages are handled first)
time.sleep(
num_seconds + 0.2
) # completely block our thread for the duration of the test
if not gotAck:
logging.error("Did not receive ack for power stress command!")
class PowerStress:
"""Walk the UUT through a set of power states so we can capture repeatable power consumption measurements."""
def __init__(self, iface):
self.client = PowerStressClient(iface)
def run(self):
"""Run the power stress test."""
try:
self.client.syncPowerStress(powermon_pb2.PowerStressMessage.PRINT_INFO)
num_seconds = 5.0
states = [
powermon_pb2.PowerStressMessage.LED_ON,
powermon_pb2.PowerStressMessage.LED_OFF,
powermon_pb2.PowerStressMessage.BT_OFF,
powermon_pb2.PowerStressMessage.BT_ON,
powermon_pb2.PowerStressMessage.CPU_FULLON,
powermon_pb2.PowerStressMessage.CPU_IDLE,
# FIXME - can't test deepsleep yet because the ttyACM device disappears. Fix the python code to retry connections
# powermon_pb2.PowerStressMessage.CPU_DEEPSLEEP,
]
for s in states:
s_name = powermon_pb2.PowerStressMessage.Opcode.Name(s)
logging.info(
f"Running power stress test {s_name} for {num_seconds} seconds"
)
self.client.syncPowerStress(s, num_seconds)
logging.info("Power stress test complete.")
except KeyboardInterrupt as e:
logging.warning(f"Power stress interrupted: {e}")

View File

File diff suppressed because one or more lines are too long

View File

@@ -12,6 +12,7 @@ import google.protobuf.message
import meshtastic.protobuf.channel_pb2
import meshtastic.protobuf.config_pb2
import meshtastic.protobuf.connection_status_pb2
import meshtastic.protobuf.device_ui_pb2
import meshtastic.protobuf.mesh_pb2
import meshtastic.protobuf.module_config_pb2
import sys
@@ -68,6 +69,16 @@ class AdminMessage(google.protobuf.message.Message):
"""
TODO: REPLACE
"""
SECURITY_CONFIG: AdminMessage._ConfigType.ValueType # 7
"""
TODO: REPLACE
"""
SESSIONKEY_CONFIG: AdminMessage._ConfigType.ValueType # 8
""""""
DEVICEUI_CONFIG: AdminMessage._ConfigType.ValueType # 9
"""
device-ui config
"""
class ConfigType(_ConfigType, metaclass=_ConfigTypeEnumTypeWrapper):
"""
@@ -102,6 +113,16 @@ class AdminMessage(google.protobuf.message.Message):
"""
TODO: REPLACE
"""
SECURITY_CONFIG: AdminMessage.ConfigType.ValueType # 7
"""
TODO: REPLACE
"""
SESSIONKEY_CONFIG: AdminMessage.ConfigType.ValueType # 8
""""""
DEVICEUI_CONFIG: AdminMessage.ConfigType.ValueType # 9
"""
device-ui config
"""
class _ModuleConfigType:
ValueType = typing.NewType("ValueType", builtins.int)
@@ -220,6 +241,7 @@ class AdminMessage(google.protobuf.message.Message):
TODO: REPLACE
"""
SESSION_PASSKEY_FIELD_NUMBER: builtins.int
GET_CHANNEL_REQUEST_FIELD_NUMBER: builtins.int
GET_CHANNEL_RESPONSE_FIELD_NUMBER: builtins.int
GET_OWNER_REQUEST_FIELD_NUMBER: builtins.int
@@ -253,14 +275,27 @@ class AdminMessage(google.protobuf.message.Message):
REMOVE_FAVORITE_NODE_FIELD_NUMBER: builtins.int
SET_FIXED_POSITION_FIELD_NUMBER: builtins.int
REMOVE_FIXED_POSITION_FIELD_NUMBER: builtins.int
SET_TIME_ONLY_FIELD_NUMBER: builtins.int
GET_UI_CONFIG_REQUEST_FIELD_NUMBER: builtins.int
GET_UI_CONFIG_RESPONSE_FIELD_NUMBER: builtins.int
STORE_UI_CONFIG_FIELD_NUMBER: builtins.int
SET_IGNORED_NODE_FIELD_NUMBER: builtins.int
REMOVE_IGNORED_NODE_FIELD_NUMBER: builtins.int
BEGIN_EDIT_SETTINGS_FIELD_NUMBER: builtins.int
COMMIT_EDIT_SETTINGS_FIELD_NUMBER: builtins.int
FACTORY_RESET_DEVICE_FIELD_NUMBER: builtins.int
REBOOT_OTA_SECONDS_FIELD_NUMBER: builtins.int
EXIT_SIMULATOR_FIELD_NUMBER: builtins.int
REBOOT_SECONDS_FIELD_NUMBER: builtins.int
SHUTDOWN_SECONDS_FIELD_NUMBER: builtins.int
FACTORY_RESET_FIELD_NUMBER: builtins.int
FACTORY_RESET_CONFIG_FIELD_NUMBER: builtins.int
NODEDB_RESET_FIELD_NUMBER: builtins.int
session_passkey: builtins.bytes
"""
The node generates this key and sends it with any get_x_response packets.
The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
Prevents replay attacks for admin messages.
"""
get_channel_request: builtins.int
"""
Send the specified channel in the response to this message
@@ -343,6 +378,23 @@ class AdminMessage(google.protobuf.message.Message):
"""
Clear fixed position coordinates and then set position.fixed_position = false
"""
set_time_only: builtins.int
"""
Set time only on the node
Convenience method to set the time on the node (as Net quality) without any other position data
"""
get_ui_config_request: builtins.bool
"""
Tell the node to send the stored ui data.
"""
set_ignored_node: builtins.int
"""
Set specified node-num to be ignored on the NodeDB on the device
"""
remove_ignored_node: builtins.int
"""
Set specified node-num to be un-ignored on the NodeDB on the device
"""
begin_edit_settings: builtins.bool
"""
Begins an edit transaction for config, module config, owner, and channel settings changes
@@ -352,6 +404,10 @@ class AdminMessage(google.protobuf.message.Message):
"""
Commits an open transaction for any edits made to config, module config, owner, and channel settings
"""
factory_reset_device: builtins.int
"""
Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared.
"""
reboot_ota_seconds: builtins.int
"""
Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot)
@@ -370,9 +426,9 @@ class AdminMessage(google.protobuf.message.Message):
"""
Tell the node to shutdown in this many seconds (or <0 to cancel shutdown)
"""
factory_reset: builtins.int
factory_reset_config: builtins.int
"""
Tell the node to factory reset, all device settings will be returned to factory defaults.
Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved.
"""
nodedb_reset: builtins.int
"""
@@ -460,9 +516,22 @@ class AdminMessage(google.protobuf.message.Message):
Set fixed position data on the node and then set the position.fixed_position = true
"""
@property
def get_ui_config_response(self) -> meshtastic.protobuf.device_ui_pb2.DeviceUIConfig:
"""
Reply stored device ui data.
"""
@property
def store_ui_config(self) -> meshtastic.protobuf.device_ui_pb2.DeviceUIConfig:
"""
Tell the node to store UI data persistently.
"""
def __init__(
self,
*,
session_passkey: builtins.bytes = ...,
get_channel_request: builtins.int = ...,
get_channel_response: meshtastic.protobuf.channel_pb2.Channel | None = ...,
get_owner_request: builtins.bool = ...,
@@ -496,18 +565,25 @@ class AdminMessage(google.protobuf.message.Message):
remove_favorite_node: builtins.int = ...,
set_fixed_position: meshtastic.protobuf.mesh_pb2.Position | None = ...,
remove_fixed_position: builtins.bool = ...,
set_time_only: builtins.int = ...,
get_ui_config_request: builtins.bool = ...,
get_ui_config_response: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
store_ui_config: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
set_ignored_node: builtins.int = ...,
remove_ignored_node: builtins.int = ...,
begin_edit_settings: builtins.bool = ...,
commit_edit_settings: builtins.bool = ...,
factory_reset_device: builtins.int = ...,
reboot_ota_seconds: builtins.int = ...,
exit_simulator: builtins.bool = ...,
reboot_seconds: builtins.int = ...,
shutdown_seconds: builtins.int = ...,
factory_reset: builtins.int = ...,
factory_reset_config: builtins.int = ...,
nodedb_reset: builtins.int = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset", b"factory_reset", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "shutdown_seconds", b"shutdown_seconds"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset", b"factory_reset", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "shutdown_seconds", b"shutdown_seconds"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["get_channel_request", "get_channel_response", "get_owner_request", "get_owner_response", "get_config_request", "get_config_response", "get_module_config_request", "get_module_config_response", "get_canned_message_module_messages_request", "get_canned_message_module_messages_response", "get_device_metadata_request", "get_device_metadata_response", "get_ringtone_request", "get_ringtone_response", "get_device_connection_status_request", "get_device_connection_status_response", "set_ham_mode", "get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", "enter_dfu_mode_request", "delete_file_request", "set_scale", "set_owner", "set_channel", "set_config", "set_module_config", "set_canned_message_module_messages", "set_ringtone_message", "remove_by_nodenum", "set_favorite_node", "remove_favorite_node", "set_fixed_position", "remove_fixed_position", "begin_edit_settings", "commit_edit_settings", "reboot_ota_seconds", "exit_simulator", "reboot_seconds", "shutdown_seconds", "factory_reset", "nodedb_reset"] | None: ...
def HasField(self, field_name: typing.Literal["begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["begin_edit_settings", b"begin_edit_settings", "commit_edit_settings", b"commit_edit_settings", "delete_file_request", b"delete_file_request", "enter_dfu_mode_request", b"enter_dfu_mode_request", "exit_simulator", b"exit_simulator", "factory_reset_config", b"factory_reset_config", "factory_reset_device", b"factory_reset_device", "get_canned_message_module_messages_request", b"get_canned_message_module_messages_request", "get_canned_message_module_messages_response", b"get_canned_message_module_messages_response", "get_channel_request", b"get_channel_request", "get_channel_response", b"get_channel_response", "get_config_request", b"get_config_request", "get_config_response", b"get_config_response", "get_device_connection_status_request", b"get_device_connection_status_request", "get_device_connection_status_response", b"get_device_connection_status_response", "get_device_metadata_request", b"get_device_metadata_request", "get_device_metadata_response", b"get_device_metadata_response", "get_module_config_request", b"get_module_config_request", "get_module_config_response", b"get_module_config_response", "get_node_remote_hardware_pins_request", b"get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", b"get_node_remote_hardware_pins_response", "get_owner_request", b"get_owner_request", "get_owner_response", b"get_owner_response", "get_ringtone_request", b"get_ringtone_request", "get_ringtone_response", b"get_ringtone_response", "get_ui_config_request", b"get_ui_config_request", "get_ui_config_response", b"get_ui_config_response", "nodedb_reset", b"nodedb_reset", "payload_variant", b"payload_variant", "reboot_ota_seconds", b"reboot_ota_seconds", "reboot_seconds", b"reboot_seconds", "remove_by_nodenum", b"remove_by_nodenum", "remove_favorite_node", b"remove_favorite_node", "remove_fixed_position", b"remove_fixed_position", "remove_ignored_node", b"remove_ignored_node", "session_passkey", b"session_passkey", "set_canned_message_module_messages", b"set_canned_message_module_messages", "set_channel", b"set_channel", "set_config", b"set_config", "set_favorite_node", b"set_favorite_node", "set_fixed_position", b"set_fixed_position", "set_ham_mode", b"set_ham_mode", "set_ignored_node", b"set_ignored_node", "set_module_config", b"set_module_config", "set_owner", b"set_owner", "set_ringtone_message", b"set_ringtone_message", "set_scale", b"set_scale", "set_time_only", b"set_time_only", "shutdown_seconds", b"shutdown_seconds", "store_ui_config", b"store_ui_config"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["get_channel_request", "get_channel_response", "get_owner_request", "get_owner_response", "get_config_request", "get_config_response", "get_module_config_request", "get_module_config_response", "get_canned_message_module_messages_request", "get_canned_message_module_messages_response", "get_device_metadata_request", "get_device_metadata_response", "get_ringtone_request", "get_ringtone_response", "get_device_connection_status_request", "get_device_connection_status_response", "set_ham_mode", "get_node_remote_hardware_pins_request", "get_node_remote_hardware_pins_response", "enter_dfu_mode_request", "delete_file_request", "set_scale", "set_owner", "set_channel", "set_config", "set_module_config", "set_canned_message_module_messages", "set_ringtone_message", "remove_by_nodenum", "set_favorite_node", "remove_favorite_node", "set_fixed_position", "remove_fixed_position", "set_time_only", "get_ui_config_request", "get_ui_config_response", "store_ui_config", "set_ignored_node", "remove_ignored_node", "begin_edit_settings", "commit_edit_settings", "factory_reset_device", "reboot_ota_seconds", "exit_simulator", "reboot_seconds", "shutdown_seconds", "factory_reset_config", "nodedb_reset"] | None: ...
global___AdminMessage = AdminMessage

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()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -21,20 +21,20 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.atak_pb
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
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_end=895
_globals['_MEMBERROLE']._serialized_start=897
_globals['_MEMBERROLE']._serialized_end=1024
_globals['_TEAM']._serialized_start=721
_globals['_TEAM']._serialized_end=913
_globals['_MEMBERROLE']._serialized_start=915
_globals['_MEMBERROLE']._serialized_end=1042
_globals['_TAKPACKET']._serialized_start=56
_globals['_TAKPACKET']._serialized_end=331
_globals['_GEOCHAT']._serialized_start=333
_globals['_GEOCHAT']._serialized_end=425
_globals['_GROUP']._serialized_start=427
_globals['_GROUP']._serialized_end=522
_globals['_STATUS']._serialized_start=524
_globals['_STATUS']._serialized_end=549
_globals['_CONTACT']._serialized_start=551
_globals['_CONTACT']._serialized_end=603
_globals['_PLI']._serialized_start=605
_globals['_PLI']._serialized_end=700
_globals['_TAKPACKET']._serialized_end=349
_globals['_GEOCHAT']._serialized_start=351
_globals['_GEOCHAT']._serialized_end=443
_globals['_GROUP']._serialized_start=445
_globals['_GROUP']._serialized_end=540
_globals['_STATUS']._serialized_start=542
_globals['_STATUS']._serialized_end=567
_globals['_CONTACT']._serialized_start=569
_globals['_CONTACT']._serialized_end=621
_globals['_PLI']._serialized_start=623
_globals['_PLI']._serialized_end=718
# @@protoc_insertion_point(module_scope)

View File

@@ -248,10 +248,16 @@ class TAKPacket(google.protobuf.message.Message):
STATUS_FIELD_NUMBER: builtins.int
PLI_FIELD_NUMBER: builtins.int
CHAT_FIELD_NUMBER: builtins.int
DETAIL_FIELD_NUMBER: builtins.int
is_compressed: builtins.bool
"""
Are the payloads strings compressed for LoRA transport?
"""
detail: builtins.bytes
"""
Generic CoT detail XML
May be compressed / truncated by the sender (EUD)
"""
@property
def contact(self) -> global___Contact:
"""
@@ -291,10 +297,11 @@ class TAKPacket(google.protobuf.message.Message):
status: global___Status | None = ...,
pli: global___PLI | None = ...,
chat: global___GeoChat | None = ...,
detail: builtins.bytes = ...,
) -> 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 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 WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["pli", "chat"] | None: ...
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", "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", "detail"] | None: ...
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 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()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -22,6 +23,6 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.cliento
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
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_end=386
_globals['_DEVICEPROFILE']._serialized_start=131
_globals['_DEVICEPROFILE']._serialized_end=583
# @@protoc_insertion_point(module_scope)

View File

@@ -7,6 +7,7 @@ import builtins
import google.protobuf.descriptor
import google.protobuf.message
import meshtastic.protobuf.localonly_pb2
import meshtastic.protobuf.mesh_pb2
import typing
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
@@ -25,6 +26,9 @@ class DeviceProfile(google.protobuf.message.Message):
CHANNEL_URL_FIELD_NUMBER: builtins.int
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 for the node
@@ -37,6 +41,14 @@ class DeviceProfile(google.protobuf.message.Message):
"""
The url of the channels from our node
"""
ringtone: builtins.str
"""
Ringtone for ExternalNotification
"""
canned_messages: builtins.str
"""
Predefined messages for CannedMessage
"""
@property
def config(self) -> meshtastic.protobuf.localonly_pb2.LocalConfig:
"""
@@ -49,6 +61,12 @@ class DeviceProfile(google.protobuf.message.Message):
The ModuleConfig of the node
"""
@property
def fixed_position(self) -> meshtastic.protobuf.mesh_pb2.Position:
"""
Fixed position data
"""
def __init__(
self,
*,
@@ -57,18 +75,27 @@ class DeviceProfile(google.protobuf.message.Message):
channel_url: builtins.str | None = ...,
config: meshtastic.protobuf.localonly_pb2.LocalConfig | 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: ...
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 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 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["_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
def WhichOneof(self, oneof_group: typing.Literal["_channel_url", b"_channel_url"]) -> typing.Literal["channel_url"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_config", b"_config"]) -> typing.Literal["config"] | None: ...
@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: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_module_config", b"_module_config"]) -> typing.Literal["module_config"] | None: ...
@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: ...
global___DeviceProfile = DeviceProfile

View File

File diff suppressed because one or more lines are too long

View File

@@ -9,6 +9,7 @@ import google.protobuf.descriptor
import google.protobuf.internal.containers
import google.protobuf.internal.enum_type_wrapper
import google.protobuf.message
import meshtastic.protobuf.device_ui_pb2
import sys
import typing
@@ -56,6 +57,7 @@ class Config(google.protobuf.message.Message):
ROUTER_CLIENT: Config.DeviceConfig._Role.ValueType # 3
"""
Description: Combination of both ROUTER and CLIENT. Not for mobile devices.
Deprecated in v2.3.15 because improper usage is impacting public meshes: Use ROUTER or CLIENT instead.
"""
REPEATER: Config.DeviceConfig._Role.ValueType # 4
"""
@@ -106,6 +108,14 @@ class Config(google.protobuf.message.Message):
and automatic TAK PLI (position location information) broadcasts.
Uses position module configuration to determine TAK PLI broadcast interval.
"""
ROUTER_LATE: Config.DeviceConfig._Role.ValueType # 11
"""
Description: Will always rebroadcast packets, but will do so after all other modes.
Technical Details: Used for router nodes that are intended to provide additional coverage
in areas not already covered by other routers, or to bridge around problematic terrain,
but should not be given priority over other routers in order to avoid unnecessaraily
consuming hops.
"""
class Role(_Role, metaclass=_RoleEnumTypeWrapper):
"""
@@ -131,6 +141,7 @@ class Config(google.protobuf.message.Message):
ROUTER_CLIENT: Config.DeviceConfig.Role.ValueType # 3
"""
Description: Combination of both ROUTER and CLIENT. Not for mobile devices.
Deprecated in v2.3.15 because improper usage is impacting public meshes: Use ROUTER or CLIENT instead.
"""
REPEATER: Config.DeviceConfig.Role.ValueType # 4
"""
@@ -181,6 +192,14 @@ class Config(google.protobuf.message.Message):
and automatic TAK PLI (position location information) broadcasts.
Uses position module configuration to determine TAK PLI broadcast interval.
"""
ROUTER_LATE: Config.DeviceConfig.Role.ValueType # 11
"""
Description: Will always rebroadcast packets, but will do so after all other modes.
Technical Details: Used for router nodes that are intended to provide additional coverage
in areas not already covered by other routers, or to bridge around problematic terrain,
but should not be given priority over other routers in order to avoid unnecessaraily
consuming hops.
"""
class _RebroadcastMode:
ValueType = typing.NewType("ValueType", builtins.int)
@@ -208,6 +227,15 @@ class Config(google.protobuf.message.Message):
Ignores observed messages from foreign meshes like LOCAL_ONLY,
but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB)
"""
NONE: Config.DeviceConfig._RebroadcastMode.ValueType # 4
"""
Only permitted for SENSOR, TRACKER and TAK_TRACKER roles, this will inhibit all rebroadcasts, not unlike CLIENT_MUTE role.
"""
CORE_PORTNUMS_ONLY: Config.DeviceConfig._RebroadcastMode.ValueType # 5
"""
Ignores packets from non-standard portnums such as: TAK, RangeTest, PaxCounter, etc.
Only rebroadcasts packets with standard portnums: NodeInfo, Text, Position, Telemetry, and Routing.
"""
class RebroadcastMode(_RebroadcastMode, metaclass=_RebroadcastModeEnumTypeWrapper):
"""
@@ -234,10 +262,18 @@ class Config(google.protobuf.message.Message):
Ignores observed messages from foreign meshes like LOCAL_ONLY,
but takes it step further by also ignoring messages from nodenums not in the node's known list (NodeDB)
"""
NONE: Config.DeviceConfig.RebroadcastMode.ValueType # 4
"""
Only permitted for SENSOR, TRACKER and TAK_TRACKER roles, this will inhibit all rebroadcasts, not unlike CLIENT_MUTE role.
"""
CORE_PORTNUMS_ONLY: Config.DeviceConfig.RebroadcastMode.ValueType # 5
"""
Ignores packets from non-standard portnums such as: TAK, RangeTest, PaxCounter, etc.
Only rebroadcasts packets with standard portnums: NodeInfo, Text, Position, Telemetry, and Routing.
"""
ROLE_FIELD_NUMBER: builtins.int
SERIAL_ENABLED_FIELD_NUMBER: builtins.int
DEBUG_LOG_ENABLED_FIELD_NUMBER: builtins.int
BUTTON_GPIO_FIELD_NUMBER: builtins.int
BUZZER_GPIO_FIELD_NUMBER: builtins.int
REBROADCAST_MODE_FIELD_NUMBER: builtins.int
@@ -254,11 +290,7 @@ class Config(google.protobuf.message.Message):
serial_enabled: builtins.bool
"""
Disabling this will disable the SerialConsole by not initilizing the StreamAPI
"""
debug_log_enabled: builtins.bool
"""
By default we turn off logging as soon as an API client connects (to keep shared serial link quiet).
Set this to true to leave the debug log outputting even when API is active.
Moved to SecurityConfig
"""
button_gpio: builtins.int
"""
@@ -287,6 +319,7 @@ class Config(google.protobuf.message.Message):
"""
If true, device is considered to be "managed" by a mesh administrator
Clients should then limit available configuration and administrative options inside the user interface
Moved to SecurityConfig
"""
disable_triple_click: builtins.bool
"""
@@ -305,7 +338,6 @@ class Config(google.protobuf.message.Message):
*,
role: global___Config.DeviceConfig.Role.ValueType = ...,
serial_enabled: builtins.bool = ...,
debug_log_enabled: builtins.bool = ...,
button_gpio: builtins.int = ...,
buzzer_gpio: builtins.int = ...,
rebroadcast_mode: global___Config.DeviceConfig.RebroadcastMode.ValueType = ...,
@@ -316,7 +348,7 @@ class Config(google.protobuf.message.Message):
tzdef: builtins.str = ...,
led_heartbeat_disabled: builtins.bool = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["button_gpio", b"button_gpio", "buzzer_gpio", b"buzzer_gpio", "debug_log_enabled", b"debug_log_enabled", "disable_triple_click", b"disable_triple_click", "double_tap_as_button_press", b"double_tap_as_button_press", "is_managed", b"is_managed", "led_heartbeat_disabled", b"led_heartbeat_disabled", "node_info_broadcast_secs", b"node_info_broadcast_secs", "rebroadcast_mode", b"rebroadcast_mode", "role", b"role", "serial_enabled", b"serial_enabled", "tzdef", b"tzdef"]) -> None: ...
def ClearField(self, field_name: typing.Literal["button_gpio", b"button_gpio", "buzzer_gpio", b"buzzer_gpio", "disable_triple_click", b"disable_triple_click", "double_tap_as_button_press", b"double_tap_as_button_press", "is_managed", b"is_managed", "led_heartbeat_disabled", b"led_heartbeat_disabled", "node_info_broadcast_secs", b"node_info_broadcast_secs", "rebroadcast_mode", b"rebroadcast_mode", "role", b"role", "serial_enabled", b"serial_enabled", "tzdef", b"tzdef"]) -> None: ...
@typing.final
class PositionConfig(google.protobuf.message.Message):
@@ -1205,6 +1237,18 @@ class Config(google.protobuf.message.Message):
"""
Singapore 923mhz
"""
PH_433: Config.LoRaConfig._RegionCode.ValueType # 19
"""
Philippines 433mhz
"""
PH_868: Config.LoRaConfig._RegionCode.ValueType # 20
"""
Philippines 868mhz
"""
PH_915: Config.LoRaConfig._RegionCode.ValueType # 21
"""
Philippines 915mhz
"""
class RegionCode(_RegionCode, metaclass=_RegionCodeEnumTypeWrapper): ...
UNSET: Config.LoRaConfig.RegionCode.ValueType # 0
@@ -1283,6 +1327,18 @@ class Config(google.protobuf.message.Message):
"""
Singapore 923mhz
"""
PH_433: Config.LoRaConfig.RegionCode.ValueType # 19
"""
Philippines 433mhz
"""
PH_868: Config.LoRaConfig.RegionCode.ValueType # 20
"""
Philippines 868mhz
"""
PH_915: Config.LoRaConfig.RegionCode.ValueType # 21
"""
Philippines 915mhz
"""
class _ModemPreset:
ValueType = typing.NewType("ValueType", builtins.int)
@@ -1301,6 +1357,7 @@ class Config(google.protobuf.message.Message):
VERY_LONG_SLOW: Config.LoRaConfig._ModemPreset.ValueType # 2
"""
Very Long Range - Slow
Deprecated in 2.5: Works only with txco and is unusably slow
"""
MEDIUM_SLOW: Config.LoRaConfig._ModemPreset.ValueType # 3
"""
@@ -1322,6 +1379,12 @@ class Config(google.protobuf.message.Message):
"""
Long Range - Moderately Fast
"""
SHORT_TURBO: Config.LoRaConfig._ModemPreset.ValueType # 8
"""
Short Range - Turbo
This is the fastest preset and the only one with 500kHz bandwidth.
It is not legal to use in all regions due to this wider bandwidth.
"""
class ModemPreset(_ModemPreset, metaclass=_ModemPresetEnumTypeWrapper):
"""
@@ -1340,6 +1403,7 @@ class Config(google.protobuf.message.Message):
VERY_LONG_SLOW: Config.LoRaConfig.ModemPreset.ValueType # 2
"""
Very Long Range - Slow
Deprecated in 2.5: Works only with txco and is unusably slow
"""
MEDIUM_SLOW: Config.LoRaConfig.ModemPreset.ValueType # 3
"""
@@ -1361,6 +1425,12 @@ class Config(google.protobuf.message.Message):
"""
Long Range - Moderately Fast
"""
SHORT_TURBO: Config.LoRaConfig.ModemPreset.ValueType # 8
"""
Short Range - Turbo
This is the fastest preset and the only one with 500kHz bandwidth.
It is not legal to use in all regions due to this wider bandwidth.
"""
USE_PRESET_FIELD_NUMBER: builtins.int
MODEM_PRESET_FIELD_NUMBER: builtins.int
@@ -1376,8 +1446,10 @@ class Config(google.protobuf.message.Message):
OVERRIDE_DUTY_CYCLE_FIELD_NUMBER: builtins.int
SX126X_RX_BOOSTED_GAIN_FIELD_NUMBER: builtins.int
OVERRIDE_FREQUENCY_FIELD_NUMBER: builtins.int
PA_FAN_DISABLED_FIELD_NUMBER: builtins.int
IGNORE_INCOMING_FIELD_NUMBER: builtins.int
IGNORE_MQTT_FIELD_NUMBER: builtins.int
CONFIG_OK_TO_MQTT_FIELD_NUMBER: builtins.int
use_preset: builtins.bool
"""
When enabled, the `modem_preset` fields will be adhered to, else the `bandwidth`/`spread_factor`/`coding_rate`
@@ -1463,10 +1535,18 @@ class Config(google.protobuf.message.Message):
Please respect your local laws and regulations. If you are a HAM, make sure you
enable HAM mode and turn off encryption.
"""
pa_fan_disabled: builtins.bool
"""
If true, disable the build-in PA FAN using pin define in RF95_FAN_EN.
"""
ignore_mqtt: builtins.bool
"""
If true, the device will not process any packets received via LoRa that passed via MQTT anywhere on the path towards it.
"""
config_ok_to_mqtt: builtins.bool
"""
Sets the ok_to_mqtt bit on outgoing packets
"""
@property
def ignore_incoming(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
"""
@@ -1492,10 +1572,12 @@ class Config(google.protobuf.message.Message):
override_duty_cycle: builtins.bool = ...,
sx126x_rx_boosted_gain: builtins.bool = ...,
override_frequency: builtins.float = ...,
pa_fan_disabled: builtins.bool = ...,
ignore_incoming: collections.abc.Iterable[builtins.int] | None = ...,
ignore_mqtt: builtins.bool = ...,
config_ok_to_mqtt: builtins.bool = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["bandwidth", b"bandwidth", "channel_num", b"channel_num", "coding_rate", b"coding_rate", "frequency_offset", b"frequency_offset", "hop_limit", b"hop_limit", "ignore_incoming", b"ignore_incoming", "ignore_mqtt", b"ignore_mqtt", "modem_preset", b"modem_preset", "override_duty_cycle", b"override_duty_cycle", "override_frequency", b"override_frequency", "region", b"region", "spread_factor", b"spread_factor", "sx126x_rx_boosted_gain", b"sx126x_rx_boosted_gain", "tx_enabled", b"tx_enabled", "tx_power", b"tx_power", "use_preset", b"use_preset"]) -> None: ...
def ClearField(self, field_name: typing.Literal["bandwidth", b"bandwidth", "channel_num", b"channel_num", "coding_rate", b"coding_rate", "config_ok_to_mqtt", b"config_ok_to_mqtt", "frequency_offset", b"frequency_offset", "hop_limit", b"hop_limit", "ignore_incoming", b"ignore_incoming", "ignore_mqtt", b"ignore_mqtt", "modem_preset", b"modem_preset", "override_duty_cycle", b"override_duty_cycle", "override_frequency", b"override_frequency", "pa_fan_disabled", b"pa_fan_disabled", "region", b"region", "spread_factor", b"spread_factor", "sx126x_rx_boosted_gain", b"sx126x_rx_boosted_gain", "tx_enabled", b"tx_enabled", "tx_power", b"tx_power", "use_preset", b"use_preset"]) -> None: ...
@typing.final
class BluetoothConfig(google.protobuf.message.Message):
@@ -1537,7 +1619,6 @@ class Config(google.protobuf.message.Message):
ENABLED_FIELD_NUMBER: builtins.int
MODE_FIELD_NUMBER: builtins.int
FIXED_PIN_FIELD_NUMBER: builtins.int
DEVICE_LOGGING_ENABLED_FIELD_NUMBER: builtins.int
enabled: builtins.bool
"""
Enable Bluetooth on the device
@@ -1550,19 +1631,84 @@ class Config(google.protobuf.message.Message):
"""
Specified PIN for PairingMode.FixedPin
"""
device_logging_enabled: builtins.bool
"""
Enables device (serial style logs) over Bluetooth
"""
def __init__(
self,
*,
enabled: builtins.bool = ...,
mode: global___Config.BluetoothConfig.PairingMode.ValueType = ...,
fixed_pin: builtins.int = ...,
device_logging_enabled: builtins.bool = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["device_logging_enabled", b"device_logging_enabled", "enabled", b"enabled", "fixed_pin", b"fixed_pin", "mode", b"mode"]) -> None: ...
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "fixed_pin", b"fixed_pin", "mode", b"mode"]) -> None: ...
@typing.final
class SecurityConfig(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
PUBLIC_KEY_FIELD_NUMBER: builtins.int
PRIVATE_KEY_FIELD_NUMBER: builtins.int
ADMIN_KEY_FIELD_NUMBER: builtins.int
IS_MANAGED_FIELD_NUMBER: builtins.int
SERIAL_ENABLED_FIELD_NUMBER: builtins.int
DEBUG_LOG_API_ENABLED_FIELD_NUMBER: builtins.int
ADMIN_CHANNEL_ENABLED_FIELD_NUMBER: builtins.int
public_key: builtins.bytes
"""
The public key of the user's device.
Sent out to other nodes on the mesh to allow them to compute a shared secret key.
"""
private_key: builtins.bytes
"""
The private key of the device.
Used to create a shared key with a remote device.
"""
is_managed: builtins.bool
"""
If true, device is considered to be "managed" by a mesh administrator via admin messages
Device is managed by a mesh administrator.
"""
serial_enabled: builtins.bool
"""
Serial Console over the Stream API."
"""
debug_log_api_enabled: builtins.bool
"""
By default we turn off logging as soon as an API client connects (to keep shared serial link quiet).
Output live debug logging over serial or bluetooth is set to true.
"""
admin_channel_enabled: builtins.bool
"""
Allow incoming device control over the insecure legacy admin channel.
"""
@property
def admin_key(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.bytes]:
"""
The public key authorized to send admin messages to this node.
"""
def __init__(
self,
*,
public_key: builtins.bytes = ...,
private_key: builtins.bytes = ...,
admin_key: collections.abc.Iterable[builtins.bytes] | None = ...,
is_managed: builtins.bool = ...,
serial_enabled: builtins.bool = ...,
debug_log_api_enabled: builtins.bool = ...,
admin_channel_enabled: builtins.bool = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["admin_channel_enabled", b"admin_channel_enabled", "admin_key", b"admin_key", "debug_log_api_enabled", b"debug_log_api_enabled", "is_managed", b"is_managed", "private_key", b"private_key", "public_key", b"public_key", "serial_enabled", b"serial_enabled"]) -> None: ...
@typing.final
class SessionkeyConfig(google.protobuf.message.Message):
"""
Blank config request, strictly for getting the session key
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
def __init__(
self,
) -> None: ...
DEVICE_FIELD_NUMBER: builtins.int
POSITION_FIELD_NUMBER: builtins.int
@@ -1571,6 +1717,9 @@ class Config(google.protobuf.message.Message):
DISPLAY_FIELD_NUMBER: builtins.int
LORA_FIELD_NUMBER: builtins.int
BLUETOOTH_FIELD_NUMBER: builtins.int
SECURITY_FIELD_NUMBER: builtins.int
SESSIONKEY_FIELD_NUMBER: builtins.int
DEVICE_UI_FIELD_NUMBER: builtins.int
@property
def device(self) -> global___Config.DeviceConfig: ...
@property
@@ -1585,6 +1734,12 @@ class Config(google.protobuf.message.Message):
def lora(self) -> global___Config.LoRaConfig: ...
@property
def bluetooth(self) -> global___Config.BluetoothConfig: ...
@property
def security(self) -> global___Config.SecurityConfig: ...
@property
def sessionkey(self) -> global___Config.SessionkeyConfig: ...
@property
def device_ui(self) -> meshtastic.protobuf.device_ui_pb2.DeviceUIConfig: ...
def __init__(
self,
*,
@@ -1595,9 +1750,12 @@ class Config(google.protobuf.message.Message):
display: global___Config.DisplayConfig | None = ...,
lora: global___Config.LoRaConfig | None = ...,
bluetooth: global___Config.BluetoothConfig | None = ...,
security: global___Config.SecurityConfig | None = ...,
sessionkey: global___Config.SessionkeyConfig | None = ...,
device_ui: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["device", "position", "power", "network", "display", "lora", "bluetooth"] | None: ...
def HasField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "device_ui", b"device_ui", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power", "security", b"security", "sessionkey", b"sessionkey"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "device_ui", b"device_ui", "display", b"display", "lora", b"lora", "network", b"network", "payload_variant", b"payload_variant", "position", b"position", "power", b"power", "security", b"security", "sessionkey", b"sessionkey"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["device", "position", "power", "network", "display", "lora", "bluetooth", "security", "sessionkey", "device_ui"] | None: ...
global___Config = Config

34
meshtastic/protobuf/device_ui_pb2.py generated Normal file
View File

@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/protobuf/device_ui.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/device_ui.proto\x12\x13meshtastic.protobuf\"\xbf\x03\n\x0e\x44\x65viceUIConfig\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x19\n\x11screen_brightness\x18\x02 \x01(\r\x12\x16\n\x0escreen_timeout\x18\x03 \x01(\r\x12\x13\n\x0bscreen_lock\x18\x04 \x01(\x08\x12\x15\n\rsettings_lock\x18\x05 \x01(\x08\x12\x10\n\x08pin_code\x18\x06 \x01(\r\x12)\n\x05theme\x18\x07 \x01(\x0e\x32\x1a.meshtastic.protobuf.Theme\x12\x15\n\ralert_enabled\x18\x08 \x01(\x08\x12\x16\n\x0e\x62\x61nner_enabled\x18\t \x01(\x08\x12\x14\n\x0cring_tone_id\x18\n \x01(\r\x12/\n\x08language\x18\x0b \x01(\x0e\x32\x1d.meshtastic.protobuf.Language\x12\x34\n\x0bnode_filter\x18\x0c \x01(\x0b\x32\x1f.meshtastic.protobuf.NodeFilter\x12:\n\x0enode_highlight\x18\r \x01(\x0b\x32\".meshtastic.protobuf.NodeHighlight\x12\x18\n\x10\x63\x61libration_data\x18\x0e \x01(\x0c\"\x96\x01\n\nNodeFilter\x12\x16\n\x0eunknown_switch\x18\x01 \x01(\x08\x12\x16\n\x0eoffline_switch\x18\x02 \x01(\x08\x12\x19\n\x11public_key_switch\x18\x03 \x01(\x08\x12\x11\n\thops_away\x18\x04 \x01(\x05\x12\x17\n\x0fposition_switch\x18\x05 \x01(\x08\x12\x11\n\tnode_name\x18\x06 \x01(\t\"~\n\rNodeHighlight\x12\x13\n\x0b\x63hat_switch\x18\x01 \x01(\x08\x12\x17\n\x0fposition_switch\x18\x02 \x01(\x08\x12\x18\n\x10telemetry_switch\x18\x03 \x01(\x08\x12\x12\n\niaq_switch\x18\x04 \x01(\x08\x12\x11\n\tnode_name\x18\x05 \x01(\t*%\n\x05Theme\x12\x08\n\x04\x44\x41RK\x10\x00\x12\t\n\x05LIGHT\x10\x01\x12\x07\n\x03RED\x10\x02*\xfc\x01\n\x08Language\x12\x0b\n\x07\x45NGLISH\x10\x00\x12\n\n\x06\x46RENCH\x10\x01\x12\n\n\x06GERMAN\x10\x02\x12\x0b\n\x07ITALIAN\x10\x03\x12\x0e\n\nPORTUGUESE\x10\x04\x12\x0b\n\x07SPANISH\x10\x05\x12\x0b\n\x07SWEDISH\x10\x06\x12\x0b\n\x07\x46INNISH\x10\x07\x12\n\n\x06POLISH\x10\x08\x12\x0b\n\x07TURKISH\x10\t\x12\x0b\n\x07SERBIAN\x10\n\x12\x0b\n\x07RUSSIAN\x10\x0b\x12\t\n\x05\x44UTCH\x10\x0c\x12\t\n\x05GREEK\x10\r\x12\r\n\tNORWEGIAN\x10\x0e\x12\x16\n\x12SIMPLIFIED_CHINESE\x10\x1e\x12\x17\n\x13TRADITIONAL_CHINESE\x10\x1f\x42\x63\n\x13\x63om.geeksville.meshB\x0e\x44\x65viceUIProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.device_ui_pb2', _globals)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016DeviceUIProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_THEME']._serialized_start=791
_globals['_THEME']._serialized_end=828
_globals['_LANGUAGE']._serialized_start=831
_globals['_LANGUAGE']._serialized_end=1083
_globals['_DEVICEUICONFIG']._serialized_start=61
_globals['_DEVICEUICONFIG']._serialized_end=508
_globals['_NODEFILTER']._serialized_start=511
_globals['_NODEFILTER']._serialized_end=661
_globals['_NODEHIGHLIGHT']._serialized_start=663
_globals['_NODEHIGHLIGHT']._serialized_end=789
# @@protoc_insertion_point(module_scope)

386
meshtastic/protobuf/device_ui_pb2.pyi generated Normal file
View File

@@ -0,0 +1,386 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
"""
import builtins
import google.protobuf.descriptor
import google.protobuf.internal.enum_type_wrapper
import google.protobuf.message
import sys
import typing
if sys.version_info >= (3, 10):
import typing as typing_extensions
else:
import typing_extensions
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
class _Theme:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _ThemeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Theme.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
DARK: _Theme.ValueType # 0
"""
Dark
"""
LIGHT: _Theme.ValueType # 1
"""
Light
"""
RED: _Theme.ValueType # 2
"""
Red
"""
class Theme(_Theme, metaclass=_ThemeEnumTypeWrapper): ...
DARK: Theme.ValueType # 0
"""
Dark
"""
LIGHT: Theme.ValueType # 1
"""
Light
"""
RED: Theme.ValueType # 2
"""
Red
"""
global___Theme = Theme
class _Language:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _LanguageEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Language.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
ENGLISH: _Language.ValueType # 0
"""
English
"""
FRENCH: _Language.ValueType # 1
"""
French
"""
GERMAN: _Language.ValueType # 2
"""
German
"""
ITALIAN: _Language.ValueType # 3
"""
Italian
"""
PORTUGUESE: _Language.ValueType # 4
"""
Portuguese
"""
SPANISH: _Language.ValueType # 5
"""
Spanish
"""
SWEDISH: _Language.ValueType # 6
"""
Swedish
"""
FINNISH: _Language.ValueType # 7
"""
Finnish
"""
POLISH: _Language.ValueType # 8
"""
Polish
"""
TURKISH: _Language.ValueType # 9
"""
Turkish
"""
SERBIAN: _Language.ValueType # 10
"""
Serbian
"""
RUSSIAN: _Language.ValueType # 11
"""
Russian
"""
DUTCH: _Language.ValueType # 12
"""
Dutch
"""
GREEK: _Language.ValueType # 13
"""
Greek
"""
NORWEGIAN: _Language.ValueType # 14
"""
Norwegian
"""
SIMPLIFIED_CHINESE: _Language.ValueType # 30
"""
Simplified Chinese (experimental)
"""
TRADITIONAL_CHINESE: _Language.ValueType # 31
"""
Traditional Chinese (experimental)
"""
class Language(_Language, metaclass=_LanguageEnumTypeWrapper):
"""
Localization
"""
ENGLISH: Language.ValueType # 0
"""
English
"""
FRENCH: Language.ValueType # 1
"""
French
"""
GERMAN: Language.ValueType # 2
"""
German
"""
ITALIAN: Language.ValueType # 3
"""
Italian
"""
PORTUGUESE: Language.ValueType # 4
"""
Portuguese
"""
SPANISH: Language.ValueType # 5
"""
Spanish
"""
SWEDISH: Language.ValueType # 6
"""
Swedish
"""
FINNISH: Language.ValueType # 7
"""
Finnish
"""
POLISH: Language.ValueType # 8
"""
Polish
"""
TURKISH: Language.ValueType # 9
"""
Turkish
"""
SERBIAN: Language.ValueType # 10
"""
Serbian
"""
RUSSIAN: Language.ValueType # 11
"""
Russian
"""
DUTCH: Language.ValueType # 12
"""
Dutch
"""
GREEK: Language.ValueType # 13
"""
Greek
"""
NORWEGIAN: Language.ValueType # 14
"""
Norwegian
"""
SIMPLIFIED_CHINESE: Language.ValueType # 30
"""
Simplified Chinese (experimental)
"""
TRADITIONAL_CHINESE: Language.ValueType # 31
"""
Traditional Chinese (experimental)
"""
global___Language = Language
@typing.final
class DeviceUIConfig(google.protobuf.message.Message):
"""
Protobuf structures for device-ui persistency
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
VERSION_FIELD_NUMBER: builtins.int
SCREEN_BRIGHTNESS_FIELD_NUMBER: builtins.int
SCREEN_TIMEOUT_FIELD_NUMBER: builtins.int
SCREEN_LOCK_FIELD_NUMBER: builtins.int
SETTINGS_LOCK_FIELD_NUMBER: builtins.int
PIN_CODE_FIELD_NUMBER: builtins.int
THEME_FIELD_NUMBER: builtins.int
ALERT_ENABLED_FIELD_NUMBER: builtins.int
BANNER_ENABLED_FIELD_NUMBER: builtins.int
RING_TONE_ID_FIELD_NUMBER: builtins.int
LANGUAGE_FIELD_NUMBER: builtins.int
NODE_FILTER_FIELD_NUMBER: builtins.int
NODE_HIGHLIGHT_FIELD_NUMBER: builtins.int
CALIBRATION_DATA_FIELD_NUMBER: builtins.int
version: builtins.int
"""
A version integer used to invalidate saved files when we make incompatible changes.
"""
screen_brightness: builtins.int
"""
TFT display brightness 1..255
"""
screen_timeout: builtins.int
"""
Screen timeout 0..900
"""
screen_lock: builtins.bool
"""
Screen/Settings lock enabled
"""
settings_lock: builtins.bool
pin_code: builtins.int
theme: global___Theme.ValueType
"""
Color theme
"""
alert_enabled: builtins.bool
"""
Audible message, banner and ring tone
"""
banner_enabled: builtins.bool
ring_tone_id: builtins.int
language: global___Language.ValueType
"""
Localization
"""
calibration_data: builtins.bytes
"""
8 integers for screen calibration data
"""
@property
def node_filter(self) -> global___NodeFilter:
"""
Node list filter
"""
@property
def node_highlight(self) -> global___NodeHighlight:
"""
Node list highlightening
"""
def __init__(
self,
*,
version: builtins.int = ...,
screen_brightness: builtins.int = ...,
screen_timeout: builtins.int = ...,
screen_lock: builtins.bool = ...,
settings_lock: builtins.bool = ...,
pin_code: builtins.int = ...,
theme: global___Theme.ValueType = ...,
alert_enabled: builtins.bool = ...,
banner_enabled: builtins.bool = ...,
ring_tone_id: builtins.int = ...,
language: global___Language.ValueType = ...,
node_filter: global___NodeFilter | None = ...,
node_highlight: global___NodeHighlight | None = ...,
calibration_data: builtins.bytes = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["node_filter", b"node_filter", "node_highlight", b"node_highlight"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["alert_enabled", b"alert_enabled", "banner_enabled", b"banner_enabled", "calibration_data", b"calibration_data", "language", b"language", "node_filter", b"node_filter", "node_highlight", b"node_highlight", "pin_code", b"pin_code", "ring_tone_id", b"ring_tone_id", "screen_brightness", b"screen_brightness", "screen_lock", b"screen_lock", "screen_timeout", b"screen_timeout", "settings_lock", b"settings_lock", "theme", b"theme", "version", b"version"]) -> None: ...
global___DeviceUIConfig = DeviceUIConfig
@typing.final
class NodeFilter(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
UNKNOWN_SWITCH_FIELD_NUMBER: builtins.int
OFFLINE_SWITCH_FIELD_NUMBER: builtins.int
PUBLIC_KEY_SWITCH_FIELD_NUMBER: builtins.int
HOPS_AWAY_FIELD_NUMBER: builtins.int
POSITION_SWITCH_FIELD_NUMBER: builtins.int
NODE_NAME_FIELD_NUMBER: builtins.int
unknown_switch: builtins.bool
"""
Filter unknown nodes
"""
offline_switch: builtins.bool
"""
Filter offline nodes
"""
public_key_switch: builtins.bool
"""
Filter nodes w/o public key
"""
hops_away: builtins.int
"""
Filter based on hops away
"""
position_switch: builtins.bool
"""
Filter nodes w/o position
"""
node_name: builtins.str
"""
Filter nodes by matching name string
"""
def __init__(
self,
*,
unknown_switch: builtins.bool = ...,
offline_switch: builtins.bool = ...,
public_key_switch: builtins.bool = ...,
hops_away: builtins.int = ...,
position_switch: builtins.bool = ...,
node_name: builtins.str = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["hops_away", b"hops_away", "node_name", b"node_name", "offline_switch", b"offline_switch", "position_switch", b"position_switch", "public_key_switch", b"public_key_switch", "unknown_switch", b"unknown_switch"]) -> None: ...
global___NodeFilter = NodeFilter
@typing.final
class NodeHighlight(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
CHAT_SWITCH_FIELD_NUMBER: builtins.int
POSITION_SWITCH_FIELD_NUMBER: builtins.int
TELEMETRY_SWITCH_FIELD_NUMBER: builtins.int
IAQ_SWITCH_FIELD_NUMBER: builtins.int
NODE_NAME_FIELD_NUMBER: builtins.int
chat_switch: builtins.bool
"""
Hightlight nodes w/ active chat
"""
position_switch: builtins.bool
"""
Highlight nodes w/ position
"""
telemetry_switch: builtins.bool
"""
Highlight nodes w/ telemetry data
"""
iaq_switch: builtins.bool
"""
Highlight nodes w/ iaq data
"""
node_name: builtins.str
"""
Highlight nodes by matching name string
"""
def __init__(
self,
*,
chat_switch: builtins.bool = ...,
position_switch: builtins.bool = ...,
telemetry_switch: builtins.bool = ...,
iaq_switch: builtins.bool = ...,
node_name: builtins.str = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["chat_switch", b"chat_switch", "iaq_switch", b"iaq_switch", "node_name", b"node_name", "position_switch", b"position_switch", "telemetry_switch", b"telemetry_switch"]) -> None: ...
global___NodeHighlight = NodeHighlight

View File

@@ -12,14 +12,13 @@ _sym_db = _symbol_database.Default()
from meshtastic.protobuf import channel_pb2 as meshtastic_dot_protobuf_dot_channel__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
from meshtastic.protobuf import module_config_pb2 as meshtastic_dot_protobuf_dot_module__config__pb2
from meshtastic.protobuf import telemetry_pb2 as meshtastic_dot_protobuf_dot_telemetry__pb2
from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config__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/module_config.proto\x1a#meshtastic/protobuf/telemetry.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\"\xa1\x02\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\'\n\x04user\x18\x02 \x01(\x0b\x32\x19.meshtastic.protobuf.User\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\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\"\xde\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\x12\x12\n\nis_ignored\x18\x0b \x01(\x08\x12\x10\n\x08next_hop\x18\x0c \x01(\rB\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(\rBm\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()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -27,20 +26,20 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'meshtastic.protobuf.deviceo
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000\222?\013\302\001\010<vector>'
_USERLITE.fields_by_name['macaddr']._options = None
_USERLITE.fields_by_name['macaddr']._serialized_options = b'\030\001'
_DEVICESTATE.fields_by_name['no_save']._options = None
_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']._serialized_options = b'\222?\'\222\001$std::vector<meshtastic_NodeInfoLite>'
_globals['_SCREENFONTS']._serialized_start=1611
_globals['_SCREENFONTS']._serialized_end=1673
_globals['_POSITIONLITE']._serialized_start=258
_globals['_POSITIONLITE']._serialized_end=411
_globals['_NODEINFOLITE']._serialized_start=414
_globals['_NODEINFOLITE']._serialized_end=703
_globals['_DEVICESTATE']._serialized_start=706
_globals['_DEVICESTATE']._serialized_end=1220
_globals['_CHANNELFILE']._serialized_start=1222
_globals['_CHANNELFILE']._serialized_end=1300
_globals['_OEMSTORE']._serialized_start=1303
_globals['_OEMSTORE']._serialized_end=1609
_globals['_POSITIONLITE']._serialized_start=214
_globals['_POSITIONLITE']._serialized_end=367
_globals['_USERLITE']._serialized_start=370
_globals['_USERLITE']._serialized_end=596
_globals['_NODEINFOLITE']._serialized_start=599
_globals['_NODEINFOLITE']._serialized_end=949
_globals['_DEVICESTATE']._serialized_start=952
_globals['_DEVICESTATE']._serialized_end=1466
_globals['_CHANNELFILE']._serialized_start=1468
_globals['_CHANNELFILE']._serialized_end=1546
# @@protoc_insertion_point(module_scope)

View File

@@ -7,60 +7,15 @@ import builtins
import collections.abc
import google.protobuf.descriptor
import google.protobuf.internal.containers
import google.protobuf.internal.enum_type_wrapper
import google.protobuf.message
import meshtastic.protobuf.channel_pb2
import meshtastic.protobuf.localonly_pb2
import meshtastic.protobuf.config_pb2
import meshtastic.protobuf.mesh_pb2
import meshtastic.protobuf.telemetry_pb2
import sys
import typing
if sys.version_info >= (3, 10):
import typing as typing_extensions
else:
import typing_extensions
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
class _ScreenFonts:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _ScreenFontsEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_ScreenFonts.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
FONT_SMALL: _ScreenFonts.ValueType # 0
"""
TODO: REPLACE
"""
FONT_MEDIUM: _ScreenFonts.ValueType # 1
"""
TODO: REPLACE
"""
FONT_LARGE: _ScreenFonts.ValueType # 2
"""
TODO: REPLACE
"""
class ScreenFonts(_ScreenFonts, metaclass=_ScreenFontsEnumTypeWrapper):
"""
Font sizes for the device screen
"""
FONT_SMALL: ScreenFonts.ValueType # 0
"""
TODO: REPLACE
"""
FONT_MEDIUM: ScreenFonts.ValueType # 1
"""
TODO: REPLACE
"""
FONT_LARGE: ScreenFonts.ValueType # 2
"""
TODO: REPLACE
"""
global___ScreenFonts = ScreenFonts
@typing.final
class PositionLite(google.protobuf.message.Message):
"""
@@ -112,6 +67,67 @@ class PositionLite(google.protobuf.message.Message):
global___PositionLite = PositionLite
@typing.final
class UserLite(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
MACADDR_FIELD_NUMBER: builtins.int
LONG_NAME_FIELD_NUMBER: builtins.int
SHORT_NAME_FIELD_NUMBER: builtins.int
HW_MODEL_FIELD_NUMBER: builtins.int
IS_LICENSED_FIELD_NUMBER: builtins.int
ROLE_FIELD_NUMBER: builtins.int
PUBLIC_KEY_FIELD_NUMBER: builtins.int
macaddr: builtins.bytes
"""
This is the addr of the radio.
"""
long_name: builtins.str
"""
A full name for this user, i.e. "Kevin Hester"
"""
short_name: builtins.str
"""
A VERY short name, ideally two characters.
Suitable for a tiny OLED screen
"""
hw_model: meshtastic.protobuf.mesh_pb2.HardwareModel.ValueType
"""
TBEAM, HELTEC, etc...
Starting in 1.2.11 moved to hw_model enum in the NodeInfo object.
Apps will still need the string here for older builds
(so OTA update can find the right image), but if the enum is available it will be used instead.
"""
is_licensed: builtins.bool
"""
In some regions Ham radio operators have different bandwidth limitations than others.
If this user is a licensed operator, set this flag.
Also, "long_name" should be their licence number.
"""
role: meshtastic.protobuf.config_pb2.Config.DeviceConfig.Role.ValueType
"""
Indicates that the user's role in the mesh
"""
public_key: builtins.bytes
"""
The public key of the user's device.
This is sent out to other nodes on the mesh to allow them to compute a shared secret key.
"""
def __init__(
self,
*,
macaddr: builtins.bytes = ...,
long_name: builtins.str = ...,
short_name: builtins.str = ...,
hw_model: meshtastic.protobuf.mesh_pb2.HardwareModel.ValueType = ...,
is_licensed: builtins.bool = ...,
role: meshtastic.protobuf.config_pb2.Config.DeviceConfig.Role.ValueType = ...,
public_key: builtins.bytes = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["hw_model", b"hw_model", "is_licensed", b"is_licensed", "long_name", b"long_name", "macaddr", b"macaddr", "public_key", b"public_key", "role", b"role", "short_name", b"short_name"]) -> None: ...
global___UserLite = UserLite
@typing.final
class NodeInfoLite(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
@@ -126,6 +142,8 @@ class NodeInfoLite(google.protobuf.message.Message):
VIA_MQTT_FIELD_NUMBER: builtins.int
HOPS_AWAY_FIELD_NUMBER: builtins.int
IS_FAVORITE_FIELD_NUMBER: builtins.int
IS_IGNORED_FIELD_NUMBER: builtins.int
NEXT_HOP_FIELD_NUMBER: builtins.int
num: builtins.int
"""
The node number
@@ -149,15 +167,24 @@ class NodeInfoLite(google.protobuf.message.Message):
"""
hops_away: builtins.int
"""
Number of hops away from us this node is (0 if adjacent)
Number of hops away from us this node is (0 if direct neighbor)
"""
is_favorite: builtins.bool
"""
True if node is in our favorites list
Persists between NodeDB internal clean ups
"""
is_ignored: builtins.bool
"""
True if node is in our ignored list
Persists between NodeDB internal clean ups
"""
next_hop: builtins.int
"""
Last byte of the node number of the node that should be used as the next hop to reach this node.
"""
@property
def user(self) -> meshtastic.protobuf.mesh_pb2.User:
def user(self) -> global___UserLite:
"""
The user info for this node
"""
@@ -179,18 +206,21 @@ class NodeInfoLite(google.protobuf.message.Message):
self,
*,
num: builtins.int = ...,
user: meshtastic.protobuf.mesh_pb2.User | None = ...,
user: global___UserLite | None = ...,
position: global___PositionLite | None = ...,
snr: builtins.float = ...,
last_heard: builtins.int = ...,
device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ...,
channel: builtins.int = ...,
via_mqtt: builtins.bool = ...,
hops_away: builtins.int = ...,
hops_away: builtins.int | None = ...,
is_favorite: builtins.bool = ...,
is_ignored: builtins.bool = ...,
next_hop: builtins.int = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["device_metrics", b"device_metrics", "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 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["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "is_ignored", b"is_ignored", "last_heard", b"last_heard", "next_hop", b"next_hop", "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
@@ -328,73 +358,3 @@ class ChannelFile(google.protobuf.message.Message):
def ClearField(self, field_name: typing.Literal["channels", b"channels", "version", b"version"]) -> None: ...
global___ChannelFile = ChannelFile
@typing.final
class OEMStore(google.protobuf.message.Message):
"""
This can be used for customizing the firmware distribution. If populated,
show a secondary bootup screen with custom logo and text for 2.5 seconds.
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
OEM_ICON_WIDTH_FIELD_NUMBER: builtins.int
OEM_ICON_HEIGHT_FIELD_NUMBER: builtins.int
OEM_ICON_BITS_FIELD_NUMBER: builtins.int
OEM_FONT_FIELD_NUMBER: builtins.int
OEM_TEXT_FIELD_NUMBER: builtins.int
OEM_AES_KEY_FIELD_NUMBER: builtins.int
OEM_LOCAL_CONFIG_FIELD_NUMBER: builtins.int
OEM_LOCAL_MODULE_CONFIG_FIELD_NUMBER: builtins.int
oem_icon_width: builtins.int
"""
The Logo width in Px
"""
oem_icon_height: builtins.int
"""
The Logo height in Px
"""
oem_icon_bits: builtins.bytes
"""
The Logo in XBM bytechar format
"""
oem_font: global___ScreenFonts.ValueType
"""
Use this font for the OEM text.
"""
oem_text: builtins.str
"""
Use this font for the OEM text.
"""
oem_aes_key: builtins.bytes
"""
The default device encryption key, 16 or 32 byte
"""
@property
def oem_local_config(self) -> meshtastic.protobuf.localonly_pb2.LocalConfig:
"""
A Preset LocalConfig to apply during factory reset
"""
@property
def oem_local_module_config(self) -> meshtastic.protobuf.localonly_pb2.LocalModuleConfig:
"""
A Preset LocalModuleConfig to apply during factory reset
"""
def __init__(
self,
*,
oem_icon_width: builtins.int = ...,
oem_icon_height: builtins.int = ...,
oem_icon_bits: builtins.bytes = ...,
oem_font: global___ScreenFonts.ValueType = ...,
oem_text: builtins.str = ...,
oem_aes_key: builtins.bytes = ...,
oem_local_config: meshtastic.protobuf.localonly_pb2.LocalConfig | None = ...,
oem_local_module_config: meshtastic.protobuf.localonly_pb2.LocalModuleConfig | None = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["oem_local_config", b"oem_local_config", "oem_local_module_config", b"oem_local_module_config"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["oem_aes_key", b"oem_aes_key", "oem_font", b"oem_font", "oem_icon_bits", b"oem_icon_bits", "oem_icon_height", b"oem_icon_height", "oem_icon_width", b"oem_icon_width", "oem_local_config", b"oem_local_config", "oem_local_module_config", b"oem_local_module_config", "oem_text", b"oem_text"]) -> None: ...
global___OEMStore = OEMStore

View File

@@ -15,7 +15,7 @@ from meshtastic.protobuf import config_pb2 as meshtastic_dot_protobuf_dot_config
from meshtastic.protobuf import module_config_pb2 as meshtastic_dot_protobuf_dot_module__config__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/localonly.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xbc\x03\n\x0bLocalConfig\x12\x38\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfig\x12<\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfig\x12\x36\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfig\x12:\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfig\x12:\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfig\x12\x34\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfig\x12>\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\"\xf0\x07\n\x11LocalModuleConfig\x12:\n\x04mqtt\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.ModuleConfig.MQTTConfig\x12>\n\x06serial\x18\x02 \x01(\x0b\x32..meshtastic.protobuf.ModuleConfig.SerialConfig\x12[\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32<.meshtastic.protobuf.ModuleConfig.ExternalNotificationConfig\x12K\n\rstore_forward\x18\x04 \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.StoreForwardConfig\x12\x45\n\nrange_test\x18\x05 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.RangeTestConfig\x12\x44\n\ttelemetry\x18\x06 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.TelemetryConfig\x12M\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.CannedMessageConfig\x12<\n\x05\x61udio\x18\t \x01(\x0b\x32-.meshtastic.protobuf.ModuleConfig.AudioConfig\x12O\n\x0fremote_hardware\x18\n \x01(\x0b\x32\x36.meshtastic.protobuf.ModuleConfig.RemoteHardwareConfig\x12K\n\rneighbor_info\x18\x0b \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.NeighborInfoConfig\x12Q\n\x10\x61mbient_lighting\x18\x0c \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.AmbientLightingConfig\x12Q\n\x10\x64\x65tection_sensor\x18\r \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.DetectionSensorConfig\x12\x46\n\npaxcounter\x18\x0e \x01(\x0b\x32\x32.meshtastic.protobuf.ModuleConfig.PaxcounterConfig\x12\x0f\n\x07version\x18\x08 \x01(\rBd\n\x13\x63om.geeksville.meshB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#meshtastic/protobuf/localonly.proto\x12\x13meshtastic.protobuf\x1a meshtastic/protobuf/config.proto\x1a\'meshtastic/protobuf/module_config.proto\"\xfa\x03\n\x0bLocalConfig\x12\x38\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32(.meshtastic.protobuf.Config.DeviceConfig\x12<\n\x08position\x18\x02 \x01(\x0b\x32*.meshtastic.protobuf.Config.PositionConfig\x12\x36\n\x05power\x18\x03 \x01(\x0b\x32\'.meshtastic.protobuf.Config.PowerConfig\x12:\n\x07network\x18\x04 \x01(\x0b\x32).meshtastic.protobuf.Config.NetworkConfig\x12:\n\x07\x64isplay\x18\x05 \x01(\x0b\x32).meshtastic.protobuf.Config.DisplayConfig\x12\x34\n\x04lora\x18\x06 \x01(\x0b\x32&.meshtastic.protobuf.Config.LoRaConfig\x12>\n\tbluetooth\x18\x07 \x01(\x0b\x32+.meshtastic.protobuf.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\x12<\n\x08security\x18\t \x01(\x0b\x32*.meshtastic.protobuf.Config.SecurityConfig\"\xf0\x07\n\x11LocalModuleConfig\x12:\n\x04mqtt\x18\x01 \x01(\x0b\x32,.meshtastic.protobuf.ModuleConfig.MQTTConfig\x12>\n\x06serial\x18\x02 \x01(\x0b\x32..meshtastic.protobuf.ModuleConfig.SerialConfig\x12[\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32<.meshtastic.protobuf.ModuleConfig.ExternalNotificationConfig\x12K\n\rstore_forward\x18\x04 \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.StoreForwardConfig\x12\x45\n\nrange_test\x18\x05 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.RangeTestConfig\x12\x44\n\ttelemetry\x18\x06 \x01(\x0b\x32\x31.meshtastic.protobuf.ModuleConfig.TelemetryConfig\x12M\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32\x35.meshtastic.protobuf.ModuleConfig.CannedMessageConfig\x12<\n\x05\x61udio\x18\t \x01(\x0b\x32-.meshtastic.protobuf.ModuleConfig.AudioConfig\x12O\n\x0fremote_hardware\x18\n \x01(\x0b\x32\x36.meshtastic.protobuf.ModuleConfig.RemoteHardwareConfig\x12K\n\rneighbor_info\x18\x0b \x01(\x0b\x32\x34.meshtastic.protobuf.ModuleConfig.NeighborInfoConfig\x12Q\n\x10\x61mbient_lighting\x18\x0c \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.AmbientLightingConfig\x12Q\n\x10\x64\x65tection_sensor\x18\r \x01(\x0b\x32\x37.meshtastic.protobuf.ModuleConfig.DetectionSensorConfig\x12\x46\n\npaxcounter\x18\x0e \x01(\x0b\x32\x32.meshtastic.protobuf.ModuleConfig.PaxcounterConfig\x12\x0f\n\x07version\x18\x08 \x01(\rBd\n\x13\x63om.geeksville.meshB\x0fLocalOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -24,7 +24,7 @@ if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017LocalOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_LOCALCONFIG']._serialized_start=136
_globals['_LOCALCONFIG']._serialized_end=580
_globals['_LOCALMODULECONFIG']._serialized_start=583
_globals['_LOCALMODULECONFIG']._serialized_end=1591
_globals['_LOCALCONFIG']._serialized_end=642
_globals['_LOCALMODULECONFIG']._serialized_start=645
_globals['_LOCALMODULECONFIG']._serialized_end=1653
# @@protoc_insertion_point(module_scope)

View File

@@ -29,6 +29,7 @@ class LocalConfig(google.protobuf.message.Message):
LORA_FIELD_NUMBER: builtins.int
BLUETOOTH_FIELD_NUMBER: builtins.int
VERSION_FIELD_NUMBER: builtins.int
SECURITY_FIELD_NUMBER: builtins.int
version: builtins.int
"""
A version integer used to invalidate old save files when we make
@@ -77,6 +78,12 @@ class LocalConfig(google.protobuf.message.Message):
The part of the config that is specific to the Bluetooth settings
"""
@property
def security(self) -> meshtastic.protobuf.config_pb2.Config.SecurityConfig:
"""
The part of the config that is specific to Security settings
"""
def __init__(
self,
*,
@@ -88,9 +95,10 @@ class LocalConfig(google.protobuf.message.Message):
lora: meshtastic.protobuf.config_pb2.Config.LoRaConfig | None = ...,
bluetooth: meshtastic.protobuf.config_pb2.Config.BluetoothConfig | None = ...,
version: builtins.int = ...,
security: meshtastic.protobuf.config_pb2.Config.SecurityConfig | None = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "position", b"position", "power", b"power"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "position", b"position", "power", b"power", "version", b"version"]) -> None: ...
def HasField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "position", b"position", "power", b"power", "security", b"security"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["bluetooth", b"bluetooth", "device", b"device", "display", b"display", "lora", b"lora", "network", b"network", "position", b"position", "power", b"power", "security", b"security", "version", b"version"]) -> None: ...
global___LocalConfig = LocalConfig

View File

File diff suppressed because one or more lines are too long

View File

@@ -11,6 +11,7 @@ import google.protobuf.internal.enum_type_wrapper
import google.protobuf.message
import meshtastic.protobuf.channel_pb2
import meshtastic.protobuf.config_pb2
import meshtastic.protobuf.device_ui_pb2
import meshtastic.protobuf.module_config_pb2
import meshtastic.protobuf.portnums_pb2
import meshtastic.protobuf.telemetry_pb2
@@ -129,6 +130,10 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
"""
Heltec HRU-3601: https://heltec.org/project/hru-3601/
"""
HELTEC_WIRELESS_BRIDGE: _HardwareModel.ValueType # 24
"""
Heltec Wireless Bridge
"""
STATION_G1: _HardwareModel.ValueType # 25
"""
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
@@ -201,7 +206,7 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
"""
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
"""
@@ -307,6 +312,91 @@ class _HardwareModelEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._
"""
Heltec Capsule Sensor V3 with ESP32-S3 CPU, Portable LoRa device that can replace GNSS modules or sensors
"""
HELTEC_VISION_MASTER_T190: _HardwareModel.ValueType # 66
"""
Heltec Vision Master T190 with ESP32-S3 CPU, and a 1.90 inch TFT display
"""
HELTEC_VISION_MASTER_E213: _HardwareModel.ValueType # 67
"""
Heltec Vision Master E213 with ESP32-S3 CPU, and a 2.13 inch E-Ink display
"""
HELTEC_VISION_MASTER_E290: _HardwareModel.ValueType # 68
"""
Heltec Vision Master E290 with ESP32-S3 CPU, and a 2.9 inch E-Ink display
"""
HELTEC_MESH_NODE_T114: _HardwareModel.ValueType # 69
"""
Heltec Mesh Node T114 board with nRF52840 CPU, and a 1.14 inch TFT display, Ultimate low-power design,
specifically adapted for the Meshtatic project
"""
SENSECAP_INDICATOR: _HardwareModel.ValueType # 70
"""
Sensecap Indicator from Seeed Studio. ESP32-S3 device with TFT and RP2040 coprocessor
"""
TRACKER_T1000_E: _HardwareModel.ValueType # 71
"""
Seeed studio T1000-E tracker card. NRF52840 w/ LR1110 radio, GPS, button, buzzer, and sensors.
"""
RAK3172: _HardwareModel.ValueType # 72
"""
RAK3172 STM32WLE5 Module (https://store.rakwireless.com/products/wisduo-lpwan-module-rak3172)
"""
WIO_E5: _HardwareModel.ValueType # 73
"""
Seeed Studio Wio-E5 (either mini or Dev kit) using STM32WL chip.
"""
RADIOMASTER_900_BANDIT: _HardwareModel.ValueType # 74
"""
RadioMaster 900 Bandit, https://www.radiomasterrc.com/products/bandit-expresslrs-rf-module
SSD1306 OLED and No GPS
"""
ME25LS01_4Y10TD: _HardwareModel.ValueType # 75
"""
Minewsemi ME25LS01 (ME25LE01_V1.0). NRF52840 w/ LR1110 radio, buttons and leds and pins.
"""
RP2040_FEATHER_RFM95: _HardwareModel.ValueType # 76
"""
RP2040_FEATHER_RFM95
Adafruit Feather RP2040 with RFM95 LoRa Radio RFM95 with SX1272, SSD1306 OLED
https://www.adafruit.com/product/5714
https://www.adafruit.com/product/326
https://www.adafruit.com/product/938
^^^ short A0 to switch to I2C address 0x3C
"""
M5STACK_COREBASIC: _HardwareModel.ValueType # 77
"""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
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
"""
WISMESH_TAP: _HardwareModel.ValueType # 84
"""
WisMesh Tap
RAK-4631 w/ TFT in injection modled case
"""
ROUTASTIC: _HardwareModel.ValueType # 85
"""
Similar to PORTDUINO but used by Routastic devices, this is not any
particular device and does not run Meshtastic's code but supports
the same frame format.
Runs on linux, see https://github.com/Jorropo/routastic
"""
MESH_TAB: _HardwareModel.ValueType # 86
"""
Mesh-Tab, esp32 based
https://github.com/valzzu/Mesh-Tab
"""
PRIVATE_HW: _HardwareModel.ValueType # 255
"""
------------------------------------------------------------------------------------------------------------------------------------------
@@ -420,6 +510,10 @@ HELTEC_HRU_3601: HardwareModel.ValueType # 23
"""
Heltec HRU-3601: https://heltec.org/project/hru-3601/
"""
HELTEC_WIRELESS_BRIDGE: HardwareModel.ValueType # 24
"""
Heltec Wireless Bridge
"""
STATION_G1: HardwareModel.ValueType # 25
"""
B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station
@@ -492,7 +586,7 @@ Custom Disaster Radio esp32 v3 device https://github.com/sudomesh/disaster-radio
"""
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
"""
@@ -598,6 +692,91 @@ HELTEC_CAPSULE_SENSOR_V3: HardwareModel.ValueType # 65
"""
Heltec Capsule Sensor V3 with ESP32-S3 CPU, Portable LoRa device that can replace GNSS modules or sensors
"""
HELTEC_VISION_MASTER_T190: HardwareModel.ValueType # 66
"""
Heltec Vision Master T190 with ESP32-S3 CPU, and a 1.90 inch TFT display
"""
HELTEC_VISION_MASTER_E213: HardwareModel.ValueType # 67
"""
Heltec Vision Master E213 with ESP32-S3 CPU, and a 2.13 inch E-Ink display
"""
HELTEC_VISION_MASTER_E290: HardwareModel.ValueType # 68
"""
Heltec Vision Master E290 with ESP32-S3 CPU, and a 2.9 inch E-Ink display
"""
HELTEC_MESH_NODE_T114: HardwareModel.ValueType # 69
"""
Heltec Mesh Node T114 board with nRF52840 CPU, and a 1.14 inch TFT display, Ultimate low-power design,
specifically adapted for the Meshtatic project
"""
SENSECAP_INDICATOR: HardwareModel.ValueType # 70
"""
Sensecap Indicator from Seeed Studio. ESP32-S3 device with TFT and RP2040 coprocessor
"""
TRACKER_T1000_E: HardwareModel.ValueType # 71
"""
Seeed studio T1000-E tracker card. NRF52840 w/ LR1110 radio, GPS, button, buzzer, and sensors.
"""
RAK3172: HardwareModel.ValueType # 72
"""
RAK3172 STM32WLE5 Module (https://store.rakwireless.com/products/wisduo-lpwan-module-rak3172)
"""
WIO_E5: HardwareModel.ValueType # 73
"""
Seeed Studio Wio-E5 (either mini or Dev kit) using STM32WL chip.
"""
RADIOMASTER_900_BANDIT: HardwareModel.ValueType # 74
"""
RadioMaster 900 Bandit, https://www.radiomasterrc.com/products/bandit-expresslrs-rf-module
SSD1306 OLED and No GPS
"""
ME25LS01_4Y10TD: HardwareModel.ValueType # 75
"""
Minewsemi ME25LS01 (ME25LE01_V1.0). NRF52840 w/ LR1110 radio, buttons and leds and pins.
"""
RP2040_FEATHER_RFM95: HardwareModel.ValueType # 76
"""
RP2040_FEATHER_RFM95
Adafruit Feather RP2040 with RFM95 LoRa Radio RFM95 with SX1272, SSD1306 OLED
https://www.adafruit.com/product/5714
https://www.adafruit.com/product/326
https://www.adafruit.com/product/938
^^^ short A0 to switch to I2C address 0x3C
"""
M5STACK_COREBASIC: HardwareModel.ValueType # 77
"""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
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
"""
WISMESH_TAP: HardwareModel.ValueType # 84
"""
WisMesh Tap
RAK-4631 w/ TFT in injection modled case
"""
ROUTASTIC: HardwareModel.ValueType # 85
"""
Similar to PORTDUINO but used by Routastic devices, this is not any
particular device and does not run Meshtastic's code but supports
the same frame format.
Runs on linux, see https://github.com/Jorropo/routastic
"""
MESH_TAB: HardwareModel.ValueType # 86
"""
Mesh-Tab, esp32 based
https://github.com/valzzu/Mesh-Tab
"""
PRIVATE_HW: HardwareModel.ValueType # 255
"""
------------------------------------------------------------------------------------------------------------------------------------------
@@ -617,7 +796,7 @@ class _ConstantsEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._Enum
First enum must be zero, and we are just using this enum to
pass int constants between two very different environments
"""
DATA_PAYLOAD_LEN: _Constants.ValueType # 237
DATA_PAYLOAD_LEN: _Constants.ValueType # 233
"""
From mesh.options
note: this payload length is ONLY the bytes that are sent inside of the Data protobuf (excluding protobuf overhead). The 16 byte header is
@@ -634,7 +813,7 @@ ZERO: Constants.ValueType # 0
First enum must be zero, and we are just using this enum to
pass int constants between two very different environments
"""
DATA_PAYLOAD_LEN: Constants.ValueType # 237
DATA_PAYLOAD_LEN: Constants.ValueType # 233
"""
From mesh.options
note: this payload length is ONLY the bytes that are sent inside of the Data protobuf (excluding protobuf overhead). The 16 byte header is
@@ -697,6 +876,17 @@ class _CriticalErrorCodeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapp
A (likely software but possibly hardware) failure was detected while trying to send packets.
If this occurs on your board, please post in the forum so that we can ask you to collect some information to allow fixing this bug
"""
FLASH_CORRUPTION_RECOVERABLE: _CriticalErrorCode.ValueType # 12
"""
Corruption was detected on the flash filesystem but we were able to repair things.
If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field.
"""
FLASH_CORRUPTION_UNRECOVERABLE: _CriticalErrorCode.ValueType # 13
"""
Corruption was detected on the flash filesystem but we were unable to repair things.
NOTE: Your node will probably need to be reconfigured the next time it reboots (it will lose the region code etc...)
If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field.
"""
class CriticalErrorCode(_CriticalErrorCode, metaclass=_CriticalErrorCodeEnumTypeWrapper):
"""
@@ -755,12 +945,151 @@ RADIO_SPI_BUG: CriticalErrorCode.ValueType # 11
A (likely software but possibly hardware) failure was detected while trying to send packets.
If this occurs on your board, please post in the forum so that we can ask you to collect some information to allow fixing this bug
"""
FLASH_CORRUPTION_RECOVERABLE: CriticalErrorCode.ValueType # 12
"""
Corruption was detected on the flash filesystem but we were able to repair things.
If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field.
"""
FLASH_CORRUPTION_UNRECOVERABLE: CriticalErrorCode.ValueType # 13
"""
Corruption was detected on the flash filesystem but we were unable to repair things.
NOTE: Your node will probably need to be reconfigured the next time it reboots (it will lose the region code etc...)
If you see this failure in the field please post in the forum because we are interested in seeing if this is occurring in the field.
"""
global___CriticalErrorCode = CriticalErrorCode
class _ExcludedModules:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _ExcludedModulesEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_ExcludedModules.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
EXCLUDED_NONE: _ExcludedModules.ValueType # 0
"""
Default value of 0 indicates no modules are excluded.
"""
MQTT_CONFIG: _ExcludedModules.ValueType # 1
"""
MQTT module
"""
SERIAL_CONFIG: _ExcludedModules.ValueType # 2
"""
Serial module
"""
EXTNOTIF_CONFIG: _ExcludedModules.ValueType # 4
"""
External Notification module
"""
STOREFORWARD_CONFIG: _ExcludedModules.ValueType # 8
"""
Store and Forward module
"""
RANGETEST_CONFIG: _ExcludedModules.ValueType # 16
"""
Range Test module
"""
TELEMETRY_CONFIG: _ExcludedModules.ValueType # 32
"""
Telemetry module
"""
CANNEDMSG_CONFIG: _ExcludedModules.ValueType # 64
"""
Canned Message module
"""
AUDIO_CONFIG: _ExcludedModules.ValueType # 128
"""
Audio module
"""
REMOTEHARDWARE_CONFIG: _ExcludedModules.ValueType # 256
"""
Remote Hardware module
"""
NEIGHBORINFO_CONFIG: _ExcludedModules.ValueType # 512
"""
Neighbor Info module
"""
AMBIENTLIGHTING_CONFIG: _ExcludedModules.ValueType # 1024
"""
Ambient Lighting module
"""
DETECTIONSENSOR_CONFIG: _ExcludedModules.ValueType # 2048
"""
Detection Sensor module
"""
PAXCOUNTER_CONFIG: _ExcludedModules.ValueType # 4096
"""
Paxcounter module
"""
class ExcludedModules(_ExcludedModules, metaclass=_ExcludedModulesEnumTypeWrapper):
"""
Enum for modules excluded from a device's configuration.
Each value represents a ModuleConfigType that can be toggled as excluded
by setting its corresponding bit in the `excluded_modules` bitmask field.
"""
EXCLUDED_NONE: ExcludedModules.ValueType # 0
"""
Default value of 0 indicates no modules are excluded.
"""
MQTT_CONFIG: ExcludedModules.ValueType # 1
"""
MQTT module
"""
SERIAL_CONFIG: ExcludedModules.ValueType # 2
"""
Serial module
"""
EXTNOTIF_CONFIG: ExcludedModules.ValueType # 4
"""
External Notification module
"""
STOREFORWARD_CONFIG: ExcludedModules.ValueType # 8
"""
Store and Forward module
"""
RANGETEST_CONFIG: ExcludedModules.ValueType # 16
"""
Range Test module
"""
TELEMETRY_CONFIG: ExcludedModules.ValueType # 32
"""
Telemetry module
"""
CANNEDMSG_CONFIG: ExcludedModules.ValueType # 64
"""
Canned Message module
"""
AUDIO_CONFIG: ExcludedModules.ValueType # 128
"""
Audio module
"""
REMOTEHARDWARE_CONFIG: ExcludedModules.ValueType # 256
"""
Remote Hardware module
"""
NEIGHBORINFO_CONFIG: ExcludedModules.ValueType # 512
"""
Neighbor Info module
"""
AMBIENTLIGHTING_CONFIG: ExcludedModules.ValueType # 1024
"""
Ambient Lighting module
"""
DETECTIONSENSOR_CONFIG: ExcludedModules.ValueType # 2048
"""
Detection Sensor module
"""
PAXCOUNTER_CONFIG: ExcludedModules.ValueType # 4096
"""
Paxcounter module
"""
global___ExcludedModules = ExcludedModules
@typing.final
class Position(google.protobuf.message.Message):
"""
a gps position
A GPS Position
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
@@ -1001,22 +1330,22 @@ class Position(google.protobuf.message.Message):
def __init__(
self,
*,
latitude_i: builtins.int = ...,
longitude_i: builtins.int = ...,
altitude: builtins.int = ...,
latitude_i: builtins.int | None = ...,
longitude_i: builtins.int | None = ...,
altitude: builtins.int | None = ...,
time: builtins.int = ...,
location_source: global___Position.LocSource.ValueType = ...,
altitude_source: global___Position.AltSource.ValueType = ...,
timestamp: builtins.int = ...,
timestamp_millis_adjust: builtins.int = ...,
altitude_hae: builtins.int = ...,
altitude_geoidal_separation: builtins.int = ...,
altitude_hae: builtins.int | None = ...,
altitude_geoidal_separation: builtins.int | None = ...,
PDOP: builtins.int = ...,
HDOP: builtins.int = ...,
VDOP: builtins.int = ...,
gps_accuracy: builtins.int = ...,
ground_speed: builtins.int = ...,
ground_track: builtins.int = ...,
ground_speed: builtins.int | None = ...,
ground_track: builtins.int | None = ...,
fix_quality: builtins.int = ...,
fix_type: builtins.int = ...,
sats_in_view: builtins.int = ...,
@@ -1025,7 +1354,22 @@ class Position(google.protobuf.message.Message):
seq_number: builtins.int = ...,
precision_bits: builtins.int = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["HDOP", b"HDOP", "PDOP", b"PDOP", "VDOP", b"VDOP", "altitude", b"altitude", "altitude_geoidal_separation", b"altitude_geoidal_separation", "altitude_hae", b"altitude_hae", "altitude_source", b"altitude_source", "fix_quality", b"fix_quality", "fix_type", b"fix_type", "gps_accuracy", b"gps_accuracy", "ground_speed", b"ground_speed", "ground_track", b"ground_track", "latitude_i", b"latitude_i", "location_source", b"location_source", "longitude_i", b"longitude_i", "next_update", b"next_update", "precision_bits", b"precision_bits", "sats_in_view", b"sats_in_view", "sensor_id", b"sensor_id", "seq_number", b"seq_number", "time", b"time", "timestamp", b"timestamp", "timestamp_millis_adjust", b"timestamp_millis_adjust"]) -> None: ...
def HasField(self, field_name: typing.Literal["_altitude", b"_altitude", "_altitude_geoidal_separation", b"_altitude_geoidal_separation", "_altitude_hae", b"_altitude_hae", "_ground_speed", b"_ground_speed", "_ground_track", b"_ground_track", "_latitude_i", b"_latitude_i", "_longitude_i", b"_longitude_i", "altitude", b"altitude", "altitude_geoidal_separation", b"altitude_geoidal_separation", "altitude_hae", b"altitude_hae", "ground_speed", b"ground_speed", "ground_track", b"ground_track", "latitude_i", b"latitude_i", "longitude_i", b"longitude_i"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["HDOP", b"HDOP", "PDOP", b"PDOP", "VDOP", b"VDOP", "_altitude", b"_altitude", "_altitude_geoidal_separation", b"_altitude_geoidal_separation", "_altitude_hae", b"_altitude_hae", "_ground_speed", b"_ground_speed", "_ground_track", b"_ground_track", "_latitude_i", b"_latitude_i", "_longitude_i", b"_longitude_i", "altitude", b"altitude", "altitude_geoidal_separation", b"altitude_geoidal_separation", "altitude_hae", b"altitude_hae", "altitude_source", b"altitude_source", "fix_quality", b"fix_quality", "fix_type", b"fix_type", "gps_accuracy", b"gps_accuracy", "ground_speed", b"ground_speed", "ground_track", b"ground_track", "latitude_i", b"latitude_i", "location_source", b"location_source", "longitude_i", b"longitude_i", "next_update", b"next_update", "precision_bits", b"precision_bits", "sats_in_view", b"sats_in_view", "sensor_id", b"sensor_id", "seq_number", b"seq_number", "time", b"time", "timestamp", b"timestamp", "timestamp_millis_adjust", b"timestamp_millis_adjust"]) -> None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_altitude", b"_altitude"]) -> typing.Literal["altitude"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_altitude_geoidal_separation", b"_altitude_geoidal_separation"]) -> typing.Literal["altitude_geoidal_separation"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_altitude_hae", b"_altitude_hae"]) -> typing.Literal["altitude_hae"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_ground_speed", b"_ground_speed"]) -> typing.Literal["ground_speed"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_ground_track", b"_ground_track"]) -> typing.Literal["ground_track"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_latitude_i", b"_latitude_i"]) -> typing.Literal["latitude_i"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_longitude_i", b"_longitude_i"]) -> typing.Literal["longitude_i"] | None: ...
global___Position = Position
@@ -1063,6 +1407,7 @@ class User(google.protobuf.message.Message):
HW_MODEL_FIELD_NUMBER: builtins.int
IS_LICENSED_FIELD_NUMBER: builtins.int
ROLE_FIELD_NUMBER: builtins.int
PUBLIC_KEY_FIELD_NUMBER: builtins.int
id: builtins.str
"""
A globally unique ID string for this user.
@@ -1102,6 +1447,11 @@ class User(google.protobuf.message.Message):
"""
Indicates that the user's role in the mesh
"""
public_key: builtins.bytes
"""
The public key of the user's device.
This is sent out to other nodes on the mesh to allow them to compute a shared secret key.
"""
def __init__(
self,
*,
@@ -1112,32 +1462,57 @@ class User(google.protobuf.message.Message):
hw_model: global___HardwareModel.ValueType = ...,
is_licensed: builtins.bool = ...,
role: meshtastic.protobuf.config_pb2.Config.DeviceConfig.Role.ValueType = ...,
public_key: builtins.bytes = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["hw_model", b"hw_model", "id", b"id", "is_licensed", b"is_licensed", "long_name", b"long_name", "macaddr", b"macaddr", "role", b"role", "short_name", b"short_name"]) -> None: ...
def ClearField(self, field_name: typing.Literal["hw_model", b"hw_model", "id", b"id", "is_licensed", b"is_licensed", "long_name", b"long_name", "macaddr", b"macaddr", "public_key", b"public_key", "role", b"role", "short_name", b"short_name"]) -> None: ...
global___User = User
@typing.final
class RouteDiscovery(google.protobuf.message.Message):
"""
A message used in our Dynamic Source Routing protocol (RFC 4728 based)
A message used in a traceroute
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
ROUTE_FIELD_NUMBER: builtins.int
SNR_TOWARDS_FIELD_NUMBER: builtins.int
ROUTE_BACK_FIELD_NUMBER: builtins.int
SNR_BACK_FIELD_NUMBER: builtins.int
@property
def route(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
"""
The list of nodenums this packet has visited so far
The list of nodenums this packet has visited so far to the destination.
"""
@property
def snr_towards(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
"""
The list of SNRs (in dB, scaled by 4) in the route towards the destination.
"""
@property
def route_back(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
"""
The list of nodenums the packet has visited on the way back from the destination.
"""
@property
def snr_back(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]:
"""
The list of SNRs (in dB, scaled by 4) in the route back from the destination.
"""
def __init__(
self,
*,
route: collections.abc.Iterable[builtins.int] | None = ...,
snr_towards: collections.abc.Iterable[builtins.int] | None = ...,
route_back: collections.abc.Iterable[builtins.int] | None = ...,
snr_back: collections.abc.Iterable[builtins.int] | None = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["route", b"route"]) -> None: ...
def ClearField(self, field_name: typing.Literal["route", b"route", "route_back", b"route_back", "snr_back", b"snr_back", "snr_towards", b"snr_towards"]) -> None: ...
global___RouteDiscovery = RouteDiscovery
@@ -1205,6 +1580,22 @@ class Routing(google.protobuf.message.Message):
The application layer service on the remote node received your request, but considered your request not authorized
(i.e you did not send the request on the required bound channel)
"""
PKI_FAILED: Routing._Error.ValueType # 34
"""
The client specified a PKI transport, but the node was unable to send the packet using PKI (and did not send the message at all)
"""
PKI_UNKNOWN_PUBKEY: Routing._Error.ValueType # 35
"""
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):
"""
@@ -1262,6 +1653,22 @@ class Routing(google.protobuf.message.Message):
The application layer service on the remote node received your request, but considered your request not authorized
(i.e you did not send the request on the required bound channel)
"""
PKI_FAILED: Routing.Error.ValueType # 34
"""
The client specified a PKI transport, but the node was unable to send the packet using PKI (and did not send the message at all)
"""
PKI_UNKNOWN_PUBKEY: Routing.Error.ValueType # 35
"""
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_REPLY_FIELD_NUMBER: builtins.int
@@ -1314,6 +1721,7 @@ class Data(google.protobuf.message.Message):
REQUEST_ID_FIELD_NUMBER: builtins.int
REPLY_ID_FIELD_NUMBER: builtins.int
EMOJI_FIELD_NUMBER: builtins.int
BITFIELD_FIELD_NUMBER: builtins.int
portnum: meshtastic.protobuf.portnums_pb2.PortNum.ValueType
"""
Formerly named typ and of type Type
@@ -1356,6 +1764,10 @@ class Data(google.protobuf.message.Message):
Defaults to false. If true, then what is in the payload should be treated as an emoji like giving
a message a heart or poop emoji.
"""
bitfield: builtins.int
"""
Bitfield for extra flags. First use is to indicate that user approves the packet being uploaded to MQTT.
"""
def __init__(
self,
*,
@@ -1367,8 +1779,11 @@ class Data(google.protobuf.message.Message):
request_id: builtins.int = ...,
reply_id: builtins.int = ...,
emoji: builtins.int = ...,
bitfield: builtins.int | None = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["dest", b"dest", "emoji", b"emoji", "payload", b"payload", "portnum", b"portnum", "reply_id", b"reply_id", "request_id", b"request_id", "source", b"source", "want_response", b"want_response"]) -> None: ...
def HasField(self, field_name: typing.Literal["_bitfield", b"_bitfield", "bitfield", b"bitfield"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_bitfield", b"_bitfield", "bitfield", b"bitfield", "dest", b"dest", "emoji", b"emoji", "payload", b"payload", "portnum", b"portnum", "reply_id", b"reply_id", "request_id", b"request_id", "source", b"source", "want_response", b"want_response"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["_bitfield", b"_bitfield"]) -> typing.Literal["bitfield"] | None: ...
global___Data = Data
@@ -1425,15 +1840,20 @@ class Waypoint(google.protobuf.message.Message):
self,
*,
id: builtins.int = ...,
latitude_i: builtins.int = ...,
longitude_i: builtins.int = ...,
latitude_i: builtins.int | None = ...,
longitude_i: builtins.int | None = ...,
expire: builtins.int = ...,
locked_to: builtins.int = ...,
name: builtins.str = ...,
description: builtins.str = ...,
icon: builtins.int = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["description", b"description", "expire", b"expire", "icon", b"icon", "id", b"id", "latitude_i", b"latitude_i", "locked_to", b"locked_to", "longitude_i", b"longitude_i", "name", b"name"]) -> None: ...
def HasField(self, field_name: typing.Literal["_latitude_i", b"_latitude_i", "_longitude_i", b"_longitude_i", "latitude_i", b"latitude_i", "longitude_i", b"longitude_i"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_latitude_i", b"_latitude_i", "_longitude_i", b"_longitude_i", "description", b"description", "expire", b"expire", "icon", b"icon", "id", b"id", "latitude_i", b"latitude_i", "locked_to", b"locked_to", "longitude_i", b"longitude_i", "name", b"name"]) -> None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_latitude_i", b"_latitude_i"]) -> typing.Literal["latitude_i"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_longitude_i", b"_longitude_i"]) -> typing.Literal["longitude_i"] | None: ...
global___Waypoint = Waypoint
@@ -1517,6 +1937,19 @@ class MeshPacket(google.protobuf.message.Message):
If priority is unset but the message is marked as want_ack,
assume it is important and use a slightly higher priority
"""
RESPONSE: MeshPacket._Priority.ValueType # 80
"""
If priority is unset but the packet is a response to a request, we want it to get there relatively quickly.
Furthermore, responses stop relaying packets directed to a node early.
"""
HIGH: MeshPacket._Priority.ValueType # 100
"""
Higher priority for specific message types (portnums) to distinguish between other reliable packets.
"""
ALERT: MeshPacket._Priority.ValueType # 110
"""
Higher priority alert message used for critical alerts which take priority over other reliable packets.
"""
ACK: MeshPacket._Priority.ValueType # 120
"""
Ack/naks are sent with very high priority to ensure that retransmission
@@ -1571,6 +2004,19 @@ class MeshPacket(google.protobuf.message.Message):
If priority is unset but the message is marked as want_ack,
assume it is important and use a slightly higher priority
"""
RESPONSE: MeshPacket.Priority.ValueType # 80
"""
If priority is unset but the packet is a response to a request, we want it to get there relatively quickly.
Furthermore, responses stop relaying packets directed to a node early.
"""
HIGH: MeshPacket.Priority.ValueType # 100
"""
Higher priority for specific message types (portnums) to distinguish between other reliable packets.
"""
ALERT: MeshPacket.Priority.ValueType # 110
"""
Higher priority alert message used for critical alerts which take priority over other reliable packets.
"""
ACK: MeshPacket.Priority.ValueType # 120
"""
Ack/naks are sent with very high priority to ensure that retransmission
@@ -1633,6 +2079,11 @@ class MeshPacket(google.protobuf.message.Message):
DELAYED_FIELD_NUMBER: builtins.int
VIA_MQTT_FIELD_NUMBER: builtins.int
HOP_START_FIELD_NUMBER: builtins.int
PUBLIC_KEY_FIELD_NUMBER: builtins.int
PKI_ENCRYPTED_FIELD_NUMBER: builtins.int
NEXT_HOP_FIELD_NUMBER: builtins.int
RELAY_NODE_FIELD_NUMBER: builtins.int
TX_AFTER_FIELD_NUMBER: builtins.int
to: builtins.int
"""
The (immediate) destination for this packet
@@ -1677,7 +2128,7 @@ class MeshPacket(google.protobuf.message.Message):
"""
hop_limit: builtins.int
"""
If unset treated as zero (no forwarding, send to adjacent nodes only)
If unset treated as zero (no forwarding, send to direct neighbor nodes only)
if 1, allow hopping through one node, etc...
For our usecase real world topologies probably have a max of about 3.
This field is normally placed into a few of bits in the header.
@@ -1716,6 +2167,30 @@ class MeshPacket(google.protobuf.message.Message):
Hop limit with which the original packet started. Sent via LoRa using three bits in the unencrypted header.
When receiving a packet, the difference between hop_start and hop_limit gives how many hops it traveled.
"""
public_key: builtins.bytes
"""
Records the public key the packet was encrypted with, if applicable.
"""
pki_encrypted: builtins.bool
"""
Indicates whether the packet was en/decrypted using PKI
"""
next_hop: builtins.int
"""
Last byte of the node number of the node that should be used as the next hop in routing.
Set by the firmware internally, clients are not supposed to set this.
"""
relay_node: builtins.int
"""
Last byte of the node number of the node that will relay/relayed this packet.
Set by the firmware internally, clients are not supposed to set this.
"""
tx_after: builtins.int
"""
*Never* sent over the radio links.
Timestamp after which this packet may be sent.
Set by the firmware internally, clients are not supposed to set this.
"""
@property
def decoded(self) -> global___Data:
"""
@@ -1739,9 +2214,14 @@ class MeshPacket(google.protobuf.message.Message):
delayed: global___MeshPacket.Delayed.ValueType = ...,
via_mqtt: builtins.bool = ...,
hop_start: builtins.int = ...,
public_key: builtins.bytes = ...,
pki_encrypted: builtins.bool = ...,
next_hop: builtins.int = ...,
relay_node: builtins.int = ...,
tx_after: builtins.int = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["decoded", b"decoded", "encrypted", b"encrypted", "payload_variant", b"payload_variant"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["channel", b"channel", "decoded", b"decoded", "delayed", b"delayed", "encrypted", b"encrypted", "from", b"from", "hop_limit", b"hop_limit", "hop_start", b"hop_start", "id", b"id", "payload_variant", b"payload_variant", "priority", b"priority", "rx_rssi", b"rx_rssi", "rx_snr", b"rx_snr", "rx_time", b"rx_time", "to", b"to", "via_mqtt", b"via_mqtt", "want_ack", b"want_ack"]) -> None: ...
def ClearField(self, field_name: typing.Literal["channel", b"channel", "decoded", b"decoded", "delayed", b"delayed", "encrypted", b"encrypted", "from", b"from", "hop_limit", b"hop_limit", "hop_start", b"hop_start", "id", b"id", "next_hop", b"next_hop", "payload_variant", b"payload_variant", "pki_encrypted", b"pki_encrypted", "priority", b"priority", "public_key", b"public_key", "relay_node", b"relay_node", "rx_rssi", b"rx_rssi", "rx_snr", b"rx_snr", "rx_time", b"rx_time", "to", b"to", "tx_after", b"tx_after", "via_mqtt", b"via_mqtt", "want_ack", b"want_ack"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["decoded", "encrypted"] | None: ...
global___MeshPacket = MeshPacket
@@ -1779,6 +2259,7 @@ class NodeInfo(google.protobuf.message.Message):
VIA_MQTT_FIELD_NUMBER: builtins.int
HOPS_AWAY_FIELD_NUMBER: builtins.int
IS_FAVORITE_FIELD_NUMBER: builtins.int
IS_IGNORED_FIELD_NUMBER: builtins.int
num: builtins.int
"""
The node number
@@ -1792,7 +2273,7 @@ class NodeInfo(google.protobuf.message.Message):
"""
TODO: REMOVE/INTEGRATE
Not currently used (till full DSR deployment?) Our current preferred node node for routing - might be the same as num if
we are adjacent Or zero if we don't yet know a route to this node.
we are direct neighbor or zero if we don't yet know a route to this node.
fixed32 next_hop = 5;
@@ -1808,13 +2289,18 @@ class NodeInfo(google.protobuf.message.Message):
"""
hops_away: builtins.int
"""
Number of hops away from us this node is (0 if adjacent)
Number of hops away from us this node is (0 if direct neighbor)
"""
is_favorite: builtins.bool
"""
True if node is in our favorites list
Persists between NodeDB internal clean ups
"""
is_ignored: builtins.bool
"""
True if node is in our ignored list
Persists between NodeDB internal clean ups
"""
@property
def user(self) -> global___User:
"""
@@ -1845,11 +2331,13 @@ class NodeInfo(google.protobuf.message.Message):
device_metrics: meshtastic.protobuf.telemetry_pb2.DeviceMetrics | None = ...,
channel: builtins.int = ...,
via_mqtt: builtins.bool = ...,
hops_away: builtins.int = ...,
hops_away: builtins.int | None = ...,
is_favorite: builtins.bool = ...,
is_ignored: builtins.bool = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["device_metrics", b"device_metrics", "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 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["_hops_away", b"_hops_away", "channel", b"channel", "device_metrics", b"device_metrics", "hops_away", b"hops_away", "is_favorite", b"is_favorite", "is_ignored", b"is_ignored", "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
@@ -1866,6 +2354,8 @@ class MyNodeInfo(google.protobuf.message.Message):
MY_NODE_NUM_FIELD_NUMBER: builtins.int
REBOOT_COUNT_FIELD_NUMBER: builtins.int
MIN_APP_VERSION_FIELD_NUMBER: builtins.int
DEVICE_ID_FIELD_NUMBER: builtins.int
PIO_ENV_FIELD_NUMBER: builtins.int
my_node_num: builtins.int
"""
Tells the phone what our node number is, default starting value is
@@ -1881,14 +2371,24 @@ class MyNodeInfo(google.protobuf.message.Message):
The minimum app version that can talk to this device.
Phone/PC apps should compare this to their build number and if too low tell the user they must update their app
"""
device_id: builtins.bytes
"""
Unique hardware identifier for this device
"""
pio_env: builtins.str
"""
The PlatformIO environment used to build this firmware
"""
def __init__(
self,
*,
my_node_num: builtins.int = ...,
reboot_count: builtins.int = ...,
min_app_version: builtins.int = ...,
device_id: builtins.bytes = ...,
pio_env: builtins.str = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["min_app_version", b"min_app_version", "my_node_num", b"my_node_num", "reboot_count", b"reboot_count"]) -> None: ...
def ClearField(self, field_name: typing.Literal["device_id", b"device_id", "min_app_version", b"min_app_version", "my_node_num", b"my_node_num", "pio_env", b"pio_env", "reboot_count", b"reboot_count"]) -> None: ...
global___MyNodeInfo = MyNodeInfo
@@ -2058,6 +2558,9 @@ class FromRadio(google.protobuf.message.Message):
XMODEMPACKET_FIELD_NUMBER: builtins.int
METADATA_FIELD_NUMBER: builtins.int
MQTTCLIENTPROXYMESSAGE_FIELD_NUMBER: builtins.int
FILEINFO_FIELD_NUMBER: builtins.int
CLIENTNOTIFICATION_FIELD_NUMBER: builtins.int
DEVICEUICONFIG_FIELD_NUMBER: builtins.int
id: builtins.int
"""
The packet id, used to allow the phone to request missing read packets from the FIFO,
@@ -2145,6 +2648,24 @@ class FromRadio(google.protobuf.message.Message):
MQTT Client Proxy Message (device sending to client / phone for publishing to MQTT)
"""
@property
def fileInfo(self) -> global___FileInfo:
"""
File system manifest messages
"""
@property
def clientNotification(self) -> global___ClientNotification:
"""
Notification message to the client
"""
@property
def deviceuiConfig(self) -> meshtastic.protobuf.device_ui_pb2.DeviceUIConfig:
"""
Persistent data for device-ui
"""
def __init__(
self,
*,
@@ -2162,13 +2683,89 @@ class FromRadio(google.protobuf.message.Message):
xmodemPacket: meshtastic.protobuf.xmodem_pb2.XModem | None = ...,
metadata: global___DeviceMetadata | None = ...,
mqttClientProxyMessage: global___MqttClientProxyMessage | None = ...,
fileInfo: global___FileInfo | None = ...,
clientNotification: global___ClientNotification | None = ...,
deviceuiConfig: meshtastic.protobuf.device_ui_pb2.DeviceUIConfig | None = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["channel", b"channel", "config", b"config", "config_complete_id", b"config_complete_id", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "xmodemPacket", b"xmodemPacket"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["channel", b"channel", "config", b"config", "config_complete_id", b"config_complete_id", "id", b"id", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "xmodemPacket", b"xmodemPacket"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["packet", "my_info", "node_info", "config", "log_record", "config_complete_id", "rebooted", "moduleConfig", "channel", "queueStatus", "xmodemPacket", "metadata", "mqttClientProxyMessage"] | None: ...
def HasField(self, field_name: typing.Literal["channel", b"channel", "clientNotification", b"clientNotification", "config", b"config", "config_complete_id", b"config_complete_id", "deviceuiConfig", b"deviceuiConfig", "fileInfo", b"fileInfo", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "xmodemPacket", b"xmodemPacket"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["channel", b"channel", "clientNotification", b"clientNotification", "config", b"config", "config_complete_id", b"config_complete_id", "deviceuiConfig", b"deviceuiConfig", "fileInfo", b"fileInfo", "id", b"id", "log_record", b"log_record", "metadata", b"metadata", "moduleConfig", b"moduleConfig", "mqttClientProxyMessage", b"mqttClientProxyMessage", "my_info", b"my_info", "node_info", b"node_info", "packet", b"packet", "payload_variant", b"payload_variant", "queueStatus", b"queueStatus", "rebooted", b"rebooted", "xmodemPacket", b"xmodemPacket"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["payload_variant", b"payload_variant"]) -> typing.Literal["packet", "my_info", "node_info", "config", "log_record", "config_complete_id", "rebooted", "moduleConfig", "channel", "queueStatus", "xmodemPacket", "metadata", "mqttClientProxyMessage", "fileInfo", "clientNotification", "deviceuiConfig"] | None: ...
global___FromRadio = FromRadio
@typing.final
class ClientNotification(google.protobuf.message.Message):
"""
A notification message from the device to the client
To be used for important messages that should to be displayed to the user
in the form of push notifications or validation messages when saving
invalid configuration.
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
REPLY_ID_FIELD_NUMBER: builtins.int
TIME_FIELD_NUMBER: builtins.int
LEVEL_FIELD_NUMBER: builtins.int
MESSAGE_FIELD_NUMBER: builtins.int
reply_id: builtins.int
"""
The id of the packet we're notifying in response to
"""
time: builtins.int
"""
Seconds since 1970 - or 0 for unknown/unset
"""
level: global___LogRecord.Level.ValueType
"""
The level type of notification
"""
message: builtins.str
"""
The message body of the notification
"""
def __init__(
self,
*,
reply_id: builtins.int | None = ...,
time: builtins.int = ...,
level: global___LogRecord.Level.ValueType = ...,
message: builtins.str = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["_reply_id", b"_reply_id", "reply_id", b"reply_id"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_reply_id", b"_reply_id", "level", b"level", "message", b"message", "reply_id", b"reply_id", "time", b"time"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["_reply_id", b"_reply_id"]) -> typing.Literal["reply_id"] | None: ...
global___ClientNotification = ClientNotification
@typing.final
class FileInfo(google.protobuf.message.Message):
"""
Individual File info for the device
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
FILE_NAME_FIELD_NUMBER: builtins.int
SIZE_BYTES_FIELD_NUMBER: builtins.int
file_name: builtins.str
"""
The fully qualified path of the file
"""
size_bytes: builtins.int
"""
The size of the file in bytes
"""
def __init__(
self,
*,
file_name: builtins.str = ...,
size_bytes: builtins.int = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["file_name", b"file_name", "size_bytes", b"size_bytes"]) -> None: ...
global___FileInfo = FileInfo
@typing.final
class ToRadio(google.protobuf.message.Message):
"""
@@ -2371,6 +2968,8 @@ class DeviceMetadata(google.protobuf.message.Message):
POSITION_FLAGS_FIELD_NUMBER: builtins.int
HW_MODEL_FIELD_NUMBER: builtins.int
HASREMOTEHARDWARE_FIELD_NUMBER: builtins.int
HASPKC_FIELD_NUMBER: builtins.int
EXCLUDED_MODULES_FIELD_NUMBER: builtins.int
firmware_version: builtins.str
"""
Device firmware version string
@@ -2411,6 +3010,15 @@ class DeviceMetadata(google.protobuf.message.Message):
"""
Has Remote Hardware enabled
"""
hasPKC: builtins.bool
"""
Has PKC capabilities
"""
excluded_modules: builtins.int
"""
Bit field of boolean for excluded modules
(bitwise OR of ExcludedModules)
"""
def __init__(
self,
*,
@@ -2424,8 +3032,10 @@ class DeviceMetadata(google.protobuf.message.Message):
position_flags: builtins.int = ...,
hw_model: global___HardwareModel.ValueType = ...,
hasRemoteHardware: builtins.bool = ...,
hasPKC: builtins.bool = ...,
excluded_modules: builtins.int = ...,
) -> 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", "excluded_modules", b"excluded_modules", "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

View File

File diff suppressed because one or more lines are too long

View File

@@ -225,6 +225,7 @@ class ModuleConfig(google.protobuf.message.Message):
ENABLED_FIELD_NUMBER: builtins.int
UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
TRANSMIT_OVER_LORA_FIELD_NUMBER: builtins.int
enabled: builtins.bool
"""
Whether the Module is enabled
@@ -232,15 +233,21 @@ class ModuleConfig(google.protobuf.message.Message):
update_interval: builtins.int
"""
Interval in seconds of how often we should try to send our
Neighbor Info to the mesh
Neighbor Info (minimum is 14400, i.e., 4 hours)
"""
transmit_over_lora: builtins.bool
"""
Whether in addition to sending it to MQTT and the PhoneAPI, our NeighborInfo should be transmitted over LoRa.
Note that this is not available on a channel with default key and name.
"""
def __init__(
self,
*,
enabled: builtins.bool = ...,
update_interval: builtins.int = ...,
transmit_over_lora: builtins.bool = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "update_interval", b"update_interval"]) -> None: ...
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "transmit_over_lora", b"transmit_over_lora", "update_interval", b"update_interval"]) -> None: ...
@typing.final
class DetectionSensorConfig(google.protobuf.message.Message):
@@ -250,13 +257,54 @@ class ModuleConfig(google.protobuf.message.Message):
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
MINIMUM_BROADCAST_SECS_FIELD_NUMBER: builtins.int
STATE_BROADCAST_SECS_FIELD_NUMBER: builtins.int
SEND_BELL_FIELD_NUMBER: builtins.int
NAME_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
enabled: builtins.bool
"""
@@ -264,13 +312,15 @@ class ModuleConfig(google.protobuf.message.Message):
"""
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
"""
Interval in seconds of how often we should send a message to the mesh with the current state regardless of changes
When set to 0, only state changes will be broadcasted
Works as a sort of status heartbeat for peace of mind
Interval in seconds of how often we should send a message to the mesh
with the current state regardless of trigger events When set to 0, only
trigger events will be broadcasted Works as a sort of status heartbeat
for peace of mind
"""
send_bell: builtins.bool
"""
@@ -287,10 +337,9 @@ class ModuleConfig(google.protobuf.message.Message):
"""
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)
Otherwise LOW (0)
The type of trigger event to be used
"""
use_pullup: builtins.bool
"""
@@ -306,10 +355,10 @@ class ModuleConfig(google.protobuf.message.Message):
send_bell: builtins.bool = ...,
name: builtins.str = ...,
monitor_pin: builtins.int = ...,
detection_triggered_high: builtins.bool = ...,
detection_trigger_type: global___ModuleConfig.DetectionSensorConfig.TriggerType.ValueType = ...,
use_pullup: builtins.bool = ...,
) -> 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
class AudioConfig(google.protobuf.message.Message):
@@ -503,6 +552,8 @@ class ModuleConfig(google.protobuf.message.Message):
NMEA: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 4
CALTOPO: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 5
"""NMEA messages specifically tailored for CalTopo"""
WS85: ModuleConfig.SerialConfig._Serial_Mode.ValueType # 6
"""Ecowitt WS85 weather station"""
class Serial_Mode(_Serial_Mode, metaclass=_Serial_ModeEnumTypeWrapper):
"""
@@ -516,6 +567,8 @@ class ModuleConfig(google.protobuf.message.Message):
NMEA: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 4
CALTOPO: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 5
"""NMEA messages specifically tailored for CalTopo"""
WS85: ModuleConfig.SerialConfig.Serial_Mode.ValueType # 6
"""Ecowitt WS85 weather station"""
ENABLED_FIELD_NUMBER: builtins.int
ECHO_FIELD_NUMBER: builtins.int
@@ -701,6 +754,7 @@ class ModuleConfig(google.protobuf.message.Message):
RECORDS_FIELD_NUMBER: builtins.int
HISTORY_RETURN_MAX_FIELD_NUMBER: builtins.int
HISTORY_RETURN_WINDOW_FIELD_NUMBER: builtins.int
IS_SERVER_FIELD_NUMBER: builtins.int
enabled: builtins.bool
"""
Enable the Store and Forward Module
@@ -721,6 +775,10 @@ class ModuleConfig(google.protobuf.message.Message):
"""
TODO: REPLACE
"""
is_server: builtins.bool
"""
Set to true to let this node act as a server that stores received messages and resends them upon request.
"""
def __init__(
self,
*,
@@ -729,8 +787,9 @@ class ModuleConfig(google.protobuf.message.Message):
records: builtins.int = ...,
history_return_max: builtins.int = ...,
history_return_window: builtins.int = ...,
is_server: builtins.bool = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "heartbeat", b"heartbeat", "history_return_max", b"history_return_max", "history_return_window", b"history_return_window", "records", b"records"]) -> None: ...
def ClearField(self, field_name: typing.Literal["enabled", b"enabled", "heartbeat", b"heartbeat", "history_return_max", b"history_return_max", "history_return_window", b"history_return_window", "is_server", b"is_server", "records", b"records"]) -> None: ...
@typing.final
class RangeTestConfig(google.protobuf.message.Message):
@@ -783,6 +842,9 @@ class ModuleConfig(google.protobuf.message.Message):
POWER_MEASUREMENT_ENABLED_FIELD_NUMBER: builtins.int
POWER_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
POWER_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
HEALTH_MEASUREMENT_ENABLED_FIELD_NUMBER: builtins.int
HEALTH_UPDATE_INTERVAL_FIELD_NUMBER: builtins.int
HEALTH_SCREEN_ENABLED_FIELD_NUMBER: builtins.int
device_update_interval: builtins.int
"""
Interval in seconds of how often we should try to send our
@@ -818,18 +880,30 @@ class ModuleConfig(google.protobuf.message.Message):
"""
power_measurement_enabled: builtins.bool
"""
Interval in seconds of how often we should try to send our
air quality metrics to the mesh
Enable/disable Power metrics
"""
power_update_interval: builtins.int
"""
Interval in seconds of how often we should try to send our
air quality metrics to the mesh
power metrics to the mesh
"""
power_screen_enabled: builtins.bool
"""
Enable/Disable the power measurement module on-device display
"""
health_measurement_enabled: builtins.bool
"""
Preferences for the (Health) Telemetry Module
Enable/Disable the telemetry measurement module measurement collection
"""
health_update_interval: builtins.int
"""
Interval in seconds of how often we should try to send our
air quality metrics to the mesh
health metrics to the mesh
"""
health_screen_enabled: builtins.bool
"""
Enable/Disable the health telemetry module on-device display
"""
def __init__(
self,
@@ -844,8 +918,11 @@ class ModuleConfig(google.protobuf.message.Message):
power_measurement_enabled: builtins.bool = ...,
power_update_interval: builtins.int = ...,
power_screen_enabled: builtins.bool = ...,
health_measurement_enabled: builtins.bool = ...,
health_update_interval: builtins.int = ...,
health_screen_enabled: builtins.bool = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ...
def ClearField(self, field_name: typing.Literal["air_quality_enabled", b"air_quality_enabled", "air_quality_interval", b"air_quality_interval", "device_update_interval", b"device_update_interval", "environment_display_fahrenheit", b"environment_display_fahrenheit", "environment_measurement_enabled", b"environment_measurement_enabled", "environment_screen_enabled", b"environment_screen_enabled", "environment_update_interval", b"environment_update_interval", "health_measurement_enabled", b"health_measurement_enabled", "health_screen_enabled", b"health_screen_enabled", "health_update_interval", b"health_update_interval", "power_measurement_enabled", b"power_measurement_enabled", "power_screen_enabled", b"power_screen_enabled", "power_update_interval", b"power_update_interval"]) -> None: ...
@typing.final
class CannedMessageConfig(google.protobuf.message.Message):
@@ -982,7 +1059,7 @@ class ModuleConfig(google.protobuf.message.Message):
allow_input_source: builtins.str
"""
Input event origin accepted by the canned message module.
Can be e.g. "rotEnc1", "upDownEnc1" or keyword "_any"
Can be e.g. "rotEnc1", "upDownEnc1", "scanAndSelect", "cardkb", "serialkb", or keyword "_any"
"""
send_bell: builtins.bool
"""

View File

@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\x8d\x04\n\x07PortNum\x12\x0f\n\x0bUNKNOWN_APP\x10\x00\x12\x14\n\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n\x0cPOSITION_APP\x10\x03\x12\x10\n\x0cNODEINFO_APP\x10\x04\x12\x0f\n\x0bROUTING_APP\x10\x05\x12\r\n\tADMIN_APP\x10\x06\x12\x1f\n\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\x07\x12\x10\n\x0cWAYPOINT_APP\x10\x08\x12\r\n\tAUDIO_APP\x10\t\x12\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_APP\x10\"\x12\x0e\n\nSERIAL_APP\x10@\x12\x15\n\x11STORE_FORWARD_APP\x10\x41\x12\x12\n\x0eRANGE_TEST_APP\x10\x42\x12\x11\n\rTELEMETRY_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x11\n\rSIMULATOR_APP\x10\x45\x12\x12\n\x0eTRACEROUTE_APP\x10\x46\x12\x14\n\x10NEIGHBORINFO_APP\x10G\x12\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42]\n\x13\x63om.geeksville.meshB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/portnums.proto\x12\x13meshtastic.protobuf*\xb1\x04\n\x07PortNum\x12\x0f\n\x0bUNKNOWN_APP\x10\x00\x12\x14\n\x10TEXT_MESSAGE_APP\x10\x01\x12\x17\n\x13REMOTE_HARDWARE_APP\x10\x02\x12\x10\n\x0cPOSITION_APP\x10\x03\x12\x10\n\x0cNODEINFO_APP\x10\x04\x12\x0f\n\x0bROUTING_APP\x10\x05\x12\r\n\tADMIN_APP\x10\x06\x12\x1f\n\x1bTEXT_MESSAGE_COMPRESSED_APP\x10\x07\x12\x10\n\x0cWAYPOINT_APP\x10\x08\x12\r\n\tAUDIO_APP\x10\t\x12\x18\n\x14\x44\x45TECTION_SENSOR_APP\x10\n\x12\r\n\tALERT_APP\x10\x0b\x12\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_APP\x10!\x12\x12\n\x0ePAXCOUNTER_APP\x10\"\x12\x0e\n\nSERIAL_APP\x10@\x12\x15\n\x11STORE_FORWARD_APP\x10\x41\x12\x12\n\x0eRANGE_TEST_APP\x10\x42\x12\x11\n\rTELEMETRY_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\x12\x11\n\rSIMULATOR_APP\x10\x45\x12\x12\n\x0eTRACEROUTE_APP\x10\x46\x12\x14\n\x10NEIGHBORINFO_APP\x10G\x12\x0f\n\x0b\x41TAK_PLUGIN\x10H\x12\x12\n\x0eMAP_REPORT_APP\x10I\x12\x13\n\x0fPOWERSTRESS_APP\x10J\x12\x10\n\x0bPRIVATE_APP\x10\x80\x02\x12\x13\n\x0e\x41TAK_FORWARDER\x10\x81\x02\x12\x08\n\x03MAX\x10\xff\x03\x42]\n\x13\x63om.geeksville.meshB\x08PortnumsZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -22,5 +22,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_globals['_PORTNUM']._serialized_start=60
_globals['_PORTNUM']._serialized_end=585
_globals['_PORTNUM']._serialized_end=621
# @@protoc_insertion_point(module_scope)

View File

@@ -93,6 +93,10 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
Same as Text Message but originating from Detection Sensor Module.
NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9
"""
ALERT_APP: _PortNum.ValueType # 11
"""
Same as Text Message but used for critical alerts.
"""
REPLY_APP: _PortNum.ValueType # 32
"""
Provides a 'ping' service that replies to any packet it receives.
@@ -154,7 +158,7 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
TRACEROUTE_APP: _PortNum.ValueType # 70
"""
Provides a traceroute functionality to show the route a packet towards
a certain destination would take on the mesh.
a certain destination would take on the mesh. Contains a RouteDiscovery message as payload.
ENCODING: Protobuf
"""
NEIGHBORINFO_APP: _PortNum.ValueType # 71
@@ -171,6 +175,10 @@ class _PortNumEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTy
"""
Provides unencrypted information about a node for consumption by a map via MQTT
"""
POWERSTRESS_APP: _PortNum.ValueType # 74
"""
PowerStress based monitoring support (for automated power consumption testing)
"""
PRIVATE_APP: _PortNum.ValueType # 256
"""
Private applications should use portnums >= 256.
@@ -274,6 +282,10 @@ DETECTION_SENSOR_APP: PortNum.ValueType # 10
Same as Text Message but originating from Detection Sensor Module.
NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9
"""
ALERT_APP: PortNum.ValueType # 11
"""
Same as Text Message but used for critical alerts.
"""
REPLY_APP: PortNum.ValueType # 32
"""
Provides a 'ping' service that replies to any packet it receives.
@@ -335,7 +347,7 @@ ENCODING: Protobuf (?)
TRACEROUTE_APP: PortNum.ValueType # 70
"""
Provides a traceroute functionality to show the route a packet towards
a certain destination would take on the mesh.
a certain destination would take on the mesh. Contains a RouteDiscovery message as payload.
ENCODING: Protobuf
"""
NEIGHBORINFO_APP: PortNum.ValueType # 71
@@ -352,6 +364,10 @@ MAP_REPORT_APP: PortNum.ValueType # 73
"""
Provides unencrypted information about a node for consumption by a map via MQTT
"""
POWERSTRESS_APP: PortNum.ValueType # 74
"""
PowerStress based monitoring support (for automated power consumption testing)
"""
PRIVATE_APP: PortNum.ValueType # 256
"""
Private applications should use portnums >= 256.

View File

@@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/powermon.proto\x12\x13meshtastic.protobuf\"\xe0\x01\n\x08PowerMon\"\xd3\x01\n\x05State\x12\x08\n\x04None\x10\x00\x12\x11\n\rCPU_DeepSleep\x10\x01\x12\x12\n\x0e\x43PU_LightSleep\x10\x02\x12\x0c\n\x08Vext1_On\x10\x04\x12\r\n\tLora_RXOn\x10\x08\x12\r\n\tLora_TXOn\x10\x10\x12\x11\n\rLora_RXActive\x10 \x12\t\n\x05\x42T_On\x10@\x12\x0b\n\x06LED_On\x10\x80\x01\x12\x0e\n\tScreen_On\x10\x80\x02\x12\x13\n\x0eScreen_Drawing\x10\x80\x04\x12\x0c\n\x07Wifi_On\x10\x80\x08\x12\x0f\n\nGPS_Active\x10\x80\x10\x42\x63\n\x13\x63om.geeksville.meshB\x0ePowerMonProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/protobuf/powermon.proto\x12\x13meshtastic.protobuf\"\xe0\x01\n\x08PowerMon\"\xd3\x01\n\x05State\x12\x08\n\x04None\x10\x00\x12\x11\n\rCPU_DeepSleep\x10\x01\x12\x12\n\x0e\x43PU_LightSleep\x10\x02\x12\x0c\n\x08Vext1_On\x10\x04\x12\r\n\tLora_RXOn\x10\x08\x12\r\n\tLora_TXOn\x10\x10\x12\x11\n\rLora_RXActive\x10 \x12\t\n\x05\x42T_On\x10@\x12\x0b\n\x06LED_On\x10\x80\x01\x12\x0e\n\tScreen_On\x10\x80\x02\x12\x13\n\x0eScreen_Drawing\x10\x80\x04\x12\x0c\n\x07Wifi_On\x10\x80\x08\x12\x0f\n\nGPS_Active\x10\x80\x10\"\x88\x03\n\x12PowerStressMessage\x12;\n\x03\x63md\x18\x01 \x01(\x0e\x32..meshtastic.protobuf.PowerStressMessage.Opcode\x12\x13\n\x0bnum_seconds\x18\x02 \x01(\x02\"\x9f\x02\n\x06Opcode\x12\t\n\x05UNSET\x10\x00\x12\x0e\n\nPRINT_INFO\x10\x01\x12\x0f\n\x0b\x46ORCE_QUIET\x10\x02\x12\r\n\tEND_QUIET\x10\x03\x12\r\n\tSCREEN_ON\x10\x10\x12\x0e\n\nSCREEN_OFF\x10\x11\x12\x0c\n\x08\x43PU_IDLE\x10 \x12\x11\n\rCPU_DEEPSLEEP\x10!\x12\x0e\n\nCPU_FULLON\x10\"\x12\n\n\x06LED_ON\x10\x30\x12\x0b\n\x07LED_OFF\x10\x31\x12\x0c\n\x08LORA_OFF\x10@\x12\x0b\n\x07LORA_TX\x10\x41\x12\x0b\n\x07LORA_RX\x10\x42\x12\n\n\x06\x42T_OFF\x10P\x12\t\n\x05\x42T_ON\x10Q\x12\x0c\n\x08WIFI_OFF\x10`\x12\x0b\n\x07WIFI_ON\x10\x61\x12\x0b\n\x07GPS_OFF\x10p\x12\n\n\x06GPS_ON\x10qBc\n\x13\x63om.geeksville.meshB\x0ePowerMonProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
@@ -25,4 +25,8 @@ if _descriptor._USE_C_DESCRIPTORS == False:
_globals['_POWERMON']._serialized_end=284
_globals['_POWERMON_STATE']._serialized_start=73
_globals['_POWERMON_STATE']._serialized_end=284
_globals['_POWERSTRESSMESSAGE']._serialized_start=287
_globals['_POWERSTRESSMESSAGE']._serialized_end=679
_globals['_POWERSTRESSMESSAGE_OPCODE']._serialized_start=392
_globals['_POWERSTRESSMESSAGE_OPCODE']._serialized_end=679
# @@protoc_insertion_point(module_scope)

View File

@@ -95,3 +95,127 @@ class PowerMon(google.protobuf.message.Message):
) -> None: ...
global___PowerMon = PowerMon
@typing.final
class PowerStressMessage(google.protobuf.message.Message):
"""
PowerStress testing support via the C++ PowerStress module
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
class _Opcode:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _OpcodeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[PowerStressMessage._Opcode.ValueType], builtins.type):
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
UNSET: PowerStressMessage._Opcode.ValueType # 0
"""
Unset/unused
"""
PRINT_INFO: PowerStressMessage._Opcode.ValueType # 1
"""Print board version slog and send an ack that we are alive and ready to process commands"""
FORCE_QUIET: PowerStressMessage._Opcode.ValueType # 2
"""Try to turn off all automatic processing of packets, screen, sleeping, etc (to make it easier to measure in isolation)"""
END_QUIET: PowerStressMessage._Opcode.ValueType # 3
"""Stop powerstress processing - probably by just rebooting the board"""
SCREEN_ON: PowerStressMessage._Opcode.ValueType # 16
"""Turn the screen on"""
SCREEN_OFF: PowerStressMessage._Opcode.ValueType # 17
"""Turn the screen off"""
CPU_IDLE: PowerStressMessage._Opcode.ValueType # 32
"""Let the CPU run but we assume mostly idling for num_seconds"""
CPU_DEEPSLEEP: PowerStressMessage._Opcode.ValueType # 33
"""Force deep sleep for FIXME seconds"""
CPU_FULLON: PowerStressMessage._Opcode.ValueType # 34
"""Spin the CPU as fast as possible for num_seconds"""
LED_ON: PowerStressMessage._Opcode.ValueType # 48
"""Turn the LED on for num_seconds (and leave it on - for baseline power measurement purposes)"""
LED_OFF: PowerStressMessage._Opcode.ValueType # 49
"""Force the LED off for num_seconds"""
LORA_OFF: PowerStressMessage._Opcode.ValueType # 64
"""Completely turn off the LORA radio for num_seconds"""
LORA_TX: PowerStressMessage._Opcode.ValueType # 65
"""Send Lora packets for num_seconds"""
LORA_RX: PowerStressMessage._Opcode.ValueType # 66
"""Receive Lora packets for num_seconds (node will be mostly just listening, unless an external agent is helping stress this by sending packets on the current channel)"""
BT_OFF: PowerStressMessage._Opcode.ValueType # 80
"""Turn off the BT radio for num_seconds"""
BT_ON: PowerStressMessage._Opcode.ValueType # 81
"""Turn on the BT radio for num_seconds"""
WIFI_OFF: PowerStressMessage._Opcode.ValueType # 96
"""Turn off the WIFI radio for num_seconds"""
WIFI_ON: PowerStressMessage._Opcode.ValueType # 97
"""Turn on the WIFI radio for num_seconds"""
GPS_OFF: PowerStressMessage._Opcode.ValueType # 112
"""Turn off the GPS radio for num_seconds"""
GPS_ON: PowerStressMessage._Opcode.ValueType # 113
"""Turn on the GPS radio for num_seconds"""
class Opcode(_Opcode, metaclass=_OpcodeEnumTypeWrapper):
"""
What operation would we like the UUT to perform.
note: senders should probably set want_response in their request packets, so that they can know when the state
machine has started processing their request
"""
UNSET: PowerStressMessage.Opcode.ValueType # 0
"""
Unset/unused
"""
PRINT_INFO: PowerStressMessage.Opcode.ValueType # 1
"""Print board version slog and send an ack that we are alive and ready to process commands"""
FORCE_QUIET: PowerStressMessage.Opcode.ValueType # 2
"""Try to turn off all automatic processing of packets, screen, sleeping, etc (to make it easier to measure in isolation)"""
END_QUIET: PowerStressMessage.Opcode.ValueType # 3
"""Stop powerstress processing - probably by just rebooting the board"""
SCREEN_ON: PowerStressMessage.Opcode.ValueType # 16
"""Turn the screen on"""
SCREEN_OFF: PowerStressMessage.Opcode.ValueType # 17
"""Turn the screen off"""
CPU_IDLE: PowerStressMessage.Opcode.ValueType # 32
"""Let the CPU run but we assume mostly idling for num_seconds"""
CPU_DEEPSLEEP: PowerStressMessage.Opcode.ValueType # 33
"""Force deep sleep for FIXME seconds"""
CPU_FULLON: PowerStressMessage.Opcode.ValueType # 34
"""Spin the CPU as fast as possible for num_seconds"""
LED_ON: PowerStressMessage.Opcode.ValueType # 48
"""Turn the LED on for num_seconds (and leave it on - for baseline power measurement purposes)"""
LED_OFF: PowerStressMessage.Opcode.ValueType # 49
"""Force the LED off for num_seconds"""
LORA_OFF: PowerStressMessage.Opcode.ValueType # 64
"""Completely turn off the LORA radio for num_seconds"""
LORA_TX: PowerStressMessage.Opcode.ValueType # 65
"""Send Lora packets for num_seconds"""
LORA_RX: PowerStressMessage.Opcode.ValueType # 66
"""Receive Lora packets for num_seconds (node will be mostly just listening, unless an external agent is helping stress this by sending packets on the current channel)"""
BT_OFF: PowerStressMessage.Opcode.ValueType # 80
"""Turn off the BT radio for num_seconds"""
BT_ON: PowerStressMessage.Opcode.ValueType # 81
"""Turn on the BT radio for num_seconds"""
WIFI_OFF: PowerStressMessage.Opcode.ValueType # 96
"""Turn off the WIFI radio for num_seconds"""
WIFI_ON: PowerStressMessage.Opcode.ValueType # 97
"""Turn on the WIFI radio for num_seconds"""
GPS_OFF: PowerStressMessage.Opcode.ValueType # 112
"""Turn off the GPS radio for num_seconds"""
GPS_ON: PowerStressMessage.Opcode.ValueType # 113
"""Turn on the GPS radio for num_seconds"""
CMD_FIELD_NUMBER: builtins.int
NUM_SECONDS_FIELD_NUMBER: builtins.int
cmd: global___PowerStressMessage.Opcode.ValueType
"""
What type of HardwareMessage is this?
"""
num_seconds: builtins.float
def __init__(
self,
*,
cmd: global___PowerStressMessage.Opcode.ValueType = ...,
num_seconds: builtins.float = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["cmd", b"cmd", "num_seconds", b"num_seconds"]) -> None: ...
global___PowerStressMessage = PowerStressMessage

View File

File diff suppressed because one or more lines are too long

View File

@@ -127,6 +127,42 @@ class _TelemetrySensorTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wra
"""
NAU7802 Scale Chip or compatible
"""
BMP3XX: _TelemetrySensorType.ValueType # 26
"""
BMP3XX High accuracy temperature and pressure
"""
ICM20948: _TelemetrySensorType.ValueType # 27
"""
ICM-20948 9-Axis digital motion processor
"""
MAX17048: _TelemetrySensorType.ValueType # 28
"""
MAX17048 1S lipo battery sensor (voltage, state of charge, time to go)
"""
CUSTOM_SENSOR: _TelemetrySensorType.ValueType # 29
"""
Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor
"""
MAX30102: _TelemetrySensorType.ValueType # 30
"""
MAX30102 Pulse Oximeter and Heart-Rate Sensor
"""
MLX90614: _TelemetrySensorType.ValueType # 31
"""
MLX90614 non-contact IR temperature sensor
"""
SCD4X: _TelemetrySensorType.ValueType # 32
"""
SCD40/SCD41 CO2, humidity, temperature sensor
"""
RADSENS: _TelemetrySensorType.ValueType # 33
"""
ClimateGuard RadSens, radiation, Geiger-Muller Tube
"""
INA226: _TelemetrySensorType.ValueType # 34
"""
High accuracy current and voltage
"""
class TelemetrySensorType(_TelemetrySensorType, metaclass=_TelemetrySensorTypeEnumTypeWrapper):
"""
@@ -237,6 +273,42 @@ NAU7802: TelemetrySensorType.ValueType # 25
"""
NAU7802 Scale Chip or compatible
"""
BMP3XX: TelemetrySensorType.ValueType # 26
"""
BMP3XX High accuracy temperature and pressure
"""
ICM20948: TelemetrySensorType.ValueType # 27
"""
ICM-20948 9-Axis digital motion processor
"""
MAX17048: TelemetrySensorType.ValueType # 28
"""
MAX17048 1S lipo battery sensor (voltage, state of charge, time to go)
"""
CUSTOM_SENSOR: TelemetrySensorType.ValueType # 29
"""
Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor
"""
MAX30102: TelemetrySensorType.ValueType # 30
"""
MAX30102 Pulse Oximeter and Heart-Rate Sensor
"""
MLX90614: TelemetrySensorType.ValueType # 31
"""
MLX90614 non-contact IR temperature sensor
"""
SCD4X: TelemetrySensorType.ValueType # 32
"""
SCD40/SCD41 CO2, humidity, temperature sensor
"""
RADSENS: TelemetrySensorType.ValueType # 33
"""
ClimateGuard RadSens, radiation, Geiger-Muller Tube
"""
INA226: TelemetrySensorType.ValueType # 34
"""
High accuracy current and voltage
"""
global___TelemetrySensorType = TelemetrySensorType
@typing.final
@@ -275,13 +347,24 @@ class DeviceMetrics(google.protobuf.message.Message):
def __init__(
self,
*,
battery_level: builtins.int = ...,
voltage: builtins.float = ...,
channel_utilization: builtins.float = ...,
air_util_tx: builtins.float = ...,
uptime_seconds: builtins.int = ...,
battery_level: builtins.int | None = ...,
voltage: builtins.float | None = ...,
channel_utilization: builtins.float | None = ...,
air_util_tx: builtins.float | None = ...,
uptime_seconds: builtins.int | None = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["air_util_tx", b"air_util_tx", "battery_level", b"battery_level", "channel_utilization", b"channel_utilization", "uptime_seconds", b"uptime_seconds", "voltage", b"voltage"]) -> None: ...
def HasField(self, field_name: typing.Literal["_air_util_tx", b"_air_util_tx", "_battery_level", b"_battery_level", "_channel_utilization", b"_channel_utilization", "_uptime_seconds", b"_uptime_seconds", "_voltage", b"_voltage", "air_util_tx", b"air_util_tx", "battery_level", b"battery_level", "channel_utilization", b"channel_utilization", "uptime_seconds", b"uptime_seconds", "voltage", b"voltage"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_air_util_tx", b"_air_util_tx", "_battery_level", b"_battery_level", "_channel_utilization", b"_channel_utilization", "_uptime_seconds", b"_uptime_seconds", "_voltage", b"_voltage", "air_util_tx", b"air_util_tx", "battery_level", b"battery_level", "channel_utilization", b"channel_utilization", "uptime_seconds", b"uptime_seconds", "voltage", b"voltage"]) -> None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_air_util_tx", b"_air_util_tx"]) -> typing.Literal["air_util_tx"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_battery_level", b"_battery_level"]) -> typing.Literal["battery_level"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_channel_utilization", b"_channel_utilization"]) -> typing.Literal["channel_utilization"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_uptime_seconds", b"_uptime_seconds"]) -> typing.Literal["uptime_seconds"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_voltage", b"_voltage"]) -> typing.Literal["voltage"] | None: ...
global___DeviceMetrics = DeviceMetrics
@@ -308,6 +391,9 @@ class EnvironmentMetrics(google.protobuf.message.Message):
WIND_DIRECTION_FIELD_NUMBER: builtins.int
WIND_SPEED_FIELD_NUMBER: builtins.int
WEIGHT_FIELD_NUMBER: builtins.int
WIND_GUST_FIELD_NUMBER: builtins.int
WIND_LULL_FIELD_NUMBER: builtins.int
RADIATION_FIELD_NUMBER: builtins.int
temperature: builtins.float
"""
Temperature measured
@@ -370,26 +456,78 @@ class EnvironmentMetrics(google.protobuf.message.Message):
"""
Weight in KG
"""
wind_gust: builtins.float
"""
Wind gust in m/s
"""
wind_lull: builtins.float
"""
Wind lull in m/s
"""
radiation: builtins.float
"""
Radiation in µR/h
"""
def __init__(
self,
*,
temperature: builtins.float = ...,
relative_humidity: builtins.float = ...,
barometric_pressure: builtins.float = ...,
gas_resistance: builtins.float = ...,
voltage: builtins.float = ...,
current: builtins.float = ...,
iaq: builtins.int = ...,
distance: builtins.float = ...,
lux: builtins.float = ...,
white_lux: builtins.float = ...,
ir_lux: builtins.float = ...,
uv_lux: builtins.float = ...,
wind_direction: builtins.int = ...,
wind_speed: builtins.float = ...,
weight: builtins.float = ...,
temperature: builtins.float | None = ...,
relative_humidity: builtins.float | None = ...,
barometric_pressure: builtins.float | None = ...,
gas_resistance: builtins.float | None = ...,
voltage: builtins.float | None = ...,
current: builtins.float | None = ...,
iaq: builtins.int | None = ...,
distance: builtins.float | None = ...,
lux: builtins.float | None = ...,
white_lux: builtins.float | None = ...,
ir_lux: builtins.float | None = ...,
uv_lux: builtins.float | None = ...,
wind_direction: builtins.int | None = ...,
wind_speed: builtins.float | None = ...,
weight: builtins.float | None = ...,
wind_gust: builtins.float | None = ...,
wind_lull: builtins.float | None = ...,
radiation: builtins.float | None = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_speed", b"wind_speed"]) -> None: ...
def HasField(self, field_name: typing.Literal["_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lux", b"_lux", "_radiation", b"_radiation", "_relative_humidity", b"_relative_humidity", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "radiation", b"radiation", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_barometric_pressure", b"_barometric_pressure", "_current", b"_current", "_distance", b"_distance", "_gas_resistance", b"_gas_resistance", "_iaq", b"_iaq", "_ir_lux", b"_ir_lux", "_lux", b"_lux", "_radiation", b"_radiation", "_relative_humidity", b"_relative_humidity", "_temperature", b"_temperature", "_uv_lux", b"_uv_lux", "_voltage", b"_voltage", "_weight", b"_weight", "_white_lux", b"_white_lux", "_wind_direction", b"_wind_direction", "_wind_gust", b"_wind_gust", "_wind_lull", b"_wind_lull", "_wind_speed", b"_wind_speed", "barometric_pressure", b"barometric_pressure", "current", b"current", "distance", b"distance", "gas_resistance", b"gas_resistance", "iaq", b"iaq", "ir_lux", b"ir_lux", "lux", b"lux", "radiation", b"radiation", "relative_humidity", b"relative_humidity", "temperature", b"temperature", "uv_lux", b"uv_lux", "voltage", b"voltage", "weight", b"weight", "white_lux", b"white_lux", "wind_direction", b"wind_direction", "wind_gust", b"wind_gust", "wind_lull", b"wind_lull", "wind_speed", b"wind_speed"]) -> None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_barometric_pressure", b"_barometric_pressure"]) -> typing.Literal["barometric_pressure"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_current", b"_current"]) -> typing.Literal["current"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_distance", b"_distance"]) -> typing.Literal["distance"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_gas_resistance", b"_gas_resistance"]) -> typing.Literal["gas_resistance"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_iaq", b"_iaq"]) -> typing.Literal["iaq"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_ir_lux", b"_ir_lux"]) -> typing.Literal["ir_lux"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_lux", b"_lux"]) -> typing.Literal["lux"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_radiation", b"_radiation"]) -> typing.Literal["radiation"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_relative_humidity", b"_relative_humidity"]) -> typing.Literal["relative_humidity"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_temperature", b"_temperature"]) -> typing.Literal["temperature"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_uv_lux", b"_uv_lux"]) -> typing.Literal["uv_lux"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_voltage", b"_voltage"]) -> typing.Literal["voltage"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_weight", b"_weight"]) -> typing.Literal["weight"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_white_lux", b"_white_lux"]) -> typing.Literal["white_lux"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_wind_direction", b"_wind_direction"]) -> typing.Literal["wind_direction"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_wind_gust", b"_wind_gust"]) -> typing.Literal["wind_gust"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_wind_lull", b"_wind_lull"]) -> typing.Literal["wind_lull"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_wind_speed", b"_wind_speed"]) -> typing.Literal["wind_speed"] | None: ...
global___EnvironmentMetrics = EnvironmentMetrics
@@ -434,14 +572,27 @@ class PowerMetrics(google.protobuf.message.Message):
def __init__(
self,
*,
ch1_voltage: builtins.float = ...,
ch1_current: builtins.float = ...,
ch2_voltage: builtins.float = ...,
ch2_current: builtins.float = ...,
ch3_voltage: builtins.float = ...,
ch3_current: builtins.float = ...,
ch1_voltage: builtins.float | None = ...,
ch1_current: builtins.float | None = ...,
ch2_voltage: builtins.float | None = ...,
ch2_current: builtins.float | None = ...,
ch3_voltage: builtins.float | None = ...,
ch3_current: builtins.float | None = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["ch1_current", b"ch1_current", "ch1_voltage", b"ch1_voltage", "ch2_current", b"ch2_current", "ch2_voltage", b"ch2_voltage", "ch3_current", b"ch3_current", "ch3_voltage", b"ch3_voltage"]) -> None: ...
def HasField(self, field_name: typing.Literal["_ch1_current", b"_ch1_current", "_ch1_voltage", b"_ch1_voltage", "_ch2_current", b"_ch2_current", "_ch2_voltage", b"_ch2_voltage", "_ch3_current", b"_ch3_current", "_ch3_voltage", b"_ch3_voltage", "ch1_current", b"ch1_current", "ch1_voltage", b"ch1_voltage", "ch2_current", b"ch2_current", "ch2_voltage", b"ch2_voltage", "ch3_current", b"ch3_current", "ch3_voltage", b"ch3_voltage"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_ch1_current", b"_ch1_current", "_ch1_voltage", b"_ch1_voltage", "_ch2_current", b"_ch2_current", "_ch2_voltage", b"_ch2_voltage", "_ch3_current", b"_ch3_current", "_ch3_voltage", b"_ch3_voltage", "ch1_current", b"ch1_current", "ch1_voltage", b"ch1_voltage", "ch2_current", b"ch2_current", "ch2_voltage", b"ch2_voltage", "ch3_current", b"ch3_current", "ch3_voltage", b"ch3_voltage"]) -> None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_ch1_current", b"_ch1_current"]) -> typing.Literal["ch1_current"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_ch1_voltage", b"_ch1_voltage"]) -> typing.Literal["ch1_voltage"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_ch2_current", b"_ch2_current"]) -> typing.Literal["ch2_current"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_ch2_voltage", b"_ch2_voltage"]) -> typing.Literal["ch2_voltage"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_ch3_current", b"_ch3_current"]) -> typing.Literal["ch3_current"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_ch3_voltage", b"_ch3_voltage"]) -> typing.Literal["ch3_voltage"] | None: ...
global___PowerMetrics = PowerMetrics
@@ -465,6 +616,7 @@ class AirQualityMetrics(google.protobuf.message.Message):
PARTICLES_25UM_FIELD_NUMBER: builtins.int
PARTICLES_50UM_FIELD_NUMBER: builtins.int
PARTICLES_100UM_FIELD_NUMBER: builtins.int
CO2_FIELD_NUMBER: builtins.int
pm10_standard: builtins.int
"""
Concentration Units Standard PM1.0
@@ -513,26 +665,183 @@ class AirQualityMetrics(google.protobuf.message.Message):
"""
10.0um Particle Count
"""
co2: builtins.int
"""
10.0um Particle Count
"""
def __init__(
self,
*,
pm10_standard: builtins.int = ...,
pm25_standard: builtins.int = ...,
pm100_standard: builtins.int = ...,
pm10_environmental: builtins.int = ...,
pm25_environmental: builtins.int = ...,
pm100_environmental: builtins.int = ...,
particles_03um: builtins.int = ...,
particles_05um: builtins.int = ...,
particles_10um: builtins.int = ...,
particles_25um: builtins.int = ...,
particles_50um: builtins.int = ...,
particles_100um: builtins.int = ...,
pm10_standard: builtins.int | None = ...,
pm25_standard: builtins.int | None = ...,
pm100_standard: builtins.int | None = ...,
pm10_environmental: builtins.int | None = ...,
pm25_environmental: builtins.int | None = ...,
pm100_environmental: builtins.int | None = ...,
particles_03um: builtins.int | None = ...,
particles_05um: builtins.int | None = ...,
particles_10um: builtins.int | None = ...,
particles_25um: builtins.int | None = ...,
particles_50um: builtins.int | None = ...,
particles_100um: builtins.int | None = ...,
co2: builtins.int | None = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["particles_03um", b"particles_03um", "particles_05um", b"particles_05um", "particles_100um", b"particles_100um", "particles_10um", b"particles_10um", "particles_25um", b"particles_25um", "particles_50um", b"particles_50um", "pm100_environmental", b"pm100_environmental", "pm100_standard", b"pm100_standard", "pm10_environmental", b"pm10_environmental", "pm10_standard", b"pm10_standard", "pm25_environmental", b"pm25_environmental", "pm25_standard", b"pm25_standard"]) -> None: ...
def HasField(self, field_name: typing.Literal["_co2", b"_co2", "_particles_03um", b"_particles_03um", "_particles_05um", b"_particles_05um", "_particles_100um", b"_particles_100um", "_particles_10um", b"_particles_10um", "_particles_25um", b"_particles_25um", "_particles_50um", b"_particles_50um", "_pm100_environmental", b"_pm100_environmental", "_pm100_standard", b"_pm100_standard", "_pm10_environmental", b"_pm10_environmental", "_pm10_standard", b"_pm10_standard", "_pm25_environmental", b"_pm25_environmental", "_pm25_standard", b"_pm25_standard", "co2", b"co2", "particles_03um", b"particles_03um", "particles_05um", b"particles_05um", "particles_100um", b"particles_100um", "particles_10um", b"particles_10um", "particles_25um", b"particles_25um", "particles_50um", b"particles_50um", "pm100_environmental", b"pm100_environmental", "pm100_standard", b"pm100_standard", "pm10_environmental", b"pm10_environmental", "pm10_standard", b"pm10_standard", "pm25_environmental", b"pm25_environmental", "pm25_standard", b"pm25_standard"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_co2", b"_co2", "_particles_03um", b"_particles_03um", "_particles_05um", b"_particles_05um", "_particles_100um", b"_particles_100um", "_particles_10um", b"_particles_10um", "_particles_25um", b"_particles_25um", "_particles_50um", b"_particles_50um", "_pm100_environmental", b"_pm100_environmental", "_pm100_standard", b"_pm100_standard", "_pm10_environmental", b"_pm10_environmental", "_pm10_standard", b"_pm10_standard", "_pm25_environmental", b"_pm25_environmental", "_pm25_standard", b"_pm25_standard", "co2", b"co2", "particles_03um", b"particles_03um", "particles_05um", b"particles_05um", "particles_100um", b"particles_100um", "particles_10um", b"particles_10um", "particles_25um", b"particles_25um", "particles_50um", b"particles_50um", "pm100_environmental", b"pm100_environmental", "pm100_standard", b"pm100_standard", "pm10_environmental", b"pm10_environmental", "pm10_standard", b"pm10_standard", "pm25_environmental", b"pm25_environmental", "pm25_standard", b"pm25_standard"]) -> None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_co2", b"_co2"]) -> typing.Literal["co2"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_particles_03um", b"_particles_03um"]) -> typing.Literal["particles_03um"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_particles_05um", b"_particles_05um"]) -> typing.Literal["particles_05um"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_particles_100um", b"_particles_100um"]) -> typing.Literal["particles_100um"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_particles_10um", b"_particles_10um"]) -> typing.Literal["particles_10um"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_particles_25um", b"_particles_25um"]) -> typing.Literal["particles_25um"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_particles_50um", b"_particles_50um"]) -> typing.Literal["particles_50um"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_pm100_environmental", b"_pm100_environmental"]) -> typing.Literal["pm100_environmental"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_pm100_standard", b"_pm100_standard"]) -> typing.Literal["pm100_standard"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_pm10_environmental", b"_pm10_environmental"]) -> typing.Literal["pm10_environmental"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_pm10_standard", b"_pm10_standard"]) -> typing.Literal["pm10_standard"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_pm25_environmental", b"_pm25_environmental"]) -> typing.Literal["pm25_environmental"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_pm25_standard", b"_pm25_standard"]) -> typing.Literal["pm25_standard"] | None: ...
global___AirQualityMetrics = AirQualityMetrics
@typing.final
class LocalStats(google.protobuf.message.Message):
"""
Local device mesh statistics
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
UPTIME_SECONDS_FIELD_NUMBER: builtins.int
CHANNEL_UTILIZATION_FIELD_NUMBER: builtins.int
AIR_UTIL_TX_FIELD_NUMBER: builtins.int
NUM_PACKETS_TX_FIELD_NUMBER: builtins.int
NUM_PACKETS_RX_FIELD_NUMBER: builtins.int
NUM_PACKETS_RX_BAD_FIELD_NUMBER: builtins.int
NUM_ONLINE_NODES_FIELD_NUMBER: builtins.int
NUM_TOTAL_NODES_FIELD_NUMBER: builtins.int
NUM_RX_DUPE_FIELD_NUMBER: builtins.int
NUM_TX_RELAY_FIELD_NUMBER: builtins.int
NUM_TX_RELAY_CANCELED_FIELD_NUMBER: builtins.int
uptime_seconds: builtins.int
"""
How long the device has been running since the last reboot (in seconds)
"""
channel_utilization: builtins.float
"""
Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise).
"""
air_util_tx: builtins.float
"""
Percent of airtime for transmission used within the last hour.
"""
num_packets_tx: builtins.int
"""
Number of packets sent
"""
num_packets_rx: builtins.int
"""
Number of packets received (both good and bad)
"""
num_packets_rx_bad: builtins.int
"""
Number of packets received that are malformed or violate the protocol
"""
num_online_nodes: builtins.int
"""
Number of nodes online (in the past 2 hours)
"""
num_total_nodes: builtins.int
"""
Number of nodes total
"""
num_rx_dupe: builtins.int
"""
Number of received packets that were duplicates (due to multiple nodes relaying).
If this number is high, there are nodes in the mesh relaying packets when it's unnecessary, for example due to the ROUTER/REPEATER role.
"""
num_tx_relay: builtins.int
"""
Number of packets we transmitted that were a relay for others (not originating from ourselves).
"""
num_tx_relay_canceled: builtins.int
"""
Number of times we canceled a packet to be relayed, because someone else did it before us.
This will always be zero for ROUTERs/REPEATERs. If this number is high, some other node(s) is/are relaying faster than you.
"""
def __init__(
self,
*,
uptime_seconds: builtins.int = ...,
channel_utilization: builtins.float = ...,
air_util_tx: builtins.float = ...,
num_packets_tx: builtins.int = ...,
num_packets_rx: builtins.int = ...,
num_packets_rx_bad: builtins.int = ...,
num_online_nodes: builtins.int = ...,
num_total_nodes: builtins.int = ...,
num_rx_dupe: builtins.int = ...,
num_tx_relay: builtins.int = ...,
num_tx_relay_canceled: builtins.int = ...,
) -> None: ...
def ClearField(self, field_name: typing.Literal["air_util_tx", b"air_util_tx", "channel_utilization", b"channel_utilization", "num_online_nodes", b"num_online_nodes", "num_packets_rx", b"num_packets_rx", "num_packets_rx_bad", b"num_packets_rx_bad", "num_packets_tx", b"num_packets_tx", "num_rx_dupe", b"num_rx_dupe", "num_total_nodes", b"num_total_nodes", "num_tx_relay", b"num_tx_relay", "num_tx_relay_canceled", b"num_tx_relay_canceled", "uptime_seconds", b"uptime_seconds"]) -> None: ...
global___LocalStats = LocalStats
@typing.final
class HealthMetrics(google.protobuf.message.Message):
"""
Health telemetry metrics
"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
HEART_BPM_FIELD_NUMBER: builtins.int
SPO2_FIELD_NUMBER: builtins.int
TEMPERATURE_FIELD_NUMBER: builtins.int
heart_bpm: builtins.int
"""
Heart rate (beats per minute)
"""
spO2: builtins.int
"""
SpO2 (blood oxygen saturation) level
"""
temperature: builtins.float
"""
Body temperature in degrees Celsius
"""
def __init__(
self,
*,
heart_bpm: builtins.int | None = ...,
spO2: builtins.int | None = ...,
temperature: builtins.float | None = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["_heart_bpm", b"_heart_bpm", "_spO2", b"_spO2", "_temperature", b"_temperature", "heart_bpm", b"heart_bpm", "spO2", b"spO2", "temperature", b"temperature"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["_heart_bpm", b"_heart_bpm", "_spO2", b"_spO2", "_temperature", b"_temperature", "heart_bpm", b"heart_bpm", "spO2", b"spO2", "temperature", b"temperature"]) -> None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_heart_bpm", b"_heart_bpm"]) -> typing.Literal["heart_bpm"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_spO2", b"_spO2"]) -> typing.Literal["spO2"] | None: ...
@typing.overload
def WhichOneof(self, oneof_group: typing.Literal["_temperature", b"_temperature"]) -> typing.Literal["temperature"] | None: ...
global___HealthMetrics = HealthMetrics
@typing.final
class Telemetry(google.protobuf.message.Message):
"""
@@ -546,6 +855,8 @@ class Telemetry(google.protobuf.message.Message):
ENVIRONMENT_METRICS_FIELD_NUMBER: builtins.int
AIR_QUALITY_METRICS_FIELD_NUMBER: builtins.int
POWER_METRICS_FIELD_NUMBER: builtins.int
LOCAL_STATS_FIELD_NUMBER: builtins.int
HEALTH_METRICS_FIELD_NUMBER: builtins.int
time: builtins.int
"""
Seconds since 1970 - or 0 for unknown/unset
@@ -574,6 +885,18 @@ class Telemetry(google.protobuf.message.Message):
Power Metrics
"""
@property
def local_stats(self) -> global___LocalStats:
"""
Local device mesh statistics
"""
@property
def health_metrics(self) -> global___HealthMetrics:
"""
Health telemetry metrics
"""
def __init__(
self,
*,
@@ -582,10 +905,12 @@ class Telemetry(google.protobuf.message.Message):
environment_metrics: global___EnvironmentMetrics | None = ...,
air_quality_metrics: global___AirQualityMetrics | None = ...,
power_metrics: global___PowerMetrics | None = ...,
local_stats: global___LocalStats | None = ...,
health_metrics: global___HealthMetrics | None = ...,
) -> None: ...
def HasField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "power_metrics", b"power_metrics", "variant", b"variant"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "power_metrics", b"power_metrics", "time", b"time", "variant", b"variant"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["variant", b"variant"]) -> typing.Literal["device_metrics", "environment_metrics", "air_quality_metrics", "power_metrics"] | None: ...
def HasField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "health_metrics", b"health_metrics", "local_stats", b"local_stats", "power_metrics", b"power_metrics", "variant", b"variant"]) -> builtins.bool: ...
def ClearField(self, field_name: typing.Literal["air_quality_metrics", b"air_quality_metrics", "device_metrics", b"device_metrics", "environment_metrics", b"environment_metrics", "health_metrics", b"health_metrics", "local_stats", b"local_stats", "power_metrics", b"power_metrics", "time", b"time", "variant", b"variant"]) -> None: ...
def WhichOneof(self, oneof_group: typing.Literal["variant", b"variant"]) -> typing.Literal["device_metrics", "environment_metrics", "air_quality_metrics", "power_metrics", "local_stats", "health_metrics"] | None: ...
global___Telemetry = Telemetry

View File

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

View File

@@ -1,10 +1,11 @@
""" Serial interface class
"""
# pylint: disable=R0917
import logging
import platform
import time
from typing import Optional
from typing import List, Optional
import serial # type: ignore[import-untyped]
@@ -18,7 +19,7 @@ if platform.system() != "Windows":
class SerialInterface(StreamInterface):
"""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
find one Meshtastic device by probing
@@ -31,13 +32,13 @@ class SerialInterface(StreamInterface):
self.devPath: Optional[str] = devPath
if self.devPath is None:
ports = meshtastic.util.findPorts(True)
ports: List[str] = meshtastic.util.findPorts(True)
logging.debug(f"ports:{ports}")
if len(ports) == 0:
print("No Serial Meshtastic device detected, attempting TCP connection on localhost.")
return
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}"
meshtastic.util.our_exit(message)
else:
@@ -58,18 +59,19 @@ class SerialInterface(StreamInterface):
self.stream = serial.Serial(
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)
StreamInterface.__init__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
)
def close(self):
def close(self) -> None:
"""Close a connection to the device"""
self.stream.flush()
time.sleep(0.1)
self.stream.flush()
time.sleep(0.1)
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
time.sleep(0.1)
self.stream.flush()
time.sleep(0.1)
logging.debug("Closing Serial stream")
StreamInterface.close(self)

View File

@@ -0,0 +1,3 @@
"""Structured logging framework (see dev docs for more info)."""
from .slog import LogSet, root_dir

96
meshtastic/slog/arrow.py Normal file
View File

@@ -0,0 +1,96 @@
"""Utilities for Apache Arrow serialization."""
import logging
import threading
import os
from typing import Optional, List
import pyarrow as pa
from pyarrow import feather
chunk_size = 1000 # disk writes are batched based on this number of rows
class ArrowWriter:
"""Writes an arrow file in a streaming fashion"""
def __init__(self, file_name: str):
"""Create a new ArrowWriter object.
file_name (str): The name of the file to write to.
"""
self.sink = pa.OSFile(file_name, "wb") # type: ignore
self.new_rows: List[dict] = []
self.schema: Optional[pa.Schema] = None # haven't yet learned the schema
self.writer: Optional[pa.RecordBatchStreamWriter] = None
self._lock = threading.Condition() # Ensure only one thread writes at a time
def close(self):
"""Close the stream and writes the file as needed."""
with self._lock:
self._write()
if self.writer:
self.writer.close()
self.sink.close()
def set_schema(self, schema: pa.Schema):
"""Set the schema for the file.
Only needed for datasets where we can't learn it from the first record written.
schema (pa.Schema): The schema to use.
"""
with self._lock:
assert self.schema is None
self.schema = schema
self.writer = pa.ipc.new_stream(self.sink, schema)
def _write(self):
"""Write the new rows to the file."""
if len(self.new_rows) > 0:
if self.schema is None:
# only need to look at the first row to learn the schema
self.set_schema(pa.Table.from_pylist([self.new_rows[0]]).schema)
self.writer.write_batch(
pa.RecordBatch.from_pylist(self.new_rows, schema=self.schema)
)
self.new_rows = []
def add_row(self, row_dict: dict):
"""Add a row to the arrow file.
We will automatically learn the schema from the first row. But all rows must use that schema.
"""
with self._lock:
self.new_rows.append(row_dict)
if len(self.new_rows) >= chunk_size:
self._write()
class FeatherWriter(ArrowWriter):
"""A smaller more interoperable version of arrow files.
Uses a temporary .arrow file (which could be huge) but converts to a much smaller (but still fast)
feather file.
"""
def __init__(self, file_name: str):
super().__init__(file_name + ".arrow")
self.base_file_name = file_name
def close(self):
super().close()
src_name = self.base_file_name + ".arrow"
dest_name = self.base_file_name + ".feather"
if os.path.getsize(src_name) == 0:
logging.warning(f"Discarding empty file: {src_name}")
os.remove(src_name)
else:
logging.info(f"Compressing log data into {dest_name}")
# note: must use open_stream, not open_file/read_table because the streaming layout is different
# data = feather.read_table(src_name)
with pa.memory_map(src_name) as source:
array = pa.ipc.open_stream(source).read_all()
# See https://stackoverflow.com/a/72406099 for more info and performance testing measurements
feather.write_feather(array, dest_name, compression="zstd")
os.remove(src_name)

296
meshtastic/slog/slog.py Normal file
View File

@@ -0,0 +1,296 @@
"""code logging power consumption of meshtastic devices."""
import atexit
import io
import logging
import os
import re
import threading
import time
from dataclasses import dataclass
from datetime import datetime
from functools import reduce
from typing import Optional, List, Tuple
import parse # type: ignore[import-untyped]
import platformdirs
import pyarrow as pa
from pubsub import pub # type: ignore[import-untyped]
from meshtastic.mesh_interface import MeshInterface
from meshtastic.powermon import PowerMeter
from .arrow import FeatherWriter
def root_dir() -> str:
"""Return the root directory for slog files."""
app_name = "meshtastic"
app_author = "meshtastic"
app_dir = platformdirs.user_data_dir(app_name, app_author)
dir_name = f"{app_dir}/slogs"
os.makedirs(dir_name, exist_ok=True)
return dir_name
@dataclass(init=False)
class LogDef:
"""Log definition."""
code: str # i.e. PM or B or whatever... see meshtastic slog documentation
fields: List[Tuple[str, pa.DataType]] # A list of field names and their arrow types
format: parse.Parser # A format string that can be used to parse the arguments
def __init__(self, code: str, fields: List[Tuple[str, pa.DataType]]) -> None:
"""Initialize the LogDef object.
code (str): The code.
format (str): The format.
"""
self.code = code
self.fields = fields
fmt = ""
for idx, f in enumerate(fields):
if idx != 0:
fmt += ","
# make the format string
suffix = (
"" if f[1] == pa.string() else ":d"
) # treat as a string or an int (the only types we have so far)
fmt += "{" + f[0] + suffix + "}"
self.format = parse.compile(
fmt
) # We include a catchall matcher at the end - to ignore stuff we don't understand
"""A dictionary mapping from logdef code to logdef"""
log_defs = {
d.code: d
for d in [
LogDef("B", [("board_id", pa.uint32()), ("sw_version", pa.string())]),
LogDef("PM", [("pm_mask", pa.uint64()), ("pm_reason", pa.string())]),
LogDef("PS", [("ps_state", pa.uint32())]),
]
}
log_regex = re.compile(".*S:([0-9A-Za-z]+):(.*)")
class PowerLogger:
"""Logs current watts reading periodically using PowerMeter and ArrowWriter."""
def __init__(self, pMeter: PowerMeter, file_path: str, interval=0.002) -> None:
"""Initialize the PowerLogger object."""
self.pMeter = pMeter
self.writer = FeatherWriter(file_path)
self.interval = interval
self.is_logging = True
self.thread = threading.Thread(
target=self._logging_thread, name="PowerLogger", daemon=True
)
self.thread.start()
def store_current_reading(self, now: Optional[datetime] = None) -> None:
"""Store current power measurement."""
if now is None:
now = datetime.now()
d = {
"time": now,
"average_mW": self.pMeter.get_average_current_mA(),
"max_mW": self.pMeter.get_max_current_mA(),
"min_mW": self.pMeter.get_min_current_mA(),
}
self.pMeter.reset_measurements()
self.writer.add_row(d)
def _logging_thread(self) -> None:
"""Background thread for logging the current watts reading."""
while self.is_logging:
self.store_current_reading()
time.sleep(self.interval)
def close(self) -> None:
"""Close the PowerLogger and stop logging."""
if self.is_logging:
self.pMeter.close()
self.is_logging = False
self.thread.join()
self.writer.close()
# FIXME move these defs somewhere else
TOPIC_MESHTASTIC_LOG_LINE = "meshtastic.log.line"
class StructuredLogger:
"""Sniffs device logs for structured log messages, extracts those into apache arrow format.
Also writes the raw log messages to raw.txt"""
def __init__(
self,
client: MeshInterface,
dir_path: str,
power_logger: Optional[PowerLogger] = None,
include_raw=True,
) -> None:
"""Initialize the StructuredLogger object.
client (MeshInterface): The MeshInterface object to monitor.
"""
self.client = client
self.power_logger = power_logger
# Setup the arrow writer (and its schema)
self.writer = FeatherWriter(f"{dir_path}/slog")
all_fields = reduce(
(lambda x, y: x + y), map(lambda x: x.fields, log_defs.values())
)
self.include_raw = include_raw
if self.include_raw:
all_fields.append(("raw", pa.string()))
# Use timestamp as the first column
all_fields.insert(0, ("time", pa.timestamp("us")))
# pass in our name->type tuples a pa.fields
self.writer.set_schema(
pa.schema(map(lambda x: pa.field(x[0], x[1]), all_fields))
)
self.raw_file: Optional[
io.TextIOWrapper
] = open( # pylint: disable=consider-using-with
f"{dir_path}/raw.txt", "w", encoding="utf8"
)
# We need a closure here because the subscription API is very strict about exact arg matching
def listen_glue(line, interface): # pylint: disable=unused-argument
self._onLogMessage(line)
self._listen_glue = (
listen_glue # we must save this so it doesn't get garbage collected
)
self._listener = pub.subscribe(listen_glue, TOPIC_MESHTASTIC_LOG_LINE)
def close(self) -> None:
"""Stop logging."""
pub.unsubscribe(self._listener, TOPIC_MESHTASTIC_LOG_LINE)
self.writer.close()
f = self.raw_file
self.raw_file = None # mark that we are shutting down
if f:
f.close() # Close the raw.txt file
def _onLogMessage(self, line: str) -> None:
"""Handle log messages.
line (str): the line of log output
"""
di = {} # the dictionary of the fields we found to log
m = log_regex.match(line)
if m:
src = m.group(1)
args = m.group(2)
logging.debug(f"SLog {src}, args: {args}")
d = log_defs.get(src)
if d:
last_field = d.fields[-1]
last_is_str = last_field[1] == pa.string()
if last_is_str:
args += " "
# append a space so that if the last arg is an empty str
# it will still be accepted as a match for a str
r = d.format.parse(args) # get the values with the correct types
if r:
di = r.named
if last_is_str:
di[last_field[0]] = di[
last_field[0]
].strip() # remove the trailing space we added
if di[last_field[0]] == "":
# If the last field is an empty string, remove it
del di[last_field[0]]
else:
logging.warning(f"Failed to parse slog {line} with {d.format}")
else:
logging.warning(f"Unknown Structured Log: {line}")
# Store our structured log record
if di or self.include_raw:
now = datetime.now()
di["time"] = now
if self.include_raw:
di["raw"] = line
self.writer.add_row(di)
# If we have a sibling power logger, make sure we have a power measurement with the EXACT same timestamp
if self.power_logger:
self.power_logger.store_current_reading(now)
if self.raw_file:
self.raw_file.write(line + "\n") # Write the raw log
class LogSet:
"""A complete set of meshtastic log/metadata for a particular run."""
def __init__(
self,
client: MeshInterface,
dir_name: Optional[str] = None,
power_meter: Optional[PowerMeter] = None,
) -> None:
"""Initialize the PowerMonClient object.
power (PowerSupply): The power supply object.
client (MeshInterface): The MeshInterface object to monitor.
"""
if not dir_name:
app_dir = root_dir()
dir_name = f"{app_dir}/{datetime.now().strftime('%Y%m%d-%H%M%S')}"
os.makedirs(dir_name, exist_ok=True)
# Also make a 'latest' directory that always points to the most recent logs
# symlink might fail on some platforms, if it does fail silently
if os.path.exists(f"{app_dir}/latest"):
os.unlink(f"{app_dir}/latest")
os.symlink(dir_name, f"{app_dir}/latest", target_is_directory=True)
self.dir_name = dir_name
logging.info(f"Writing slogs to {dir_name}")
self.power_logger: Optional[PowerLogger] = (
None
if not power_meter
else PowerLogger(power_meter, f"{self.dir_name}/power")
)
self.slog_logger: Optional[StructuredLogger] = StructuredLogger(
client, self.dir_name, power_logger=self.power_logger
)
# Store a lambda so we can find it again to unregister
self.atexit_handler = lambda: self.close() # pylint: disable=unnecessary-lambda
def close(self) -> None:
"""Close the log set."""
if self.slog_logger:
logging.info(f"Closing slogs in {self.dir_name}")
atexit.unregister(
self.atexit_handler
) # docs say it will silently ignore if not found
self.slog_logger.close()
if self.power_logger:
self.power_logger.close()
self.slog_logger = None

View File

@@ -1,10 +1,13 @@
"""Stream Interface base class
"""
import io
import logging
import threading
import time
import traceback
from typing import Optional, cast
import serial # type: ignore[import-untyped]
from meshtastic.mesh_interface import MeshInterface
@@ -19,7 +22,7 @@ MAX_TO_FROM_RADIO_SIZE = 512
class StreamInterface(MeshInterface):
"""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
Keyword Arguments:
@@ -35,13 +38,15 @@ class StreamInterface(MeshInterface):
raise Exception( # pylint: disable=W0719
"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._wantExit = False
self.is_windows11 = is_windows11()
self.cur_log_line = ""
# FIXME, figure out why daemon=True causes reader thread to exit too early
self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True)
self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True, name="stream reader")
MeshInterface.__init__(self, debugOut=debugOut, noProto=noProto, noNodes=noNodes)
@@ -51,7 +56,7 @@ class StreamInterface(MeshInterface):
if not noProto:
self.waitForConfig()
def connect(self):
def connect(self) -> None:
"""Connect to our radio
Normally this is called automatically by the constructor, but if you
@@ -62,7 +67,7 @@ class StreamInterface(MeshInterface):
# 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
# because we want to ensure it is looking for START1)
p = bytearray([START2] * 32)
p: bytes = bytearray([START2] * 32)
self._writeBytes(p)
time.sleep(0.1) # wait 100ms to give device time to start running
@@ -73,7 +78,7 @@ class StreamInterface(MeshInterface):
if not self.noProto: # Wait for the db download if using the protocol
self._waitConnected()
def _disconnected(self):
def _disconnected(self) -> None:
"""We override the superclass implementation to close our port"""
MeshInterface._disconnected(self)
@@ -85,7 +90,7 @@ class StreamInterface(MeshInterface):
# pylint: disable=W0201
self.stream = None
def _writeBytes(self, b):
def _writeBytes(self, b: bytes) -> None:
"""Write an array of bytes to our stream and flush"""
if self.stream: # ignore writes when stream is closed
self.stream.write(b)
@@ -97,24 +102,24 @@ class StreamInterface(MeshInterface):
# we sleep here to give the TBeam a chance to work
time.sleep(0.1)
def _readBytes(self, length):
def _readBytes(self, length) -> Optional[bytes]:
"""Read an array of bytes from our stream"""
if self.stream:
return self.stream.read(length)
else:
return None
def _sendToRadioImpl(self, toRadio):
def _sendToRadioImpl(self, toRadio) -> None:
"""Send a ToRadio protobuf to the device"""
logging.debug(f"Sending: {stripnl(toRadio)}")
b = toRadio.SerializeToString()
bufLen = len(b)
b: bytes = toRadio.SerializeToString()
bufLen: int = len(b)
# We convert into a string, because the TCP code doesn't work with byte arrays
header = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF])
logging.debug(f"sending header:{header} b:{b}")
header: bytes = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF])
logging.debug(f"sending header:{header!r} b:{b!r}")
self._writeBytes(header + b)
def close(self):
def close(self) -> None:
"""Close a connection to the device"""
logging.debug("Closing stream")
MeshInterface.close(self)
@@ -124,7 +129,24 @@ class StreamInterface(MeshInterface):
if self._rxThread != threading.current_thread():
self._rxThread.join() # wait for it to exit
def __reader(self):
def _handleLogByte(self, b):
"""Handle a byte that is part of a log message from the device."""
utf = "?" # assume we might fail
try:
utf = b.decode("utf-8")
except:
pass
if utf == "\r":
pass # ignore
elif utf == "\n":
self._handleLogLine(self.cur_log_line)
self.cur_log_line = ""
else:
self.cur_log_line += utf
def __reader(self) -> None:
"""The reader thread that reads bytes from our stream"""
logging.debug("in __reader()")
empty = bytes()
@@ -132,13 +154,13 @@ class StreamInterface(MeshInterface):
try:
while not self._wantExit:
# logging.debug("reading character")
b = self._readBytes(1)
b: Optional[bytes] = self._readBytes(1)
# logging.debug("In reader loop")
# logging.debug(f"read returned {b}")
if len(b) > 0:
c = b[0]
if b is not None and len(cast(bytes, b)) > 0:
c: int = b[0]
# 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
self._rxBuf = self._rxBuf + b
@@ -146,11 +168,9 @@ class StreamInterface(MeshInterface):
if ptr == 0: # looking for START1
if c != START1:
self._rxBuf = empty # failed to find start
if self.debugOut is not None:
try:
self.debugOut.write(b.decode("utf-8"))
except:
self.debugOut.write("?")
# This must be a log message from the device
self._handleLogByte(b)
elif ptr == 1: # looking for START2
if c != START2:

View File

@@ -1,6 +1,7 @@
""" 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.
"""
# pylint: disable=R0917
# 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

View File

@@ -1,11 +1,13 @@
"""TCPInterface class for interfacing with http endpoint
"""
# pylint: disable=R0917
import logging
import socket
from typing import Optional
from typing import Optional, cast
from meshtastic.stream_interface import StreamInterface
DEFAULT_TCP_PORT = 4403
class TCPInterface(StreamInterface):
"""Interface class for meshtastic devices over a TCP link"""
@@ -14,9 +16,9 @@ class TCPInterface(StreamInterface):
self,
hostname: str,
debugOut=None,
noProto=False,
connectNow=True,
portNumber=4403,
noProto: bool=False,
connectNow: bool=True,
portNumber: int=DEFAULT_TCP_PORT,
noNodes:bool=False,
):
"""Constructor, opens a connection to a specified IP address/hostname
@@ -27,14 +29,16 @@ class TCPInterface(StreamInterface):
self.stream = None
self.hostname = hostname
self.portNumber = portNumber
self.hostname: str = hostname
self.portNumber: int = portNumber
self.socket: Optional[socket.socket] = None
if connectNow:
logging.debug(f"Connecting to {hostname}") # type: ignore[str-bytes-safe]
server_address = (hostname, portNumber)
sock = socket.create_connection(server_address)
self.socket: Optional[socket.socket] = sock
server_address: tuple[str, int] = (hostname, portNumber)
sock: Optional[socket.socket] = socket.create_connection(server_address)
self.socket = sock
else:
self.socket = None
@@ -42,25 +46,26 @@ class TCPInterface(StreamInterface):
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow, noNodes=noNodes
)
def _socket_shutdown(self):
def _socket_shutdown(self) -> None:
"""Shutdown the socket.
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"""
server_address = (self.hostname, self.portNumber)
sock = socket.create_connection(server_address)
server_address: tuple[str, int] = (self.hostname, self.portNumber)
sock: Optional[socket.socket] = socket.create_connection(server_address)
self.socket = sock
def close(self):
def close(self) -> None:
"""Close a connection to the device"""
logging.debug("Closing TCP stream")
StreamInterface.close(self)
# Sometimes the socket read might be blocked in the reader thread.
# Therefore we force the shutdown by closing the socket here
self._wantExit = True
self._wantExit: bool = True
if not self.socket is None:
try:
self._socket_shutdown()
@@ -68,10 +73,14 @@ class TCPInterface(StreamInterface):
pass # Ignore errors in shutdown, because we might have a race with the server
self.socket.close()
def _writeBytes(self, b):
def _writeBytes(self, b: bytes) -> None:
"""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"""
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 time
import traceback
import io
from typing import List, Optional
from dotmap import DotMap # 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
"""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"""
receivedPackets = None
receivedPackets: Optional[List] = None
testsRunning = False
testsRunning: bool = False
testNumber = 0
testNumber: int = 0
sendingInterface = None
def onReceive(packet, interface):
def onReceive(packet, interface) -> None:
"""Callback invoked when a packet arrives"""
if sendingInterface == interface:
pass
@@ -42,20 +45,20 @@ def onReceive(packet, interface):
receivedPackets.append(p)
def onNode(node):
def onNode(node) -> None:
"""Callback invoked when the node DB changes"""
print(f"Node changed: {node}")
def subscribe():
def subscribe() -> None:
"""Subscribe to the topics the user probably wants to see, prints output to stdout"""
pub.subscribe(onNode, "meshtastic.node")
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
@@ -93,16 +96,16 @@ def testSend(
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."""
logging.info(f"Running {numTests} tests with wantAck={wantAck}")
numFail = 0
numSuccess = 0
numFail: int = 0
numSuccess: int = 0
for _ in range(numTests):
# pylint: disable=W0603
global testNumber
testNumber = testNumber + 1
isBroadcast = True
isBroadcast:bool = True
# asBinary=(i % 2 == 0)
success = testSend(
interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck
@@ -126,10 +129,10 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
return True
def testThread(numTests=50):
def testThread(numTests=50) -> bool:
"""Test thread"""
logging.info("Found devices, starting tests...")
result = runTests(numTests, wantAck=True)
result: bool = runTests(numTests, wantAck=True)
if result:
# Run another test
# Allow a few dropped packets
@@ -137,25 +140,25 @@ def testThread(numTests=50):
return result
def onConnection(topic=pub.AUTO_TOPIC):
def onConnection(topic=pub.AUTO_TOPIC) -> None:
"""Callback invoked when we connect/disconnect from a radio"""
print(f"Connection changed: {topic.getName()}")
def openDebugLog(portName):
def openDebugLog(portName) -> io.TextIOWrapper:
"""Open the debug log file"""
debugname = "log" + portName.replace("/", "_")
logging.info(f"Writing serial debugging to {debugname}")
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.
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:
meshtastic.util.our_exit(
"Warning: Must have at least two devices connected to USB."
@@ -175,7 +178,7 @@ def testAll(numTests=5):
)
logging.info("Ports opened, starting test")
result = testThread(numTests)
result: bool = testThread(numTests)
for i in interfaces:
i.close()
@@ -183,7 +186,7 @@ def testAll(numTests=5):
return result
def testSimulator():
def testSimulator() -> None:
"""
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
@@ -195,7 +198,7 @@ def testSimulator():
logging.basicConfig(level=logging.DEBUG)
logging.info("Connecting to simulator on localhost!")
try:
iface = TCPInterface("localhost")
iface: meshtastic.tcp_interface.TCPInterface = TCPInterface("localhost")
iface.showInfo()
iface.localNode.showInfo()
iface.localNode.exitSimulator()

View File

Binary file not shown.

View File

@@ -0,0 +1,349 @@
[RadioIf] getFromRadio=STATE_SEND_PACKETS
[RadioIf] Can not send yet, busyRx
Telling client we have new packets 3
BLE notify fromNum
[Blink] S:PM:0x000009c8,
[Blink] S:PM:0x00000948,
toRadioWriteCb data 0x2001ffea, len 26
PACKET FROM PHONE (id=0x8f26f64c fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
[RadioIf] Ignore false preamble detection.
[RadioIf] S:PM:0x00000940,
[RadioIf] Starting low level send (id=0x8f26f64b fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted hopStart=3 priority=64)
[RadioIf] S:PM:0x00000950,
[RadioIf] (bw=250, sf=11, cr=4/5) packet symLen=8 ms, payloadSize=25, time 419 ms
[RadioIf] AirTime - Packet transmitted : 419ms
Telling client we have new packets 4
BLE notify fromNum
[Router] Add packet record (id=0x8f26f64c fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725846)
[Router] handleReceived(REMOTE) (id=0x8f26f64c fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725846)
[Router] Module 'powerstress' wantsPacket=1
[Router] Received powerstress from=0x0, id=0x8f26f64c, portnum=74, payloadlen=2
[Router] Received PowerStress cmd=1
[Router] S:B:9,2.3.15.177d19ac
[Router] Asked module 'powerstress' to send a response
[Router] Module 'powerstress' handled and skipped other processing
[Router] No one responded, send a nak
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f64c,id=0x5fa26660
[Router] Enqueued local (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
[Router] Rx someone rebroadcasting for us (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
[Router] didn't find pending packet
[Router] Add packet record (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
[Router] handleReceived(REMOTE) (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
[Router] Module 'canned' wantsPacket=1
[Router] showing standard frames
[Router] Showing 0 module frames
[Router] Total frame count: 103
[Router] Added modules. numframes: 0
[Router] Finished building frames. numframes: 7
[Router] Module 'canned' considered
[Router] Module 'routing' wantsPacket=1
[Router] Received routing from=0x67f63246, id=0x5fa26660, portnum=5, payloadlen=2
[Router] Routing sniffing (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
[Router] Received a nak for 0x8f26f64c, stopping retransmissions
[Router] Delivering rx packet (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
[Router] Update DB node 0x67f63246, rx_time=1720725847
[Router] Forwarding to phone (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
[Router] Module 'routing' considered
[RadioIf] Completed sending (id=0x8f26f64b fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted hopStart=3 priority=64)
[RadioIf] S:PM:0x00000940,
[RadioIf] S:PM:0x00000948,
Telling client we have new packets 5
BLE notify fromNum
[RadioIf] S:PM:0x00000940,
[RadioIf] Starting low level send (id=0x5fa2665f fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted hopStart=3 priority=10)
[RadioIf] S:PM:0x00000950,
[RadioIf] (bw=250, sf=11, cr=4/5) packet symLen=8 ms, payloadSize=66, time 722 ms
[RadioIf] AirTime - Packet transmitted : 722ms
[Blink] S:PM:0x000009d0,
[Blink] S:PM:0x00000950,
getFromRadio=STATE_SEND_PACKETS
getFromRadio=STATE_SEND_PACKETS
phone downloaded packet (id=0x5fa26660 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64c rxtime=1720725847 priority=120)
toRadioWriteCb data 0x2001ffea, len 31
PACKET FROM PHONE (id=0x8f26f64d fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
Enqueued local (id=0x8f26f64d fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725848)
Telling client we have new packets 6
BLE notify fromNum
[Router] Add packet record (id=0x8f26f64d fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725848)
[Router] handleReceived(REMOTE) (id=0x8f26f64d fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725848)
[Router] Module 'powerstress' wantsPacket=1
[Router] Received powerstress from=0x0, id=0x8f26f64d, portnum=74, payloadlen=7
[Router] Received PowerStress cmd=48
[Router] Asked module 'powerstress' to send a response
[Router] Module 'powerstress' handled and skipped other processing
[Router] No one responded, send a nak
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f64d,id=0x5fa26661
[Router] Enqueued local (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
[Router] Rx someone rebroadcasting for us (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
[Router] didn't find pending packet
[Router] Add packet record (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
[Router] handleReceived(REMOTE) (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
[Router] Module 'canned' wantsPacket=1
[Router] Showing 0 module frames
[Router] Total frame count: 103
[Router] Added modules. numframes: 0
[Router] Finished building frames. numframes: 7
[Router] Module 'canned' considered
[Router] Module 'routing' wantsPacket=1
[Router] Received routing from=0x67f63246, id=0x5fa26661, portnum=5, payloadlen=2
[Router] Routing sniffing (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
[Router] Received a nak for 0x8f26f64d, stopping retransmissions
[Router] Delivering rx packet (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
[Router] Update DB node 0x67f63246, rx_time=1720725848
[Router] Forwarding to phone (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
[Router] Module 'routing' considered
[PowerStressModule] S:PS:48
[PowerStressModule] S:PM:0x000009d0,
[RadioIf] Completed sending (id=0x5fa2665f fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted hopStart=3 priority=10)
[RadioIf] S:PM:0x000009c0,
[RadioIf] S:PM:0x000009c8,
Telling client we have new packets 7
BLE notify fromNum
getFromRadio=STATE_SEND_PACKETS
phone downloaded packet (id=0x5fa26661 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64d rxtime=1720725848 priority=120)
toRadioWriteCb data 0x2001ffea, len 31
PACKET FROM PHONE (id=0x8f26f64e fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
Enqueued local (id=0x8f26f64e fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725853)
Telling client we have new packets 8
[Router] Add packet record (id=0x8f26f64e fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725853)
[Router] handleReceived(REMOTE) (id=0x8f26f64e fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725853)
[Router] Module 'powerstress' wantsPacket=1
[Router] Received powerstress from=0x0, id=0x8f26f64e, portnum=74, payloadlen=7
[Router] Received PowerStress cmd=49
[Router] PowerStress operation 48 already in progress! Can't start new command
[Router] Asked module 'powerstress' to send a response
[Router] Module 'powerstress' handled and skipped other processing
[Router] No one responded, send a nak
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f64e,id=0x5fa26662
[Router] Enqueued local (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
[Router] Rx someone rebroadcasting for us (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
[Router] didn't find pending packet
[Router] Add packet record (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
[Router] handleReceived(REMOTE) (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
[Router] Module 'canned' wantsPacket=1
[Router] showing standard frames
[Router] Showing 0 module frames
[Router] Total frame count: 103
[Router] Added modules. numframes: 0
[Router] Finished building frames. numframes: 7
[Router] Module 'canned' considered
[Router] Module 'routing' wantsPacket=1
[Router] Received routing from=0x67f63246, id=0x5fa26662, portnum=5, payloadlen=2
[Router] Routing sniffing (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
[Router] Received a nak for 0x8f26f64e, stopping retransmissions
[Router] Delivering rx packet (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
[Router] Update DB node 0x67f63246, rx_time=1720725853
[Router] Forwarding to phone (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
[Router] Module 'routing' considered
[PowerStressModule] S:PS:0
Telling client we have new packets 9
BLE notify fromNum
[Power] Battery: usbPower=0, isCharging=0, batMv=3191, batPct=4
getFromRadio=STATE_SEND_PACKETS
phone downloaded packet (id=0x5fa26662 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64e rxtime=1720725853 priority=120)
toRadioWriteCb data 0x2001ffea, len 31
PACKET FROM PHONE (id=0x8f26f64f fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
Enqueued local (id=0x8f26f64f fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725858)
Telling client we have new packets 10
BLE notify fromNum
[Router] Add packet record (id=0x8f26f64f fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725858)
[Router] handleReceived(REMOTE) (id=0x8f26f64f fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725858)
[Router] Module 'powerstress' wantsPacket=1
[Router] Received powerstress from=0x0, id=0x8f26f64f, portnum=74, payloadlen=7
[Router] Received PowerStress cmd=80
[Router] Asked module 'powerstress' to send a response
[Router] Module 'powerstress' handled and skipped other processing
[Router] No one responded, send a nak
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f64f,id=0x5fa26663
[Router] Enqueued local (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
[Router] Rx someone rebroadcasting for us (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
[Router] didn't find pending packet
[Router] Add packet record (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
[Router] handleReceived(REMOTE) (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
[Router] Module 'canned' wantsPacket=1
[Router] showing standard frames
[Router] Showing 0 module frames
[Router] Added modules. numframes: 0
[Router] Finished building frames. numframes: 7
[Router] Module 'canned' considered
[Router] Module 'routing' wantsPacket=1
[Router] Received routing from=0x67f63246, id=0x5fa26663, portnum=5, payloadlen=2
[Router] Routing sniffing (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
[Router] Received a nak for 0x8f26f64f, stopping retransmissions
[Router] Delivering rx packet (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
[Router] Update DB node 0x67f63246, rx_time=1720725858
[Router] Forwarding to phone (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
[Router] Module 'routing' considered
[PowerStressModule] S:PS:80
[PowerStressModule] S:PM:0x00000988,
[PowerStressModule] Disable NRF52 bluetooth
Telling client we have new packets 11
BLE notify fromNum
[DeviceTelemetryModule] (Sending): air_util_tx=0.031694, channel_utilization=1.901667, battery_level=4, voltage=3.191000, uptime=50
[DeviceTelemetryModule] updateTelemetry LOCAL
[DeviceTelemetryModule] Node status update: 1 online, 82 total
[DeviceTelemetryModule] Sending packet to mesh
[DeviceTelemetryModule] Update DB node 0x67f63246, rx_time=1720725859
[DeviceTelemetryModule] handleReceived(LOCAL) (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x0 Portnum=67 rxtime=1720725859 priority=10)
[DeviceTelemetryModule] No modules interested in portnum=67, src=LOCAL
[DeviceTelemetryModule] localSend to channel 0
[DeviceTelemetryModule] Add packet record (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x0 Portnum=67 rxtime=1720725859 priority=10)
[DeviceTelemetryModule] Expanding short PSK #1
[DeviceTelemetryModule] Using AES128 key!
[DeviceTelemetryModule] nRF52 encrypt fr=67f63246, num=5fa26664, numBytes=30!
[DeviceTelemetryModule] enqueuing for send (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted rxtime=1720725859 hopStart=3 priority=10)
[DeviceTelemetryModule] txGood=2,rxGood=0,rxBad=0
[DeviceTelemetryModule] Using channel 0 (hash 0x8)
[DeviceTelemetryModule] Expanding short PSK #1
[DeviceTelemetryModule] Using AES128 key!
[DeviceTelemetryModule] nRF52 encrypt fr=67f63246, num=5fa26664, numBytes=30!
[DeviceTelemetryModule] decoded message (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x0 Portnum=67 rxtime=1720725859 hopStart=3 priority=10)
Telling client we have new packets 13
BLE notify fromNum
[RadioIf] Can not send yet, busyRx
[RadioIf] Can not send yet, busyRx
[RadioIf] Can not send yet, busyRx
[RadioIf] Can not send yet, busyRx
getFromRadio=STATE_SEND_PACKETS
[RadioIf] Can not send yet, busyRx
[RadioIf] Can not send yet, busyRx
[RadioIf] Can not send yet, busyRx
[RadioIf] Can not send yet, busyRx
[RadioIf] Can not send yet, busyRx
getFromRadio=STATE_SEND_PACKETS
phone downloaded packet (id=0x5fa26663 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f64f rxtime=1720725858 priority=120)
[RadioIf] Ignore false preamble detection.
[RadioIf] S:PM:0x00000980,
[RadioIf] Starting low level send (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted rxtime=1720725859 hopStart=3 priority=10)
[RadioIf] S:PM:0x00000990,
[RadioIf] (bw=250, sf=11, cr=4/5) packet symLen=8 ms, payloadSize=46, time 575 ms
[RadioIf] AirTime - Packet transmitted : 575ms
getFromRadio=STATE_SEND_PACKETS
phone downloaded packet (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x0 Portnum=67 rxtime=1720725859 hopStart=3 priority=10)
[RadioIf] Completed sending (id=0x5fa26664 fr=0x46 to=0xff, WantAck=0, HopLim=3 Ch=0x8 encrypted rxtime=1720725859 hopStart=3 priority=10)
[RadioIf] S:PM:0x00000980,
[RadioIf] S:PM:0x00000988,
toRadioWriteCb data 0x2001ffea, len 31
PACKET FROM PHONE (id=0x8f26f650 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
Enqueued local (id=0x8f26f650 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725864)
Telling client we have new packets 14
BLE notify fromNum
[Router] Add packet record (id=0x8f26f650 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725864)
[Router] handleReceived(REMOTE) (id=0x8f26f650 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725864)
[Router] Module 'powerstress' wantsPacket=1
[Router] Received powerstress from=0x0, id=0x8f26f650, portnum=74, payloadlen=7
[Router] Received PowerStress cmd=81
[Router] PowerStress operation 80 already in progress! Can't start new command
[Router] Asked module 'powerstress' to send a response
[Router] Module 'powerstress' handled and skipped other processing
[Router] No one responded, send a nak
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f650,id=0x5fa26665
[Router] Enqueued local (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
[Router] Rx someone rebroadcasting for us (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
[Router] didn't find pending packet
[Router] Add packet record (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
[Router] handleReceived(REMOTE) (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
[Router] Module 'canned' wantsPacket=1
[Router] showing standard frames
[Router] Showing 0 module frames
[Router] Total frame count: 103
[Router] Added modules. numframes: 0
[Router] Finished building frames. numframes: 7
[Router] Module 'canned' considered
[Router] Module 'routing' wantsPacket=1
[Router] Received routing from=0x67f63246, id=0x5fa26665, portnum=5, payloadlen=2
[Router] Routing sniffing (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
[Router] Received a nak for 0x8f26f650, stopping retransmissions
[Router] Delivering rx packet (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
[Router] Update DB node 0x67f63246, rx_time=1720725864
[Router] Forwarding to phone (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
[Router] Module 'routing' considered
Telling client we have new packets 15
BLE notify fromNum
getFromRadio=STATE_SEND_PACKETS
[PowerStressModule] S:PS:0
getFromRadio=STATE_SEND_PACKETS
phone downloaded packet (id=0x5fa26665 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f650 rxtime=1720725864 priority=120)
toRadioWriteCb data 0x2001ffea, len 31
PACKET FROM PHONE (id=0x8f26f651 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
Enqueued local (id=0x8f26f651 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725869)
Telling client we have new packets 16
BLE notify fromNum
[Router] Add packet record (id=0x8f26f651 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725869)
[Router] handleReceived(REMOTE) (id=0x8f26f651 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725869)
[Router] Module 'powerstress' wantsPacket=1
[Router] Received powerstress from=0x0, id=0x8f26f651, portnum=74, payloadlen=7
[Router] Received PowerStress cmd=34
[Router] Asked module 'powerstress' to send a response
[Router] Module 'powerstress' handled and skipped other processing
[Router] No one responded, send a nak
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f651,id=0x5fa26666
[Router] Enqueued local (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
[Router] Rx someone rebroadcasting for us (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
[Router] didn't find pending packet
[Router] Add packet record (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
[Router] handleReceived(REMOTE) (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
[Router] Module 'canned' wantsPacket=1
[Router] showing standard frames
[Router] Showing 0 module frames
[Router] Total frame count: 103
[Router] Added modules. numframes: 0
[Router] Finished building frames. numframes: 7
[Router] Module 'canned' considered
[Router] Module 'routing' wantsPacket=1
[Router] Received routing from=0x67f63246, id=0x5fa26666, portnum=5, payloadlen=2
[Router] Routing sniffing (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
[Router] Received a nak for 0x8f26f651, stopping retransmissions
[Router] Delivering rx packet (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
[Router] Update DB node 0x67f63246, rx_time=1720725869
[Router] Forwarding to phone (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
[Router] Module 'routing' considered
[PowerStressModule] S:PS:34
[PowerStressModule] getFromRadio=STATE_SEND_PACKETS
[PowerStressModule] getFromRadio=STATE_SEND_PACKETS
[PowerStressModule] phone downloaded packet (id=0x5fa26666 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f651 rxtime=1720725869 priority=120)
Telling client we have new packets 17
BLE notify fromNum
[Power] Battery: usbPower=0, isCharging=0, batMv=3202, batPct=5
[PowerStressModule] S:PS:0
toRadioWriteCb data 0x2001ffea, len 31
PACKET FROM PHONE (id=0x8f26f652 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP)
Enqueued local (id=0x8f26f652 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725874)
Telling client we have new packets 18
[Router] Add packet record (id=0x8f26f652 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725874)
[Router] handleReceived(REMOTE) (id=0x8f26f652 fr=0x00 to=0x46, WantAck=1, HopLim=3 Ch=0x0 Portnum=74 WANTRESP rxtime=1720725875)
[Router] Module 'powerstress' wantsPacket=1
[Router] Received powerstress from=0x0, id=0x8f26f652, portnum=74, payloadlen=7
[Router] Received PowerStress cmd=32
[Router] Asked module 'powerstress' to send a response
[Router] Module 'powerstress' handled and skipped other processing
[Router] No one responded, send a nak
[Router] Alloc an err=8,to=0x67f63246,idFrom=0x8f26f652,id=0x5fa26667
[Router] Enqueued local (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
[Router] Rx someone rebroadcasting for us (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
[Router] didn't find pending packet
[Router] Add packet record (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
[Router] handleReceived(REMOTE) (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
[Router] Module 'canned' wantsPacket=1
[Router] showing standard frames
[Router] Showing 0 module frames
[Router] Total frame count: 103
[Router] Added modules. numframes: 0
[Router] Finished building frames. numframes: 7
[Router] Module 'canned' considered
[Router] Module 'routing' wantsPacket=1
[Router] Received routing from=0x67f63246, id=0x5fa26667, portnum=5, payloadlen=2
[Router] Routing sniffing (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
[Router] Received a nak for 0x8f26f652, stopping retransmissions
[Router] Delivering rx packet (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
[Router] Update DB node 0x67f63246, rx_time=1720725875
[Router] Forwarding to phone (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
[Router] Module 'routing' considered
[PowerStressModule] S:PS:32
Telling client we have new packets 19
BLE notify fromNum
getFromRadio=STATE_SEND_PACKETS
phone downloaded packet (id=0x5fa26667 fr=0x46 to=0x46, WantAck=0, HopLim=3 Ch=0x0 Portnum=5 requestId=8f26f652 rxtime=1720725875 priority=120)
toRadioWriteCb data 0x2001ffea, len 2
Disconnecting from phone
[PowerStressModule] S:PS:0

View File

Binary file not shown.

View File

@@ -0,0 +1,25 @@
"""Test analysis processing."""
import logging
import os
import sys
import pytest
from meshtastic.analysis.__main__ import main
@pytest.mark.unit
def test_analysis(caplog):
"""Test analysis processing"""
cur_dir = os.path.dirname(os.path.abspath(__file__))
slog_input_dir = os.path.join(cur_dir, "slog-test-input")
sys.argv = ["fakescriptname", "--no-server", "--slog", slog_input_dir]
with caplog.at_level(logging.DEBUG):
logging.getLogger().propagate = True # Let our testing framework see our logs
main()
assert "Exiting without running visualization server" in caplog.text

View File

@@ -1,5 +1,5 @@
"""Meshtastic unit tests for __main__.py"""
# pylint: disable=C0302,W0613
# pylint: disable=C0302,W0613,R0917
import logging
import os
@@ -726,8 +726,8 @@ def test_main_sendtext_with_dest(mock_findPorts, mock_serial, mocked_open, mock_
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_main_removeposition_invalid(capsys):
"""Test --remove-position with an invalid dest"""
def test_main_removeposition_remote(capsys):
"""Test --remove-position with a remote dest"""
sys.argv = ["", "--remove-position", "--dest", "!12345678"]
mt_config.args = sys.argv
iface = MagicMock(autospec=SerialInterface)
@@ -735,14 +735,15 @@ def test_main_removeposition_invalid(capsys):
main()
out, err = capsys.readouterr()
assert re.search(r"Connected to radio", out, re.MULTILINE)
assert re.search(r"remote nodes is not supported", out, re.MULTILINE)
assert 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 == ""
mo.assert_called()
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_main_setlat_invalid(capsys):
"""Test --setlat with an invalid dest"""
def test_main_setlat_remote(capsys):
"""Test --setlat with a remote dest"""
sys.argv = ["", "--setlat", "37.5", "--dest", "!12345678"]
mt_config.args = sys.argv
iface = MagicMock(autospec=SerialInterface)
@@ -750,7 +751,8 @@ def test_main_setlat_invalid(capsys):
main()
out, err = capsys.readouterr()
assert re.search(r"Connected to radio", out, re.MULTILINE)
assert re.search(r"remote nodes is not supported", out, re.MULTILINE)
assert 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 == ""
mo.assert_called()
@@ -769,7 +771,7 @@ def test_main_removeposition(capsys):
mocked_node.removeFixedPosition.side_effect = mock_removeFixedPosition
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:
main()
@@ -796,7 +798,7 @@ def test_main_setlat(capsys):
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
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:
main()
@@ -825,7 +827,7 @@ def test_main_setlon(capsys):
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
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:
main()
@@ -854,7 +856,7 @@ def test_main_setalt(capsys):
mocked_node.setFixedPosition.side_effect = mock_setFixedPosition
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:
main()
@@ -1606,7 +1608,7 @@ def test_main_onReceive_empty(caplog, capsys):
assert re.search(r"in onReceive", caplog.text, re.MULTILINE)
out, err = capsys.readouterr()
assert re.search(
r"Warning: There is no field 'to' in the packet.", out, re.MULTILINE
r"Warning: Error processing received packet: 'to'.", out, re.MULTILINE
)
assert err == ""
@@ -2650,3 +2652,64 @@ def test_tunnel_tunnel_arg(
out, err = capsys.readouterr()
assert re.search(r"Connected to radio", out, re.MULTILINE)
assert err == ""
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_set_favorite_node():
"""Test --set-favorite-node node"""
sys.argv = ["", "--set-favorite-node", "!12345678"]
mt_config.args = sys.argv
mocked_node = MagicMock(autospec=Node)
iface = MagicMock(autospec=SerialInterface)
iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
main()
mocked_node.setFavorite.assert_called_once_with("!12345678")
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_remove_favorite_node():
"""Test --remove-favorite-node node"""
sys.argv = ["", "--remove-favorite-node", "!12345678"]
mt_config.args = sys.argv
mocked_node = MagicMock(autospec=Node)
iface = MagicMock(autospec=SerialInterface)
iface.getNode.return_value = mocked_node
mocked_node.iface = iface
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
main()
mocked_node.removeFavorite.assert_called_once_with("!12345678")
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_set_ignored_node():
"""Test --set-ignored-node node"""
sys.argv = ["", "--set-ignored-node", "!12345678"]
mt_config.args = sys.argv
mocked_node = MagicMock(autospec=Node)
iface = MagicMock(autospec=SerialInterface)
iface.getNode.return_value = mocked_node
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
main()
mocked_node.setIgnored.assert_called_once_with("!12345678")
@pytest.mark.unit
@pytest.mark.usefixtures("reset_mt_config")
def test_remove_ignored_node():
"""Test --remove-ignored-node node"""
sys.argv = ["", "--remove-ignored-node", "!12345678"]
mt_config.args = sys.argv
mocked_node = MagicMock(autospec=Node)
iface = MagicMock(autospec=SerialInterface)
iface.getNode.return_value = mocked_node
mocked_node.iface = iface
with patch("meshtastic.serial_interface.SerialInterface", return_value=iface):
main()
mocked_node.removeIgnored.assert_called_once_with("!12345678")

View File

@@ -11,6 +11,8 @@ from ..protobuf import mesh_pb2, config_pb2
from .. import BROADCAST_ADDR, LOCAL_ADDR
from ..mesh_interface import MeshInterface, _timeago
from ..node import Node
from ..slog import LogSet
from ..powermon import SimPowerSupply
# TODO
# from ..config import Config
@@ -47,11 +49,15 @@ def test_MeshInterface(capsys):
iface.localNode.localConfig.lora.CopyFrom(config_pb2.Config.LoRaConfig())
# Also get some coverage of the structured logging/power meter stuff by turning it on as well
log_set = LogSet(iface, None, SimPowerSupply())
iface.showInfo()
iface.localNode.showInfo()
iface.showNodes()
iface.sendText("hello")
iface.close()
log_set.close()
out, err = capsys.readouterr()
assert re.search(r"Owner: None \(None\)", out, re.MULTILINE)
assert re.search(r"Nodes", out, re.MULTILINE)
@@ -167,7 +173,23 @@ def test_getNode_not_local_timeout(capsys):
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1
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 == ""
@@ -179,7 +201,7 @@ def test_sendPosition(caplog):
with caplog.at_level(logging.DEBUG):
iface.sendPosition()
iface.close()
assert re.search(r"p.time:", caplog.text, re.MULTILINE)
# assert re.search(r"p.time:", caplog.text, re.MULTILINE)
# TODO

View File

@@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
import pytest
from ..protobuf import localonly_pb2, config_pb2
from ..protobuf import admin_pb2, localonly_pb2, config_pb2
from ..protobuf.channel_pb2 import Channel # pylint: disable=E0611
from ..node import Node
from ..serial_interface import SerialInterface
@@ -231,7 +231,9 @@ def test_node(capsys):
@pytest.mark.unit
def test_exitSimulator(caplog):
"""Test exitSimulator"""
anode = Node("foo", "bar", noProto=True)
interface = MeshInterface()
interface.nodesByNum = {}
anode = Node(interface, "!ba400000", noProto=True)
with caplog.at_level(logging.DEBUG):
anode.exitSimulator()
assert re.search(r"in exitSimulator", caplog.text, re.MULTILINE)
@@ -240,7 +242,9 @@ def test_exitSimulator(caplog):
@pytest.mark.unit
def test_reboot(caplog):
"""Test reboot"""
anode = Node(MeshInterface(), 1234567890, noProto=True)
interface = MeshInterface()
interface.nodesByNum = {}
anode = Node(interface, 1234567890, noProto=True)
with caplog.at_level(logging.DEBUG):
anode.reboot()
assert re.search(r"Telling node to reboot", caplog.text, re.MULTILINE)
@@ -249,7 +253,9 @@ def test_reboot(caplog):
@pytest.mark.unit
def test_shutdown(caplog):
"""Test shutdown"""
anode = Node(MeshInterface(), 1234567890, noProto=True)
interface = MeshInterface()
interface.nodesByNum = {}
anode = Node(interface, 1234567890, noProto=True)
with caplog.at_level(logging.DEBUG):
anode.shutdown()
assert re.search(r"Telling node to shutdown", caplog.text, re.MULTILINE)
@@ -836,6 +842,34 @@ def test_requestChannel_localNode(caplog):
assert re.search(r"Requesting channel 0", 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
# def test_onResponseRequestCannedMessagePluginMesagePart1(caplog):
@@ -1392,6 +1426,60 @@ def test_requestChannel_localNode(caplog):
# assert err == ''
@pytest.mark.unit
@pytest.mark.parametrize("favorite", ["!1dec0ded", 502009325])
def test_set_favorite(favorite):
"""Test setFavorite"""
iface = MagicMock(autospec=SerialInterface)
node = Node(iface, 12345678)
amesg = admin_pb2.AdminMessage()
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
node.setFavorite(favorite)
assert amesg.set_favorite_node == 502009325
iface.sendData.assert_called_once()
@pytest.mark.unit
@pytest.mark.parametrize("favorite", ["!1dec0ded", 502009325])
def test_remove_favorite(favorite):
"""Test setFavorite"""
iface = MagicMock(autospec=SerialInterface)
node = Node(iface, 12345678)
amesg = admin_pb2.AdminMessage()
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
node.removeFavorite(favorite)
assert amesg.remove_favorite_node == 502009325
iface.sendData.assert_called_once()
@pytest.mark.unit
@pytest.mark.parametrize("ignored", ["!1dec0ded", 502009325])
def test_set_ignored(ignored):
"""Test setFavorite"""
iface = MagicMock(autospec=SerialInterface)
node = Node(iface, 12345678)
amesg = admin_pb2.AdminMessage()
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
node.setIgnored(ignored)
assert amesg.set_ignored_node == 502009325
iface.sendData.assert_called_once()
@pytest.mark.unit
@pytest.mark.parametrize("ignored", ["!1dec0ded", 502009325])
def test_remove_ignored(ignored):
"""Test setFavorite"""
iface = MagicMock(autospec=SerialInterface)
node = Node(iface, 12345678)
amesg = admin_pb2.AdminMessage()
with patch("meshtastic.admin_pb2.AdminMessage", return_value=amesg):
node.removeIgnored(ignored)
assert amesg.remove_ignored_node == 502009325
iface.sendData.assert_called_once()
# TODO
# @pytest.mark.unitslow
# def test_waitForConfig():

View File

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

View File

@@ -442,6 +442,13 @@ def test_is_windows11_false_win8_1(patched_platform, patched_release):
patched_platform.assert_called()
patched_release.assert_called()
@patch("platform.release", return_value="2022Server")
@patch("platform.system", return_value="Windows")
def test_is_windows11_false_winserver(patched_platform, patched_release):
"""Test is_windows11()"""
assert is_windows11() is False
patched_platform.assert_called()
patched_release.assert_called()
@pytest.mark.unit
@patch("platform.system", return_value="Linux")
@@ -556,7 +563,7 @@ def test_active_ports_on_supported_devices_mac_duplicates_check(mock_platform, m
def test_message_to_json_shows_all():
"""Test that message_to_json prints fields that aren't included in data passed in"""
actual = json.loads(message_to_json(mesh_pb2.MyNodeInfo()))
expected = { "myNodeNum": 0, "rebootCount": 0, "minAppVersion": 0 }
expected = { "myNodeNum": 0, "rebootCount": 0, "minAppVersion": 0, "deviceId": "", "pioEnv": "" }
assert actual == expected
@pytest.mark.unit
@@ -594,3 +601,72 @@ def test_roundtrip_snake_to_camel_camel_to_snake(a_string):
value0 = snake_to_camel(a_string=a_string)
value1 = camel_to_snake(a_string=value0)
assert a_string == value1, (a_string, value1)
@given(st.text())
def test_fuzz_camel_to_snake(a_string):
"""Test that camel_to_snake produces outputs with underscores for multi-word camelcase"""
result = camel_to_snake(a_string)
assert "_" in result or result == a_string.lower().replace("_", "")
@given(st.text())
def test_fuzz_snake_to_camel(a_string):
"""Test that snake_to_camel removes underscores"""
result = snake_to_camel(a_string)
assert "_" not in result or result == a_string.split("_")[0] + "".join(ele.title() for ele in a_string.split("_")[1:])
@given(st.text())
def test_fuzz_stripnl(s):
"""Test that stripnl always takes away newlines"""
result = stripnl(s)
assert "\n" not in result
@given(st.binary())
def test_fuzz_pskToString(psk):
"""Test that pskToString produces sane output for any bytes"""
result = pskToString(psk)
if len(psk) == 0:
assert result == "unencrypted"
elif len(psk) == 1:
b = psk[0]
if b == 0:
assert result == "unencrypted"
elif b == 1:
assert result == "default"
else:
assert result == f"simple{b - 1}"
else:
assert result == "secret"
@given(st.text())
def test_fuzz_fromStr(valstr):
"""Test that fromStr produces mostly-useful output given any string"""
result = fromStr(valstr)
if valstr.startswith("0x"):
assert isinstance(result, bytes)
elif valstr.startswith("base64:"):
assert isinstance(result, bytes)
elif len(valstr) == 0:
assert result == b''
elif valstr.lower() in {"t", "true", "yes"}:
assert result is True
elif valstr.lower() in {"f", "false", "no"}:
assert result is False
else:
try:
int(valstr)
assert isinstance(result, int)
except ValueError:
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
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

View File

@@ -11,7 +11,7 @@ import threading
import time
import traceback
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.message import Message
@@ -25,9 +25,13 @@ from meshtastic.supported_device import supported_devices
from meshtastic.version import get_active_version
"""Some devices such as a seger jlink or st-link we never want to accidentally open
0x1915 NordicSemi (PPK2)
0483 STMicroelectronics ST-LINK/V2
0136 SEGGER J-Link
1915 NordicSemi (PPK2)
0925 Lakeview Research Saleae Logic (logic analyzer)
04b4:602a Cypress Semiconductor Corp. Hantek DSO-6022BL (oscilloscope)
"""
blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915])
blacklistVids: Dict = dict.fromkeys([0x1366, 0x0483, 0x1915, 0x0925, 0x04b4])
"""Some devices are highly likely to be meshtastic.
0x239a RAK4631
@@ -35,21 +39,21 @@ blacklistVids = dict.fromkeys([0x1366, 0x0483, 0x1915])
whitelistVids = dict.fromkeys([0x239a, 0x303a])
def quoteBooleans(a_string):
def quoteBooleans(a_string: str) -> str:
"""Quote booleans
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'")
return tmp
def genPSK256():
def genPSK256() -> bytes:
"""Generate a random preshared key"""
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.
In that case we also allow "none", "default" or "random" (to have python generate one), or simpleN
"""
@@ -66,7 +70,7 @@ def fromPSK(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)
Returns: an int, bool, float, str or byte array (for strings of hex digits)
@@ -74,11 +78,12 @@ def fromStr(valstr):
Args:
valstr (string): A user provided string
"""
val: Any
if len(valstr) == 0: # Treat an emptystring as an empty bytes
val = bytes()
elif valstr.startswith("0x"):
# 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:"):
val = base64.b64decode(valstr[7:])
elif valstr.lower() in {"t", "true", "yes"}:
@@ -96,7 +101,15 @@ def fromStr(valstr):
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"""
if len(psk) == 0:
return "unencrypted"
@@ -118,12 +131,12 @@ def stripnl(s) -> str:
return " ".join(s.split())
def fixme(message):
def fixme(message: str) -> None:
"""Raise an exception for things that needs to be fixed"""
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"""
try:
closure()
@@ -141,7 +154,7 @@ def findPorts(eliminate_duplicates: bool=False) -> List[str]:
all_ports = serial.tools.list_ports.comports()
# look for 'likely' meshtastic devices
ports = list(
ports: List = list(
map(
lambda port: port.device,
filter(
@@ -180,12 +193,12 @@ class dotdict(dict):
class Timeout:
"""Timeout class"""
def __init__(self, maxSecs: int=20):
def __init__(self, maxSecs: int=20) -> None:
self.expireTime: Union[int, float] = 0
self.sleepInterval: float = 0.1
self.expireTimeout: int = maxSecs
def reset(self):
def reset(self) -> None:
"""Restart the waitForSet timer"""
self.expireTime = time.time() + self.expireTimeout
@@ -241,10 +254,20 @@ class Timeout:
time.sleep(self.sleepInterval)
return False
def waitForWaypoint(self, acknowledgment) -> bool:
"""Block until waypoint response is received. Returns True if waypoint response has been received."""
self.reset()
while time.time() < self.expireTime:
if getattr(acknowledgment, "receivedWaypoint", None):
acknowledgment.reset()
return True
time.sleep(self.sleepInterval)
return False
class Acknowledgment:
"A class that records which type of acknowledgment was just received, if any."
def __init__(self):
def __init__(self) -> None:
"""initialize"""
self.receivedAck = False
self.receivedNak = False
@@ -252,8 +275,9 @@ class Acknowledgment:
self.receivedTraceRoute = False
self.receivedTelemetry = False
self.receivedPosition = False
self.receivedWaypoint = False
def reset(self):
def reset(self) -> None:
"""reset"""
self.receivedAck = False
self.receivedNak = False
@@ -261,22 +285,24 @@ class Acknowledgment:
self.receivedTraceRoute = False
self.receivedTelemetry = False
self.receivedPosition = False
self.receivedWaypoint = False
class DeferredExecution:
"""A thread that accepts closures to run, and runs them as they are received"""
def __init__(self, name=None):
self.queue = Queue()
self.thread = threading.Thread(target=self._run, args=(), name=name)
def __init__(self, name) -> None:
self.queue: Queue = Queue()
# 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.daemon = True
self.thread.start()
def queueWork(self, runnable):
def queueWork(self, runnable) -> None:
"""Queue up the work"""
self.queue.put(runnable)
def _run(self):
def _run(self) -> None:
while True:
try:
o = self.queue.get()
@@ -296,7 +322,7 @@ def our_exit(message, return_value=1) -> NoReturn:
sys.exit(return_value)
def support_info():
def support_info() -> None:
"""Print out info that helps troubleshooting of the cli."""
print("")
print("If having issues with meshtastic cli or python library")
@@ -317,9 +343,6 @@ def support_info():
)
else:
print(f" meshtastic: v{the_version}")
if sys.version_info[0] == 3 and sys.version_info[1] < 9:
print(" *** this version of the CLI is the last that supports python 3.8 ***")
print(" *** please update your python installation ***")
print(f" Executable: {sys.argv[0]}")
print(
f" Python: {platform.python_version()} {platform.python_implementation()} {platform.python_compiler()}"
@@ -328,7 +351,7 @@ def support_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.
Will removed nested keys.
"""
@@ -343,33 +366,33 @@ def remove_keys_from_dict(keys, adict):
return adict
def hexstr(barray):
def hexstr(barray: bytes) -> str:
"""Print a string of hex digits"""
return ":".join(f"{x:02x}" for x in barray)
def ipstr(barray):
def ipstr(barray: bytes) -> str:
"""Print a string of ip digits"""
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)"""
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
val - base64 encoded value (ex: '/c0gFyhb'))
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):
val_as_bytes = base64.b64decode(val)
val_as_bytes: bytes = base64.b64decode(val)
return hexstr(val_as_bytes)
return val
def snake_to_camel(a_string):
def snake_to_camel(a_string: str) -> str:
"""convert snake_case to camelCase"""
# split underscore using split
temp = a_string.split("_")
@@ -378,16 +401,16 @@ def snake_to_camel(a_string):
return result
def camel_to_snake(a_string):
def camel_to_snake(a_string: str) -> str:
"""convert camelCase to snake_case"""
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"""
system = platform.system()
system: str = platform.system()
# print(f'system:{system}')
possible_devices = set()
@@ -445,9 +468,9 @@ def detect_supported_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"""
need_to_install_driver = False
need_to_install_driver: bool = False
if sd:
system = platform.system()
@@ -473,7 +496,7 @@ def detect_windows_needs_driver(sd, print_reason=False):
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.
ports is a list of ports
@@ -506,23 +529,23 @@ def eliminate_duplicate_port(ports):
return new_ports
def is_windows11():
def is_windows11() -> bool:
"""Detect if Windows 11"""
is_win11 = False
is_win11: bool = False
if platform.system() == "Windows":
if float(platform.release()) >= 10.0:
patch = platform.version().split(".")[2]
# in case they add some number suffix later, just get first 5 chars of patch
patch = patch[:5]
try:
try:
if float(platform.release()) >= 10.0:
patch = platform.version().split(".")[2]
# in case they add some number suffix later, just get first 5 chars of patch
patch = patch[:5]
if int(patch) >= 22000:
is_win11 = True
except Exception as e:
print(f"problem detecting win11 e:{e}")
except Exception as e:
print(f"problem detecting win11 e:{e}")
return is_win11
def get_unique_vendor_ids():
def get_unique_vendor_ids() -> Set[str]:
"""Return a set of unique vendor ids"""
vids = set()
for d in supported_devices:
@@ -531,7 +554,7 @@ def get_unique_vendor_ids():
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"""
sd = set()
for d in supported_devices:
@@ -540,11 +563,11 @@ def get_devices_with_vendor_id(vid):
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"""
ports = set()
baseports = set()
system = platform.system()
ports: Set = set()
baseports: Set = set()
system: str = platform.system()
# figure out what possible base ports there are
for d in sds:
@@ -602,13 +625,13 @@ def active_ports_on_supported_devices(sds, eliminate_duplicates=False):
for com_port in com_ports:
ports.add(com_port)
if eliminate_duplicates:
ports = eliminate_duplicate_port(list(ports))
ports.sort()
ports = set(ports)
portlist: List = eliminate_duplicate_port(list(ports))
portlist.sort()
ports = set(portlist)
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"""
ports = set()
@@ -633,20 +656,26 @@ def detect_windows_port(sd):
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."""
pypi_version = None
pypi_version: Optional[str] = None
try:
url = "https://pypi.org/pypi/meshtastic/json"
url: str = "https://pypi.org/pypi/meshtastic/json"
data = requests.get(url, timeout=5).json()
pypi_version = data["info"]["version"]
except Exception:
pass
act_version = get_active_version()
if pypi_version is None:
return None
try:
parsed_act_version = pkg_version.parse(act_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:
return pypi_version
@@ -658,5 +687,8 @@ def check_if_newer_version():
def message_to_json(message: Message, multiline: bool=False) -> str:
"""Return protobuf message as JSON. Always print all fields, even when not present in data."""
json = MessageToJson(message, always_print_fields_with_no_presence=True)
try:
json = MessageToJson(message, always_print_fields_with_no_presence=True)
except TypeError:
json = MessageToJson(message, including_default_value_fields=True) # type: ignore[call-arg] # pylint: disable=E1123
return stripnl(json) if not multiline else json

4107
poetry.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,31 @@
[tool.poetry]
name = "meshtastic"
version = "2.3.13"
version = "2.5.9"
description = "Python API & client shell for talking to Meshtastic devices"
authors = ["Meshtastic Developers <contact@meshtastic.org>"]
license = "GPL-3.0-only"
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.8,<3.13" # was 3.7 for production but, 3.8 is needed for pytap2, 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"
protobuf = ">=5.26.0"
dotmap = "^1.3.30"
pexpect = "^4.9.0"
pyqrcode = "^1.2.1"
protobuf = ">=4.21.12"
tabulate = "^0.9.0"
webencodings = "^0.5.1"
requests = "^2.31.0"
pyparsing = "^3.1.2"
pyyaml = "^6.0.1"
pypubsub = "^4.0.3"
bleak = "^0.21.1"
bleak = "^0.22.3"
packaging = "^24.0"
print-color = "^0.4.6"
argcomplete = { version = "^3.5.2", optional = true }
pyqrcode = { version = "^1.2.1", optional = true }
dotmap = { version = "^1.3.30", optional = true }
print-color = { version = "^0.4.6", optional = true }
dash = { version = "^2.17.1", optional = true }
pytap2 = { version = "^2.3.0", optional = true }
dash-bootstrap-components = { version = "^1.6.0", optional = true }
pandas = { version = "^2.2.2", optional = true }
pandas-stubs = { version = "^2.2.2.240603", optional = true }
wcwidth = {version = "^0.2.13", optional = true}
[tool.poetry.group.dev.dependencies]
hypothesis = "^6.103.2"
@@ -30,22 +34,53 @@ pytest-cov = "^5.0.0"
pdoc3 = "^0.10.0"
autopep8 = "^2.1.0"
pylint = "^3.2.3"
pytap2 = "^2.3.0"
pyinstaller = "^6.8.0"
mypy = "^1.10.0"
mypy-protobuf = "^3.6.0"
mypy-protobuf = "^3.3.0"
types-protobuf = "^5.26.0.20240422"
types-tabulate = "^0.9.0.20240106"
types-requests = "^2.31.0.20240406"
types-setuptools = "^69.5.0.20240423"
types-pyyaml = "^6.0.12.20240311"
pyarrow-stubs = "^10.0.1.7"
[tool.poetry.group.powermon]
optional = true
[tool.poetry.group.powermon.dependencies]
riden = { git = "https://github.com/geeksville/riden.git#1.2.1" }
ppk2-api = "^0.9.2"
parse = "^1.20.2"
pyarrow = "^16.1.0"
platformdirs = "^4.2.2"
# If you are doing power analysis you might want these extra devtools
[tool.poetry.group.analysis]
optional = true
[tool.poetry.group.analysis.dependencies]
jupyterlab = "^4.2.2"
matplotlib = "^3.9.0"
ipympl = "^0.9.4"
ipywidgets = "^8.1.3"
jupyterlab-widgets = "^3.0.11"
[tool.poetry.extras]
cli = ["pyqrcode", "print-color", "dotmap", "argcomplete", "wcwidth"]
tunnel = ["pytap2"]
analysis = ["dash", "dash-bootstrap-components", "pandas", "pandas-stubs"]
[tool.poetry.scripts]
meshtastic = "meshtastic.__main__:main"
mesh-tunnel = "meshtastic.__main__:tunnelMain [tunnel]"
mesh-analysis = "meshtastic.analysis.__main__:main [analysis]"
# "Poe the poet" (optional) provides an easy way of running non python tools inside the poetry virtualenv
# if you would like to use it run "pipx install poe"
# then you can do stuff like "poe code" to run vscode inside this environment
[tool.poe.tasks]
code = "code ."
juypter = "poetry run jupyter lab"
[build-system]
requires = ["poetry-core"]