Compare commits

...

449 Commits

Author SHA1 Message Date
Ben Meadors
08c0b0e940 Protos 2023-08-25 19:28:06 -05:00
github-actions
775cb4d650 bump version 2023-08-21 20:32:45 +00:00
Ben Meadors
52cc825b3e Update protobufs 2023-08-21 14:56:26 -05:00
Ben Meadors
ce3065b37d Merge pull request #457 from GUVWAF/reqTelemetry
Add `request-telemetry` option
2023-08-21 14:50:27 -05:00
GUVWAF
d6ee815183 Only wait if response is wanted 2023-08-21 21:09:12 +02:00
GUVWAF
0192eed76e Spacing 2023-08-21 21:04:30 +02:00
GUVWAF
9858fa1976 Add request-telemetry option 2023-08-21 20:58:27 +02:00
Ben Meadors
95bfc0b428 Merge pull request #455 from bmidgley/brad/error-exit
Exit with an error code on failure
2023-08-11 06:21:36 -05:00
Brad Midgley
130c82ae4f Exit with an error code on failure 2023-08-10 22:18:08 -06:00
Ben Meadors
3698f2e4fb Add bump_version back 2023-08-08 19:09:54 -05:00
Ben Meadors
f58f8bdb1d Comment bump version for now 2023-08-08 16:50:09 -05:00
Ben Meadors
e76c9852d6 2.2 changes and added device_metadata 2023-08-08 16:40:15 -05:00
Ben Meadors
0788c1fadc Update protos to 2.2 and add neighbor_info 2023-08-08 13:48:51 -05:00
github-actions
a8057ac670 bump version 2023-08-08 17:49:28 +00:00
Ben Meadors
bb067e0e1e Remove min app version check 2023-08-08 12:48:03 -05:00
github-actions
755e68040f bump version 2023-08-06 20:54:23 +00:00
Ben Meadors
1687a4cb90 Update protos 2023-08-06 15:53:37 -05:00
github-actions
03398c7e3b bump version 2023-08-01 01:45:41 +00:00
Ben Meadors
d27be003c7 Update protos 2023-07-31 20:12:28 -05:00
github-actions
8e39a00c30 bump version 2023-07-26 00:47:11 +00:00
Ben Meadors
055da95b8a Update protos and remove old max_channels check 2023-07-25 19:46:17 -05:00
Ben Meadors
0c2ad5c77c Merge pull request #453 from luzpaz/typos
Fix various source comment typos
2023-07-25 19:42:44 -05:00
luzpaz
0a88ca6a5c Fix various source comment typos
Found via `codespell -q 3`
2023-07-15 01:04:47 +00:00
github-actions
51079d4f25 bump version 2023-07-09 11:26:51 +00:00
Ben Meadors
4ca3b4bf58 Update protobufs 2023-07-09 06:25:24 -05:00
Ben Meadors
25d42d3361 Merge pull request #451 from hhartzer/python-3.11
Update Github CI removing end-of-lifed Python 3.6 and 3.7, add 3.11
2023-07-07 19:43:51 -05:00
Henrich Hartzer
a1bffe4f26 Update Github CI removing end-of-lifed Python 3.6 and 3.7, add 3.11 2023-07-07 15:44:19 +00:00
github-actions
b87630803f bump version 2023-06-28 01:16:18 +00:00
Ben Meadors
e2c7a2c32c Update protobufs 2023-06-27 20:14:40 -05:00
Ben Meadors
9dda5d6d2d Merge pull request #449 from pdxlocations/no-psk
Show Channels with No PSK on --info
2023-06-25 14:48:04 -05:00
Ben Meadors
c3be392533 Merge pull request #450 from GUVWAF/NeighborInfo
Setter for NeighborInfo Module
2023-06-25 14:47:22 -05:00
GUVWAF
f63f2e3e39 Merge remote-tracking branch 'origin/master' into NeighborInfo 2023-06-25 15:55:38 +02:00
github-actions
c8dbac7770 Update protobuf submodule 2023-06-25 13:53:04 +00:00
GUVWAF
959c597e33 Setter for NeighborInfo 2023-06-25 15:50:59 +02:00
pdxlocations
4b0ca13ad1 list channels with no psk 2023-06-15 21:29:46 -07:00
github-actions
811bfdcb8c bump version 2023-05-30 12:46:45 +00:00
Ben Meadors
79095dc243 Update protos 2023-05-30 07:38:53 -05:00
github-actions
ff9ab44796 bump version 2023-05-28 14:33:46 +00:00
Ben Meadors
77dea8ee67 Update protos 2023-05-28 09:32:34 -05:00
Ben Meadors
c2eb466792 Merge pull request #445 from mverch67/fix-444
Fix TypeError: 'NoneType'
2023-05-26 05:49:51 -05:00
Manuel Verch
b4405dc60e Fix TypeError: 'NoneType' 2023-04-11 17:24:45 +02:00
github-actions
dc3d43c57c bump version 2023-04-11 15:06:05 +00:00
Ben Meadors
be91e923ab Merge pull request #443 from GUVWAF/master
Option to wait for ACK after --sendtext
2023-04-11 10:05:02 -05:00
Ben Meadors
6408d65ae7 2.1.8 protos 2023-04-11 10:04:29 -05:00
GUVWAF
6506a1be1a Option to wait for ACK after --sendtext 2023-04-10 11:26:50 +02:00
github-actions
fc768fa3ea bump version 2023-04-06 14:20:28 +00:00
Ben Meadors
8012f979cb Update protos 2023-04-06 09:15:45 -05:00
Manuel Verch
06b87b376c Fix: showNodes crash #439 2023-04-06 13:13:47 +02:00
Thomas Göttgens
1a97dc6982 Merge pull request #433 from mverch67/master
Output  of --info "Nodes in mesh" to JSON Format - YOLO
2023-03-31 20:11:55 +02:00
Thomas Göttgens
6a181ae025 Merge branch 'master' into master 2023-03-31 20:10:56 +02:00
Thomas Göttgens
97aa8a8d74 fixing a few linter errors. 2023-03-31 20:09:59 +02:00
Thomas Göttgens
7e6f13f0a2 establish trunk format 2023-03-31 20:09:59 +02:00
Manuel
f0723ffbc0 Merge branch 'meshtastic:master' into master 2023-03-31 10:41:10 +02:00
Manuel Verch
78f85efe56 Added install_requires requests 2023-03-31 10:08:36 +02:00
Manuel Verch
72510088c9 reverted file belonging to another PR 2023-03-31 10:08:36 +02:00
Manuel Verch
edb537947a Add module "requests" as requirement 2023-03-31 10:08:36 +02:00
Manuel Verch
b8b268feac Check most current meshtastic version 2023-03-31 10:08:36 +02:00
Manuel Verch
c28b61fbb1 Output of --info "Nodes in mesh" to JSON Format 2023-03-31 10:08:36 +02:00
Thomas Göttgens
62843ea39c Ignore the generated files 2023-03-31 10:04:42 +02:00
Pavel Boldin
cb93669740 python: add QueueStatus support
This makes Python API parse QueueStatus packages returned by
the firmware allowing for TX Queue status control from the phone
or PC. This makes it easier to not overwhelm device with packages
that are going to be ignored anyways.

Signed-off-by: Pavel Boldin <pavel.b@techspark.engineering>
2023-03-31 08:55:58 +02:00
Manuel Verch
f154d223bf Output of --info "Nodes in mesh" to JSON Format 2023-03-30 14:37:01 +02:00
github-actions
4980a02ef6 bump version 2023-03-29 00:36:41 +00:00
Ben Meadors
c65a60d22d Update protos 2023-03-28 19:35:48 -05:00
github-actions
3a6475dc9d bump version 2023-03-26 20:30:40 +00:00
Ben Meadors
6f91479605 Re-cut protos for spelling fix 2023-03-26 15:29:14 -05:00
github-actions
bbebddea78 bump version 2023-03-26 19:45:12 +00:00
Ben Meadors
45be828183 Merge pull request #432 from mverch67/master
Fix remote_hardware configuration
2023-03-26 14:43:41 -05:00
Ben Meadors
e0753d4745 Added new smart broadcast properties 2023-03-26 14:43:07 -05:00
Manuel
21f2e185f2 Merge branch 'master' into master 2023-03-26 20:03:19 +02:00
Manuel Verch
4bfedf6aa9 added missing audio module remote config 2023-03-26 19:59:25 +02:00
Manuel Verch
193c3faca5 Fix remote_hardware configuration 2023-03-26 16:25:59 +02:00
Manuel Verch
1f47c225b3 Fix remote_hardware configuration 2023-03-25 21:37:10 +01:00
github-actions
1b372fca8d bump version 2023-03-19 18:38:37 +00:00
Ben Meadors
5e7b6027fa Bump to 2.1.x 2023-03-19 13:36:38 -05:00
Ben Meadors
64bd5deb4b Update protos to 2.1.3 2023-03-19 13:35:13 -05:00
Ben Meadors
d0fdb9b570 Merge pull request #428 from GUVWAF/remoteAdmin
Get and set methods for remote node using one admin message
2023-03-19 13:31:49 -05:00
Garth Vander Houwen
f37755209e Merge pull request #429 from GUVWAF/configSnake
Convert sections to snake_case when configuring
2023-03-19 09:17:30 -07:00
GUVWAF
297c0dbc0e Convert sections to snake_case when configuring 2023-03-19 15:25:08 +01:00
GUVWAF
7de17f7c94 Display that '--nodes' is not supported for a remote node 2023-03-19 14:42:44 +01:00
GUVWAF
55f6494681 Wait for ACK after setting remote config 2023-03-19 14:42:12 +01:00
GUVWAF
b2cfebc5a7 '--set' works as well, also chained commands 2023-03-18 21:12:05 +01:00
GUVWAF
802768e0cc Nicer printing 2023-03-18 16:40:27 +01:00
GUVWAF
f68e4112e1 Remote get method works 2023-03-18 15:20:00 +01:00
github-actions
19b4cd65ce bump version 2023-02-10 20:53:49 +00:00
Ben Meadors
ef12125785 Update protos 2023-02-10 13:26:29 -06:00
Garth Vander Houwen
014993f058 Merge pull request #417 from tobymurray/patch-2 2023-02-09 10:01:21 -08:00
github-actions
a10da7d3ed bump version 2023-01-31 16:54:53 +00:00
Ben Meadors
786dca9d13 Protos 2023-01-31 10:53:03 -06:00
github-actions
1cfd471abc bump version 2023-01-29 23:04:27 +00:00
Ben Meadors
9ff575c388 Hack to make python compile again 2023-01-29 15:19:03 -06:00
Toby Murray
b973e39ef7 Fix link to Getting Started Guide
Previous link (https://meshtastic.org/docs/software/python/python-installation) 404's
2023-01-16 13:53:31 -05:00
github-actions
75cdc5a36b bump version 2023-01-07 21:29:31 +00:00
Thomas Göttgens
93441a473f Merge pull request #414 from meshtastic/ringtone-support
get and set RTTTL ringtone
2023-01-04 20:23:28 +01:00
Thomas Göttgens
7b8a83ade7 get and set RTTTL ringtone 2023-01-04 19:52:24 +01:00
github-actions
2cf1ce2898 Update protobuf submodule 2023-01-04 17:19:27 +00:00
Sacha Weatherstone
e4890bfff2 Update README.md 2023-01-02 16:49:27 +11:00
github-actions
5c6ba26334 Update protobuf submodule 2022-12-29 16:18:30 +00:00
Thomas Göttgens
e6e3ad121d Merge pull request #413 from GUVWAF/metrics
Display battery level, channel and Tx air util. with --nodes
2022-12-29 17:13:00 +01:00
Thomas Göttgens
7133c859d6 Merge pull request #411 from mkinney/fix_pylint_warnings
fix pylint warnings
2022-12-29 17:12:00 +01:00
GUVWAF
8acf0b849a --nodes displays battery, channel and Tx air util. 2022-12-29 17:00:35 +01:00
github-actions
42f6818a02 bump version 2022-12-28 16:41:43 +00:00
github-actions
af043ef5c0 Update protobuf submodule 2022-12-28 16:37:24 +00:00
github-actions
4f46858643 bump version 2022-12-27 21:23:16 +00:00
Thomas Göttgens
ddc47fb8de Merge pull request #412 from meshtastic/remote-hardware
tryfix remote hardware
2022-12-27 22:16:57 +01:00
Thomas Göttgens
7ee134b819 tryfix remote hardware 2022-12-27 21:32:25 +01:00
Ben Meadors
254e9f4015 Typos 2022-12-22 14:35:48 -06:00
Mike Kinney
babd1242d5 fix pylint warnings 2022-12-20 11:18:40 -08:00
mkinney
cc99ea009e Update README.md
fix codecoverage link
2022-12-20 11:00:01 -08:00
github-actions
76407e11f8 bump version 2022-12-06 14:44:31 +00:00
Thomas Göttgens
57719ddd5e Merge pull request #407 from GUVWAF/traceRoute
Traceroute option
2022-12-06 15:36:49 +01:00
GUVWAF
d0b8b9ff1b Forgot to convert into TRACEROUTE_APP 2022-12-06 14:18:30 +01:00
github-actions
c5c9723208 Update protobuf submodule 2022-12-06 13:16:28 +00:00
Ben Meadors
9bceaafd9c Merge pull request #406 from GUVWAF/hopLimit
Set hopLimit to the config of the device
2022-12-05 20:10:27 -06:00
GUVWAF
7d3a9178ea Add traceroute option 2022-12-05 20:36:34 +01:00
GUVWAF
2c76c0cafa Remove setting hopLimit to default
Instead set is to config of device
2022-12-05 20:04:38 +01:00
GUVWAF
82977e9ef2 Fix --ch-set help message 2022-12-05 20:02:53 +01:00
Sacha Weatherstone
7a9c25da8e Merge pull request #404 from ahmedkadd/fix-script-typo
Rename protobufs shell script to fix typo
2022-12-02 14:39:18 +11:00
Ahmed Kaddoura
342c48fb16 Rename protobufs shell script to fix typo 2022-11-30 07:58:56 -08:00
github-actions
6bc955a403 bump version 2022-11-27 12:10:59 +00:00
Ben Meadors
741ba378ab Merge pull request #402 from GUVWAF/wantResponse
Only set wantResponse for admin packets
2022-11-27 06:10:01 -06:00
GUVWAF
c1054caf4a Only set wantResponse for admin packets 2022-11-27 12:44:58 +01:00
Ben Meadors
24b97d9277 Merge pull request #401 from GUVWAF/setowner
Remove automatic short owner naming and increase to 4 characters
2022-11-26 07:56:34 -06:00
GUVWAF
868fb64857 Only set is_licensed if long_name is set 2022-11-26 11:08:00 +01:00
GUVWAF
8729e97e1b Remove automatic short owner naming and increase to 4 characters 2022-11-26 10:52:20 +01:00
github-actions
aaed54393e bump version 2022-11-22 17:03:56 +00:00
Ben Meadors
d12776bb5f Merge pull request #400 from meshtastic/transaction-for-config
Transactions for editing settings
2022-11-21 15:27:31 -06:00
Ben Meadors
7829f6afca Args 2022-11-20 20:01:50 -06:00
Ben Meadors
4bd10bc102 Add begin / edit transactions for setting updates 2022-11-20 19:46:43 -06:00
Ben Meadors
3821e02f09 Update protos 2022-11-20 19:11:38 -06:00
Ben Meadors
97689da0b4 Merge pull request #395 from GUVWAF/master
Change how admin packets to remote nodes are handled
2022-11-19 15:36:33 -06:00
GUVWAF
5c75e74bf9 Don't request config and channels if not needed.
Instead, wait for an (implicit) ACK or NAK.
Applies to admin packets set-owner, reboot,
shutdown, factory-reset and reset-nodedb.
2022-11-19 21:19:06 +01:00
GUVWAF
388a46abf4 Merge branch 'meshtastic:master' into master 2022-11-15 13:26:30 +01:00
github-actions
6b89fc81a1 bump version 2022-11-10 03:23:52 +00:00
Ben Meadors
c9b5d5d697 Update protos and add audio 2022-11-09 21:20:38 -06:00
Ben Meadors
f16dd0e737 Update url 2022-11-08 18:25:49 -06:00
Ben Meadors
5ed19eff73 Create cleanup_artifacts.yml 2022-11-05 06:52:28 -05:00
github-actions
0b3605141d bump version 2022-11-03 13:02:34 +00:00
GUVWAF
f1df14ca92 lastTried channel should be its index 2022-11-03 09:28:15 +01:00
Ben Meadors
83776ceec5 Fix url 2022-11-02 21:20:25 -05:00
GUVWAF
7aff5e9ee5 Unset wantAck if you set wantResponse for Admin 2022-11-02 09:49:20 +01:00
github-actions
bf6be107d3 bump version 2022-11-01 12:26:50 +00:00
Ben Meadors
c24d1fe26b Missing a comma! 2022-11-01 07:26:04 -05:00
Ben Meadors
61f5468847 Update setup.py 2022-11-01 07:24:54 -05:00
github-actions
c713ce04b6 bump version 2022-11-01 12:17:41 +00:00
Ben Meadors
fe2b36e04b Merge pull request #392 from meshtastic/licence-change-&-2.0-prep
License change & 2.0 Prep
2022-11-01 07:08:09 -05:00
Ben Meadors
a720916df5 Update version in setup.py 2022-10-31 08:00:29 -05:00
Sacha Weatherstone
b2593e4bb1 Changes 2022-10-31 19:47:42 +10:00
github-actions
6e3c759e5c bump version 2022-10-30 01:08:22 +00:00
github-actions
a41f33e0bd bump version 2022-10-28 15:06:14 +00:00
Ben Meadors
111bf8dcbf Update protos 2022-10-28 10:03:26 -05:00
Ben Meadors
f18abb2fe6 Update protos 2022-10-26 08:53:32 -05:00
github-actions
b7093e176a bump version 2022-10-21 16:58:02 +00:00
Ben Meadors
cf7d37a454 Update __main__.py 2022-10-21 11:53:58 -05:00
github-actions
2af431e2eb bump version 2022-10-21 12:38:51 +00:00
Ben Meadors
3db64f7988 Merge pull request #387 from meshtastic/fun-fun
Print available options
2022-10-21 07:37:56 -05:00
Ben Meadors
7ef64d4250 Print options 2022-10-21 07:27:55 -05:00
Ben Meadors
363aa995a2 Push it real good 2022-10-20 17:13:26 -05:00
github-actions
696fa28e6f bump version 2022-10-16 13:46:40 +00:00
Ben Meadors
a908bdfc1c Protos 2022-10-16 08:44:30 -05:00
Ben Meadors
81b64ac908 Merge pull request #385 from GUVWAF/master
Use new config for pos-fields
2022-10-16 08:43:38 -05:00
GUVWAF
d5ccdc826f Use new config for pos-fields 2022-10-16 15:38:18 +02:00
Ben Meadors
fac4faaae8 Merge pull request #383 from meshtastic/reboot-ota
add reboot to ota command for testing purposes.
2022-10-10 10:28:59 -05:00
Thomas Göttgens
cfb8769746 add reeboot to ota command for testing purposes. This is a developer only command for now :-) 2022-10-10 16:59:01 +02:00
github-actions
c1b0e4e8d0 bump version 2022-10-09 15:10:27 +00:00
Ben Meadors
5683e31f6b Update protos 2022-10-09 10:00:51 -05:00
Ben Meadors
7909ad477b Merge pull request #380 from GUVWAF/master
Remove additional print statements
2022-10-09 09:59:51 -05:00
github-actions
f94dbf05ef bump version 2022-10-08 12:19:51 +00:00
Ben Meadors
a44b769390 Merge pull request #381 from meshtastic/config
Get export-configuration and configure working again
2022-10-08 07:19:03 -05:00
Ben Meadors
2a4816a9cd Get configuration yaml working again 2022-10-08 07:16:20 -05:00
GUVWAF
674fd92690 Merge branch 'master' of https://github.com/GUVWAF/Meshtastic-python 2022-10-08 13:21:02 +02:00
GUVWAF
cc29cab99a Remove additional print statements 2022-10-08 13:20:28 +02:00
Ben Meadors
bf803bb6e9 Merge pull request #379 from GUVWAF/master
Catch RoutingApp response for Admin packet
2022-10-08 05:57:56 -05:00
Ben Meadors
3c80fd0f02 Protos 2022-10-08 05:57:10 -05:00
GUVWAF
616a3ab706 Catch RoutingApp on requestGetMetadata 2022-10-08 12:47:48 +02:00
GUVWAF
8350cc611d Catch RoutingApp on requestChannel 2022-10-08 12:47:13 +02:00
github-actions
621feb749d bump version 2022-10-02 14:40:33 +00:00
Ben Meadors
f6731a435d Proto baggins 2022-10-01 08:36:43 -05:00
Ben Meadors
dcfe5fb558 Merge pull request #377 from GUVWAF/master
Add KnownProtocol for Simulator_App
2022-10-01 07:59:26 -05:00
GUVWAF
4fa80e9652 Add KnownProtocol for Simulator_App 2022-10-01 11:46:40 +02:00
github-actions
02851b6237 bump version 2022-09-25 18:52:40 +00:00
Ben Meadors
a74ec12445 Reset nodedb command 2022-09-25 13:46:11 -05:00
github-actions
262e921a81 bump version 2022-09-20 02:05:17 +00:00
Ben Meadors
0c7b9e10f4 Update setup.py 2022-09-19 21:04:22 -05:00
github-actions
ab8b930365 bump version 2022-09-18 13:07:41 +00:00
Ben Meadors
4ae49c68aa Add factory reset 2022-09-18 08:05:50 -05:00
github-actions
733f22d927 bump version 2022-09-18 01:00:31 +00:00
Ben Meadors
791131ea27 Version bump fix 2022-09-17 19:59:08 -05:00
github-actions
f17292221c bump version 2022-09-18 00:42:08 +00:00
Ben Meadors
b60a438c9d Update setup.py 2022-09-17 19:30:56 -05:00
github-actions
3b3a610375 bump version 2022-09-18 00:20:12 +00:00
Ben Meadors
38f928bdb7 Update setup.py 2022-09-17 19:19:23 -05:00
github-actions
6272e992a4 bump version 2022-09-18 00:09:40 +00:00
Ben Meadors
3263fbca28 Increment build number instead 2022-09-17 19:08:41 -05:00
Ben Meadors
f0e7af389c Update setup.py 2022-09-17 18:52:53 -05:00
github-actions
471dfc7a29 bump version 2022-09-13 01:19:06 +00:00
Ben Meadors
2a7c21c062 Protos sync 2022-09-12 20:16:14 -05:00
github-actions
03797e3336 bump version 2022-09-11 13:03:34 +00:00
Ben Meadors
dc1be12c86 Update protos 2022-09-11 08:01:55 -05:00
github-actions
4c83a43d64 bump version 2022-09-09 16:44:46 +00:00
Ben Meadors
e083cda3d9 Merge pull request #371 from meshtastic/big-proto-refactor
Update python to use reworked protos
2022-09-09 11:43:10 -05:00
Ben Meadors
c0006f888b Add alpha back for upcoming release 2022-09-09 11:05:10 -05:00
Ben Meadors
e2d2d3a347 Refactor 2022-09-09 11:02:58 -05:00
Ben Meadors
3fd50b0e44 Update python to use reworked protos 2022-09-09 09:49:04 -05:00
github-actions
4b0e3ae923 bump version 2022-09-08 21:43:54 +00:00
Ben Meadors
37f10cc0d4 Remove alpha 2022-09-08 15:55:19 -05:00
github-actions
998df265e6 bump version 2022-09-08 12:11:11 +00:00
Ben Meadors
d852981371 Med 2022-09-08 07:09:25 -05:00
Ben Meadors
afed5bd943 Fixed med preset names 2022-09-08 07:09:13 -05:00
Ben Meadors
97b9041b76 Merge pull request #368 from ghostop14/master
Fixes #367 MedFast Enum Not Defined
2022-09-08 06:58:18 -05:00
Ben Meadors
2dc14ef466 Merge pull request #369 from rohanki/master
Fix for --setalt --setlat --setlon
2022-09-05 20:30:29 -05:00
Rohan King
8e69c32a36 Fix for --setalt --setlat --setlon
fix for --setalt --setlat and --setlon
2022-09-06 10:23:19 +10:00
github-actions
42b33bea5b bump version 2022-09-03 14:03:36 +00:00
Ben Meadors
7fd101cbf8 Update protos and fix error 2022-09-03 09:02:36 -05:00
ghostop14
92ee9889b1 Fixes #367 MedFast Enum Not Defined 2022-09-02 10:44:59 -04:00
github-actions
b6e1610abe bump version 2022-08-27 13:37:05 +00:00
Ben Meadors
bde5db9c51 Fixed ch-set command in setPref 2022-08-27 08:14:48 -05:00
github-actions
148ae49ded bump version 2022-08-25 18:25:24 +00:00
Ben Meadors
d1f8365da1 Merge pull request #363 from meshtastic/channel-url
Channel setting fixed
2022-08-25 13:24:20 -05:00
Ben Meadors
59fc294d66 Channel setting fixed 2022-08-25 13:22:05 -05:00
github-actions
7473b4e18c bump version 2022-08-17 02:49:05 +00:00
Ben Meadors
58aafcf3f1 Merge pull request #361 from Douile/dev-change-channel
Fix changing modem_preset from CLI shortcuts
2022-08-16 08:58:56 -05:00
Douile
776fc57c35 Fix changing modem_preset from CLI shortcuts
Previously the channel shortcuts (e.g. --ch-shortfast) would overrwrite
the entire lora config, this meant you lost other configured things such
as region. This patch changes that so that just modem_preset is
overwritten, and also fixes the call to write config that was missing
an argument.
2022-08-16 14:55:17 +01:00
Ben Meadors
b3f752a3c4 Merge pull request #360 from meshtastic/bt-canned
Bluetooth and canned messages changes
2022-08-15 21:01:44 -05:00
Ben Meadors
4965ec7f1d Missed some spots 2022-08-15 20:54:11 -05:00
Ben Meadors
0746acd34f Canned messages and bluetooth sections 2022-08-15 19:01:37 -05:00
github-actions
49b1c4816e bump version 2022-08-11 17:46:19 +00:00
Ben Meadors
7c6e87e161 Merge pull request #356 from meshtastic/increase-timeouts
Only write single config
2022-08-11 12:45:12 -05:00
Ben Meadors
b548700c0b Only write single config 2022-08-11 12:43:41 -05:00
github-actions
f278a30003 bump version 2022-08-11 16:53:26 +00:00
Ben Meadors
22bbe67d24 Merge pull request #355 from meshtastic/increase-timeouts
Increase delay for reliability
2022-08-11 11:51:09 -05:00
Ben Meadors
a2861a133e Increase delay for reliability 2022-08-11 11:48:57 -05:00
github-actions
03aab10786 bump version 2022-08-09 11:29:44 +00:00
Ben Meadors
95e768efd5 Merge pull request #354 from meshtastic/device-metadata
Add get device metadata admin message
2022-08-08 07:07:47 -05:00
Ben Meadors
6644e86be9 Add get device metadata admin message 2022-08-08 07:04:29 -05:00
Ben Meadors
c8363cd476 Merge pull request #352 from Douile/dev-export-valid-yaml
Replace the primative print based config generator with proper yaml serialization
2022-08-05 10:14:42 -05:00
Ben Meadors
62efe1ab7f Merge pull request #353 from Douile/dev-fix-lint-errors-2
Fix lint errors
2022-08-05 10:13:58 -05:00
Douile
01e643ad2f Fix lint errors 2022-08-05 13:48:07 +01:00
Douile
e4078e84d7 Fix linting errors caused by this PR 2022-08-05 13:34:25 +01:00
Ben Meadors
abfcbe2a90 Merge pull request #350 from Douile/dev-base64-decode
Allow setting values from base64 values
2022-07-28 17:42:08 -05:00
Douile
e06d8bbc06 Use pyyaml to generate valid configuration output
The custom yaml generator made an invalid yaml file that needed to be
fixed, by using the already included pyyaml library we can make sure the
output is valid.
2022-07-28 15:11:35 +01:00
Ben Meadors
a78cdde86f Merge pull request #349 from Douile/dev-fix-errors
Fix crash when trying to set value that doesn't exist
2022-07-27 18:53:44 -05:00
Douile
10517ac94d Allow setting values from base64 values
By prefixing a value with "base64:" e.g. ("base64:AQ==") it will now be
decoded to raw bytes. This is useful when setting psk as that is often
shown as base64.
2022-07-27 15:12:23 +01:00
Douile
a572699588 Fix crash when trying to set value that doesn't exist 2022-07-27 15:09:58 +01:00
github-actions
d11fb47734 bump version 2022-07-02 19:52:35 +00:00
Ben Meadors
92c7b2db69 Merge pull request #348 from meshtastic/moduleconfg
100ms delays to prevent overloading the serial
2022-07-02 14:43:24 -05:00
Ben Meadors
ff94ad968c 100ms delays to prevent overloading the serial 2022-07-02 14:41:09 -05:00
github-actions
c6071c57ec bump version 2022-06-30 01:17:37 +00:00
Ben Meadors
e3e3562c2c Merge pull request #347 from meshtastic/moduleconfg
Module config progress
2022-06-29 20:16:37 -05:00
Ben Meadors
06b5b8fa83 It works, I think 2022-06-29 20:01:48 -05:00
Ben Meadors
4b95b0ff30 Module config progress 2022-06-29 18:35:06 -05:00
Thomas Göttgens
42f2ed571d Merge pull request #346 from meshtastic/patch-1
small issues corrected
2022-06-21 19:38:01 +02:00
Thomas Göttgens
f3791c5c6d - make segemented config print entire name on screen
- rename wifi password to psk
2022-06-21 19:29:54 +02:00
github-actions
4cff344971 bump version 2022-06-21 16:18:33 +00:00
Ben Meadors
594b307e94 Merge pull request #344 from meshtastic/patch-1
Get and Set general attributes working.
2022-06-21 08:23:58 -05:00
Ben Meadors
8a5fd16469 Don't blow away config please 2022-06-21 08:05:15 -05:00
Ben Meadors
4fa93989fa Don't need to pass interface down 2022-06-19 15:41:21 -05:00
Ben Meadors
032072d2f3 Started on set 2022-06-19 09:55:09 -05:00
Ben Meadors
ce7b1d9916 Get preferences now working 2022-06-19 08:17:28 -05:00
Thomas Göttgens
d015da3ca1 Get and Set general attributes almost working. 2022-06-18 14:53:04 +02:00
Thomas Göttgens
a956c8068c Merge pull request #343 from meshtastic/patch-1
Untangle Modem Presets from Channels
2022-06-18 13:41:38 +02:00
Thomas Göttgens
2124e292f1 Untangle Modem Presets from Channels 2022-06-18 13:38:08 +02:00
Ben Meadors
115739a9bb Merge pull request #342 from meshtastic/nanopb-upgrade
Github action protos version
2022-06-17 08:39:00 -05:00
Ben Meadors
cc2c16b957 Github action protos version 2022-06-17 08:33:13 -05:00
Thomas Göttgens
b9245c6c1f Merge pull request #341 from meshtastic/patch-1
almost working
2022-06-17 12:40:06 +02:00
Thomas Göttgens
d21f7811fa where did that came from? 2022-06-17 12:37:50 +02:00
Thomas Göttgens
8dbd6431f7 almost working 2022-06-17 12:31:18 +02:00
Thomas Göttgens
c7b2bbf700 Merge pull request #340 from meshtastic/patch-1
remove "Request Settings" during init, team settings and deprecated settings.
2022-06-17 10:59:54 +02:00
Thomas Göttgens
682fdb7ef4 Remove test for deprecated option 2022-06-17 10:57:39 +02:00
Thomas Göttgens
9c79f9d80e Wait again, since we don't request config manually 2022-06-17 10:54:31 +02:00
Thomas Göttgens
c55b1188e8 remove "Request Settings" during init, team settings and deprecated settings. 2022-06-17 10:40:40 +02:00
github-actions
d5e4eaf2d8 bump version 2022-06-17 05:53:24 +00:00
mkinney
d49cc74828 Merge pull request #339 from mkinney/hack_13
Hack 13
2022-06-16 22:49:22 -07:00
Mike Kinney
47781fa1e0 remove unused import 2022-06-16 22:46:04 -07:00
Mike Kinney
cc98ed1084 comment yet another test 2022-06-16 22:43:42 -07:00
Mike Kinney
bec8cf2b13 change the d to an e 2022-06-16 22:40:48 -07:00
Mike Kinney
5bfebbe436 comment out code until it does not complain 2022-06-16 22:38:23 -07:00
Mike Kinney
c02a4d8138 Merge remote-tracking branch 'upstream/master' 2022-06-16 19:35:58 -07:00
mkinney
15aae34d65 Merge pull request #338 from mkinney/bump_nanopb
Bump nanopb
2022-06-16 19:30:46 -07:00
Mike Kinney
89b0426a2b fix warnings now that tests have been commented out 2022-06-16 19:26:52 -07:00
Mike Kinney
e1f1cab5a5 comment out failing tests (for now) 2022-06-16 19:22:13 -07:00
Mike Kinney
132fb4fe5f get pylint to pass 2022-06-16 19:13:18 -07:00
Mike Kinney
f1843649ba bump nanopb 2022-06-16 18:57:40 -07:00
Mike Kinney
43b7bbb5b3 Merge remote-tracking branch 'upstream/master' 2022-06-16 18:37:40 -07:00
Ben Meadors
b79b7ceb40 Correct config 2022-06-16 20:31:14 -05:00
Ben Meadors
2a546f8899 Merge master 2022-06-16 16:57:31 -05:00
Ben Meadors
a4a0740903 Regen protos 2022-06-16 16:57:05 -05:00
Ben Meadors
075ad01e46 Merge pull request #336 from meshtastic/change-default-baud
Changed baud to 115200
2022-06-15 11:46:31 -05:00
Ben Meadors
1296a1ce28 Changed baud to 115200 2022-06-15 10:58:55 -05:00
Thomas Göttgens
d510ba15c5 Trunk Protos 2022-06-09 12:20:13 +02:00
Jm Casler
decc887cb5 updating proto submodule to latest 2022-05-21 17:10:27 -07:00
Mike Kinney
163f7eeaaa update proto 2022-05-19 10:09:25 -07:00
Ben Meadors
d15667d5ce Regened new protos 2022-05-19 07:24:39 -05:00
Sacha Weatherstone
39a7869524 Update README.md 2022-05-12 21:52:04 +10:00
Sacha Weatherstone
c9464d2595 Update README.md 2022-05-12 21:28:38 +10:00
Sacha Weatherstone
717de611b9 Update ci.yml 2022-05-12 21:28:09 +10:00
Jm Casler
85869cf595 updating proto submodule to latest 2022-05-01 18:33:44 -07:00
Jm Casler
03ca28e0d2 updating proto submodule to latest 2022-05-01 08:42:04 -07:00
github-actions
a3bdf976bb bump version 2022-04-27 18:40:29 +00:00
mkinney
bf6eec626c Merge pull request #328 from mkinney/master
add nano_g1
2022-04-27 11:35:44 -07:00
Mike Kinney
cd0bdbbd9c add nano_g1 2022-04-27 11:29:05 -07:00
github-actions
e7faa85476 bump version 2022-04-27 12:33:09 +00:00
Ben Meadors
e419f95910 Merge pull request #327 from meshtastic/device-updates-release
Updated protos
2022-04-27 07:21:56 -05:00
Ben Meadors
ee613104f1 Updated protos 2022-04-27 07:19:08 -05:00
github-actions
93a8722b22 bump version 2022-04-25 07:20:32 +00:00
Thomas Göttgens
73b06248aa Update __init__.py
Device firmware requires 20300 now - change coming from android.
2022-04-25 09:15:33 +02:00
Jm Casler
82169f9d58 updating proto submodule to latest 2022-04-20 18:01:40 -07:00
github-actions
1fa71f2e2d bump version 2022-04-18 23:38:33 +00:00
mkinney
af599ab320 Merge pull request #326 from mkinney/master
update protos
2022-04-18 16:37:41 -07:00
Mike Kinney
99eed4bb5c remove send_owner_interval 2022-04-18 16:35:06 -07:00
Jm Casler
d8deb90527 updating proto submodule to latest 2022-04-18 12:29:23 -07:00
Mike Kinney
37a0010714 Merge remote-tracking branch 'upstream/master' 2022-04-18 11:53:16 -07:00
Mike Kinney
3c298df5ce update protos 2022-04-18 11:49:43 -07:00
Jm Casler
c6a8618d33 updating proto submodule to latest 2022-04-15 22:30:23 -07:00
Jm Casler
f19ddf66b8 updating proto submodule to latest 2022-04-12 21:10:46 -07:00
github-actions
203d5246eb bump version 2022-04-11 21:27:30 +00:00
mkinney
b78276e49a Merge pull request #323 from mkinney/master
fix smoke1 tests; regen protobufs
2022-04-11 14:26:00 -07:00
Mike Kinney
bd4d309d89 update codecov 2022-04-11 14:23:49 -07:00
Mike Kinney
804c09b6c5 bump to v2 of codecov 2022-04-11 14:18:38 -07:00
Mike Kinney
92202807f7 update/regen protobufs 2022-04-11 14:07:04 -07:00
Mike Kinney
0c92460163 got smoke1 tests to pass 2022-04-11 13:58:56 -07:00
Mike Kinney
8bb570d222 fix smoke1 tests 2022-04-11 13:17:39 -07:00
mkinney
f1abce9eff Merge pull request #322 from amerinoj/master
fixed set and get canned message
2022-04-11 12:48:50 -07:00
chst
1fc46a3c02 fixed set and get canned message
replacement the entries "get canned_message plugin" to get_canned_message_module_" in node.py
2022-04-10 19:21:57 +02:00
Jm Casler
1d45adfb27 Update README.md 2022-04-07 16:31:27 -07:00
mkinney
8365ad5d1b Merge pull request #316 from mkinney/master
fix tests for 1.3
2022-03-31 14:22:41 -07:00
Mike Kinney
00346ea441 fix tests for 1.3 2022-03-31 14:18:53 -07:00
github-actions
ae70d34dd6 bump version 2022-03-31 17:29:03 +00:00
mkinney
e0ef62d1b3 Merge pull request #315 from mkinney/master
detect devices only using vendor id
2022-03-31 10:28:04 -07:00
Mike Kinney
02e8467fdd detect devices only using vendor id 2022-03-31 10:24:17 -07:00
github-actions
48e7f8c755 bump version 2022-03-30 21:00:08 +00:00
mkinney
19b607b3f2 Merge pull request #314 from mkinney/master
add epaper; fix product id on 5005/4631
2022-03-30 13:59:15 -07:00
Mike Kinney
1d827ab2bf add epaper; fix product id on 5005/4631 2022-03-30 13:56:19 -07:00
github-actions
7af886cf07 bump version 2022-03-30 18:48:11 +00:00
mkinney
a76ad6c686 Merge pull request #313 from mkinney/master
regen code from latest protobufs
2022-03-30 11:36:33 -07:00
Mike Kinney
3d9a55add3 regen code from updated protobufs 2022-03-30 11:32:47 -07:00
Mike Kinney
4ceac5e847 Merge remote-tracking branch 'upstream/master' 2022-03-30 11:25:45 -07:00
Jm Casler
0939022cb4 updating proto submodule to latest 2022-03-29 21:44:51 -07:00
Jm Casler
f7afb9ff15 updating proto submodule to latest 2022-03-29 20:04:43 -07:00
Jm Casler
13fd4ba614 updating proto submodule to latest 2022-03-26 09:32:15 -07:00
Jm Casler
2f80c9866a updating proto submodule to latest 2022-03-25 22:30:43 -07:00
Jm Casler
e2bca647ae updating proto submodule to latest 2022-03-25 22:23:58 -07:00
mkinney
ec2467486c Merge pull request #302 from raldi/master
Added examples/tcp_gps_example.py
2022-03-22 15:10:25 -07:00
Mike Schiraldi
3332271a97 lint 2022-03-22 12:41:50 -07:00
github-actions
7fdfd782d8 bump version 2022-03-21 17:50:05 +00:00
Mike Kinney
af7bf7ff7f add wifi min length check 2022-03-21 10:48:27 -07:00
github-actions
b6dc4d0bd2 bump version 2022-03-16 03:26:41 +00:00
mkinney
8ec5dbbf38 Merge pull request #307 from mkinney/master
there is no tlora_v2.1
2022-03-15 20:13:51 -07:00
Mike Kinney
0c760f3721 there is no tlora_v2.1 2022-03-15 20:11:14 -07:00
mkinney
c45568731f Merge pull request #306 from mkinney/master
regen protobuf code; tlora v1 vendor/product id fix
2022-03-15 19:57:45 -07:00
Mike Kinney
ef4c9dc338 fix the vendor and product id for tlora v1 2022-03-15 19:54:18 -07:00
Mike Kinney
89de553aba regen python based on updated protobufs 2022-03-15 19:30:54 -07:00
mkinney
247e7b2605 Merge pull request #303 from mkinney/supported_devices_fixes
fix some tlora entries
2022-03-15 19:27:42 -07:00
Mike Kinney
f81ba64b91 fix some tlora entries 2022-03-15 19:25:04 -07:00
Jm Casler
d6fbca1bf1 updating proto submodule to latest 2022-03-15 15:46:02 -07:00
Mike Schiraldi
ef9441e7d2 Added examples/tcp_gps_example.py 2022-03-14 23:32:42 -07:00
Jm Casler
6fbf78fa19 updating proto submodule to latest 2022-03-14 18:05:28 -07:00
github-actions
e38a614c37 bump version 2022-03-11 23:08:22 +00:00
mkinney
d8b9665946 Merge pull request #299 from mkinney/master
update to latest protobufs
2022-03-11 15:07:34 -08:00
Mike Kinney
e655beb01c update to latest protobufs 2022-03-11 15:04:09 -08:00
mkinney
aa1dcb7f99 Merge pull request #298 from mkinney/master
refactor code to util; add duplicate check
2022-03-08 10:53:33 -08:00
Mike Kinney
1a2519d647 refactor code to util 2022-03-08 10:45:11 -08:00
Mike Kinney
a3572efaa6 add duplicate check 2022-03-08 10:44:14 -08:00
github-actions
e28f0d5509 bump version 2022-03-08 06:16:57 +00:00
mkinney
789a14054f Update release.yml 2022-03-07 22:16:03 -08:00
github-actions
4655b5c732 bump version 2022-03-08 06:04:02 +00:00
mkinney
81ebd8c8e7 Merge pull request #296 from mkinney/fix_release_version
keep sha ref and use it for subsequent checkouts
2022-03-07 22:02:28 -08:00
Mike Kinney
7998520e4a keep sha ref and use it for subsequent checkouts 2022-03-07 21:59:46 -08:00
mkinney
460196a62b Merge pull request #295 from mkinney/bug_when_ports_not_sorted
do not checkout the code again
2022-03-07 21:48:28 -08:00
Mike Kinney
22f43851bd do not checkout the code again 2022-03-07 21:44:11 -08:00
mkinney
91bb63eb97 Merge pull request #294 from mkinney/bug_when_ports_not_sorted
fix when ports are not sorted
2022-03-07 21:25:42 -08:00
Mike Kinney
ede1b5f08b fix when ports are not sorted 2022-03-07 21:21:41 -08:00
mkinney
a26c157c65 Merge pull request #292 from mkinney/handle_ignore_incoming
handle ignore_incoming repeated field
2022-03-03 17:14:09 -08:00
Mike Kinney
bcf00a1f8d handle ignore_incoming repeated field 2022-03-03 17:11:22 -08:00
Jm Casler
749a94e6e4 updating proto submodule to latest 2022-03-02 18:49:49 -08:00
github-actions
fc9d1e077c bump version 2022-03-02 21:56:05 +00:00
mkinney
a40f0b4038 Merge pull request #291 from mkinney/1_3_python
min req of python is 3.7 now; bump nanopb version
2022-03-02 13:55:09 -08:00
Mike Kinney
dbf54396f3 min req of python is 3.7 now; bump nanopb version 2022-03-02 13:29:19 -08:00
github-actions
3fe881e45a bump version 2022-03-02 19:43:05 +00:00
mkinney
af03c5163c Merge pull request #290 from mkinney/master
change version per pypi reqs
2022-03-02 11:42:09 -08:00
Mike Kinney
58de84945f change version per pypi reqs 2022-03-02 11:39:07 -08:00
github-actions
a2d4252002 bump version 2022-03-02 19:32:52 +00:00
mkinney
0689fc19a9 Merge pull request #289 from mkinney/1_3_initial
got lint and pytest working
2022-03-02 11:31:49 -08:00
Mike Kinney
68530f6e11 change medium to mid 2022-03-02 11:29:38 -08:00
Mike Kinney
fd752bedc5 change version format 2022-03-02 11:24:13 -08:00
Mike Kinney
cac880eb1a change to alpha 2022-03-02 11:15:47 -08:00
Mike Kinney
2c66cd4a95 change the name not the package 2022-03-02 10:50:08 -08:00
Mike Kinney
64e428f182 change package name and version 2022-03-02 10:44:35 -08:00
Mike Kinney
6f2efdcefe got lint and pytest working 2022-03-02 10:28:07 -08:00
github-actions
9214b2ffcc bump version 2022-03-02 00:20:31 +00:00
mkinney
cfb14d4b77 Merge pull request #287 from mkinney/changes_to_smokevirt
increase timeout; ensure secondary channel is deleted before trying t…
2022-03-01 16:19:14 -08:00
Mike Kinney
5965615e17 increase timeout; ensure secondary channel is deleted before trying to add it 2022-03-02 00:04:21 +00:00
Jm Casler
40eb7d8515 updating proto submodule to latest 2022-02-28 22:03:42 -08:00
Jm Casler
e2f36a9bea updating proto submodule to latest 2022-02-28 19:36:02 -08:00
Jm Casler
a0ba644488 updating proto submodule to latest 2022-02-27 10:01:44 -08:00
Jm Casler
2f67f344b7 updating proto submodule to latest 2022-02-27 09:57:16 -08:00
Jm Casler
8bb208aed1 updating proto submodule to latest 2022-02-27 09:52:34 -08:00
Jm Casler
48b145a592 updating proto submodule to latest 2022-02-27 01:00:38 -08:00
Jm Casler
65de9200fb updating proto submodule to latest 2022-02-27 00:48:57 -08:00
Jm Casler
e51d7a7a18 updating proto submodule to latest 2022-02-27 00:38:09 -08:00
Jm Casler
c4af50d63a updating proto submodule to latest 2022-02-26 21:22:33 -08:00
Jm Casler
933fe8953a updating proto submodule to latest 2022-02-26 21:09:20 -08:00
mkinney
66aa492d50 Merge pull request #283 from mkinney/add_cm
add cm
2022-02-25 23:44:13 -08:00
Mike Kinney
730934f520 add cm 2022-02-25 23:36:05 -08:00
mkinney
d2ec09eaf8 Merge pull request #281 from prampec/extend_canned_message_length
Canned message - Extend messages length
2022-02-25 22:58:57 -08:00
mkinney
7fd3b313b2 Update release.yml 2022-02-25 22:15:36 -08:00
github-actions
89f1549741 bump version 2022-02-26 06:11:26 +00:00
github-actions
e91015f5c8 bump version 2022-02-26 06:07:50 +00:00
mkinney
d9c3edfb12 Update release.yml 2022-02-25 22:06:27 -08:00
mkinney
cbf9696f47 Merge pull request #282 from mkinney/master
improve the release process
2022-02-25 22:04:48 -08:00
Mike Kinney
24e556b9a7 improve the release process 2022-02-25 22:01:21 -08:00
mkinney
eaf29512b6 Update standalone_readme.txt 2022-02-25 21:38:23 -08:00
Jm Casler
0c1e0ec375 updating proto submodule to latest 2022-02-22 17:10:04 -08:00
Balazs Kelemen
b56a054f50 Canned message - Extend messages length 2022-02-21 22:06:03 +01:00
Jm Casler
33ff4e36de updating proto submodule to latest 2022-02-20 01:32:53 -08:00
Jm Casler
bf879934e6 updating proto submodule to latest 2022-02-19 23:48:24 -08:00
Jm Casler
b878fa3a80 updating proto submodule to latest 2022-02-19 23:17:50 -08:00
Jm Casler
901849f176 updating proto submodule to latest 2022-02-19 23:10:53 -08:00
Jm Casler
371c0d22c2 updating proto submodule to latest 2022-02-19 22:55:38 -08:00
mkinney
4f7f38e0a7 Update setup.py 2022-02-18 11:16:49 -08:00
mkinney
85dca2e14e Merge pull request #278 from mkinney/fix_smoke1
add detection of duplicate ports to findPorts; fix smoke1 test
2022-02-18 11:16:25 -08:00
Mike Kinney
e53a5023f1 add detection of duplicate ports to findPorts; fix smoke1 test 2022-02-18 11:13:48 -08:00
mkinney
ce8b75d96d Merge pull request #277 from mkinney/win11_delay_only
only delay on win11
2022-02-18 10:12:16 -08:00
Mike Kinney
26f65c4fee only delay on win11 2022-02-18 10:08:53 -08:00
mkinney
1ba1e51ca4 Merge pull request #276 from mkinney/keep_devpath_on_serial
keep the devPath used on serial connections
2022-02-18 10:03:43 -08:00
Mike Kinney
f674afc412 keep the devPath used on serial connections 2022-02-18 09:59:09 -08:00
mkinney
db90b898e1 Update setup.py 2022-02-16 10:16:10 -08:00
mkinney
71621c2225 Merge pull request #269 from mkinney/two_serials
if we have a duplicate serial port use the appropriate one
2022-02-16 10:12:57 -08:00
Mike Kinney
ed36fca4a2 remove testing prints 2022-02-16 10:10:32 -08:00
Mike Kinney
fdd3699ba5 if we have a duplicate serial port use the appropriate one 2022-02-16 10:08:57 -08:00
Jm Casler
7d4b39643b updating proto submodule to latest 2022-02-14 20:35:46 -08:00
Jm Casler
7698cd2c7d updating proto submodule to latest 2022-02-14 20:08:31 -08:00
Jm Casler
03c744df54 updating proto submodule to latest 2022-02-14 19:55:21 -08:00
Jm Casler
90978d1f35 updating proto submodule to latest 2022-02-14 19:42:06 -08:00
Jm Casler
68a2bf271a updating proto submodule to latest 2022-02-14 17:59:45 -08:00
Jm Casler
8301384c53 updating proto submodule to latest 2022-02-14 16:59:26 -08:00
Jm Casler
045592212a updating proto submodule to latest 2022-02-14 16:56:59 -08:00
mkinney
45d879e607 Update setup.py 2022-02-12 21:19:20 -08:00
mkinney
3e44ee1eba Merge pull request #266 from mkinney/increase_sleep_for_win11
increase sleep for win11
2022-02-12 21:18:53 -08:00
Mike Kinney
36c5fc8d3c increase sleep for win11 2022-02-12 21:16:11 -08:00
mkinney
38ceb85ad9 Update setup.py 2022-02-10 15:15:46 -08:00
mkinney
6d18d9226d Merge pull request #263 from mkinney/add_rak11200
Add rak11200
2022-02-10 15:15:15 -08:00
Mike Kinney
6d5ed2129a add rak11200 2022-02-10 15:12:28 -08:00
Mike Kinney
5cfb6ffa11 add examples to lint and fix warnings 2022-02-10 14:51:22 -08:00
mkinney
363b268ccf Update setup.py 2022-02-03 10:03:31 -08:00
mkinney
e6cddb0084 Merge pull request #260 from mkinney/update_protobufs
update protobufs to latest
2022-02-03 10:01:31 -08:00
Mike Kinney
5e77bf62b9 update protobufs to latest 2022-02-03 09:59:03 -08:00
github-actions
4905c0b179 Update protobuf submodule 2022-02-03 15:00:36 +00:00
Ben Meadors
43ab3be804 Update update_protobufs.yml 2022-02-03 08:59:32 -06:00
github-actions
14a6e581bc Update protobuf submodule 2022-02-03 14:38:02 +00:00
104 changed files with 8327 additions and 4539 deletions

4
.gitattributes vendored Normal file
View File

@@ -0,0 +1,4 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
*.{sh,[sS][hH]} text eol=lf

View File

@@ -1,4 +1,4 @@
name: Linting and Tests name: CI
on: on:
push: push:
branches: branches:
@@ -13,11 +13,10 @@ jobs:
strategy: strategy:
matrix: matrix:
python-version: python-version:
- "3.6"
- "3.7"
- "3.8" - "3.8"
- "3.9" - "3.9"
- "3.10" - "3.10"
- "3.11"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Python 3 - name: Install Python 3
@@ -35,31 +34,30 @@ jobs:
which meshtastic which meshtastic
meshtastic --version meshtastic --version
- name: Run pylint - name: Run pylint
run: pylint meshtastic run: pylint meshtastic examples/ --ignore-patterns ".*_pb2.py$"
- name: Run tests with pytest - name: Run tests with pytest
run: pytest --cov=meshtastic run: pytest --cov=meshtastic
- name: Generate coverage report - name: Generate coverage report
run: | run: |
pytest --cov=meshtastic --cov-report=xml pytest --cov=meshtastic --cov-report=xml
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v2
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml files: ./coverage.xml
flags: unittests flags: unittests
name: codecov-umbrella name: codecov-umbrella
yml: ./codecov.yml
fail_ci_if_error: true fail_ci_if_error: true
verbose: true
validate: validate:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: python-version:
- "3.6"
- "3.7"
- "3.8" - "3.8"
- "3.9" - "3.9"
- "3.10" - "3.10"
- "3.11"
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Python 3 - name: Install Python 3

20
.github/workflows/cleanup_artifacts.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Remove old artifacts
on:
schedule:
# Every day at 1am
- cron: "0 1 * * *"
workflow_dispatch:
jobs:
remove-old-artifacts:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Remove old artifacts
uses: c-hive/gha-remove-artifacts@v1
with:
age: "1 month"
skip-tags: true

View File

@@ -1,40 +1,52 @@
name: Make Release name: Make Release
on: on: workflow_dispatch
workflow_dispatch:
inputs:
version:
description: "Release version (Example: 1.0.0, must match 'version' in setup.py)"
required: true
default: '1.0.0'
jobs: jobs:
release_create: release_create:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps:
- name: Create GitHub release
uses: actions/create-release@v1
id: create_release
with:
draft: true
prerelease: true
release_name: ${{ github.event.inputs.version}}
tag_name: ${{ github.event.inputs.version}}
body: |
Autogenerated by github action, developer should edit as required before publishing...
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
outputs: outputs:
version: ${{ steps.get_version.outputs.version }}
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
new_sha: ${{ steps.commit_updated.outputs.sha }}
publish_to_pypi:
runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Bump version
run: >-
bin/bump_version.py
- name: Commit updated version.py
id: commit_updated
run: |
git config --global user.name 'github-actions'
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 setup.py
git commit -m "bump 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: >-
bin/show_version.py
- name: Create GitHub release
uses: actions/create-release@v1
id: create_release
with:
draft: true
prerelease: true
release_name: Meshtastic Python ${{ steps.get_version.outputs.version }}
tag_name: ${{ steps.get_version.outputs.version }}
body: |
Autogenerated by github action, developer should edit as required before publishing...
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Python 3.9 - name: Set up Python 3.9
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
@@ -62,14 +74,14 @@ jobs:
user: __token__ user: __token__
password: ${{ secrets.PYPI_API_TOKEN }} password: ${{ secrets.PYPI_API_TOKEN }}
build-and-publish-mac: build-and-publish-mac:
runs-on: macos-latest runs-on: macos-latest
needs: release_create needs: release_create
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
with:
ref: ${{ needs.release_create.outputs.new_sha }}
- name: Set up Python 3.9 - name: Set up Python 3.9
uses: actions/setup-python@v2 uses: actions/setup-python@v2
@@ -112,9 +124,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: release_create needs: release_create
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
with:
ref: ${{ needs.release_create.outputs.new_sha }}
- name: Set up Python 3.9 - name: Set up Python 3.9
uses: actions/setup-python@v2 uses: actions/setup-python@v2
@@ -137,7 +150,7 @@ jobs:
asset_path: dist/meshtastic asset_path: dist/meshtastic
asset_name: meshtastic_ubuntu asset_name: meshtastic_ubuntu
asset_content_type: application/zip asset_content_type: application/zip
- name: Add readme.txt to release - name: Add readme.txt to release
uses: actions/upload-release-asset@v1 uses: actions/upload-release-asset@v1
env: env:
@@ -146,15 +159,16 @@ jobs:
upload_url: ${{ needs.release_create.outputs.upload_url }} upload_url: ${{ needs.release_create.outputs.upload_url }}
asset_path: standalone_readme.txt asset_path: standalone_readme.txt
asset_name: readme.txt asset_name: readme.txt
asset_content_type: text/plain asset_content_type: text/plain
build-and-publish-windows: build-and-publish-windows:
runs-on: windows-latest runs-on: windows-latest
needs: release_create needs: release_create
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v3
with:
ref: ${{ needs.release_create.outputs.new_sha }}
- name: Set up Python 3.9 - name: Set up Python 3.9
uses: actions/setup-python@v2 uses: actions/setup-python@v2

View File

@@ -15,21 +15,22 @@ jobs:
run: | run: |
git pull --recurse-submodules git pull --recurse-submodules
git submodule update --remote --recursive git submodule update --remote --recursive
- name: Download nanopb - name: Download nanopb
run: | run: |
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.4-linux-x86.tar.gz wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.6-linux-x86.tar.gz
tar xvzf nanopb-0.4.4-linux-x86.tar.gz tar xvzf nanopb-0.4.6-linux-x86.tar.gz
mv nanopb-0.4.4-linux-x86 nanopb-0.4.4 mv nanopb-0.4.6-linux-x86 nanopb-0.4.6
- name: Re-generate protocol buffers - name: Re-generate protocol buffers
run: | run: |
./bin/regen-protos.sh ./bin/regen-protobufs.sh
- name: Commit update - name: Commit update
run: | run: |
git config --global user.name 'github-actions' git config --global user.name 'github-actions'
git config --global user.email 'bot@noreply.github.com' 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 remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git add proto git add protobufs
git add meshtastic
git commit -m "Update protobuf submodule" && git push || echo "No changes to commit" git commit -m "Update protobuf submodule" && git push || echo "No changes to commit"

2
.gitignore vendored
View File

@@ -5,7 +5,7 @@ dist
*.egg-info *.egg-info
log_* log_*
.eggs .eggs
nanopb-0.4.4 nanopb-*
.*swp .*swp
.coverage .coverage
*.py-E *.py-E

6
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "proto"] [submodule "protobufs"]
path = proto path = protobufs
url = https://github.com/meshtastic/Meshtastic-protobufs.git url = http://github.com/meshtastic/protobufs

View File

@@ -7,7 +7,7 @@
# Add files or directories matching the regex patterns to the blacklist. The # Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths. # regex matches against base names, not paths.
ignore-patterns=mqtt_pb2.py,channel_pb2.py,environmental_measurement_pb2.py,admin_pb2.py,radioconfig_pb2.py,deviceonly_pb2.py,apponly_pb2.py,remote_hardware_pb2.py,portnums_pb2.py,mesh_pb2.py,storeforward_pb2.py,cannedmessages_pb2.py ignore-patterns=mqtt_pb2.py,channel_pb2.py,telemetry_pb2.py,admin_pb2.py,config_pb2.py,deviceonly_pb2.py,apponly_pb2.py,remote_hardware_pb2.py,portnums_pb2.py,mesh_pb2.py,storeforward_pb2.py,cannedmessages_pb2.py,module_config_pb2.py,localonly_pb2.py,node.py,device_metadata_pb2.py
@@ -23,8 +23,7 @@ ignore-patterns=mqtt_pb2.py,channel_pb2.py,environmental_measurement_pb2.py,admi
# no Warning level messages displayed, use"--disable=all --enable=classes # no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W" # --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,no-self-use,pointless-string-statement,too-few-public-methods,broad-except,no-else-return,no-else-raise,bare-except 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
[BASIC] [BASIC]
@@ -41,7 +40,7 @@ bad-names=foo,bar,baz,toto,tutu,tata
max-line-length=150 max-line-length=150
# Maximum number of lines in a module # Maximum number of lines in a module
max-module-lines=1200 max-module-lines=1600

7
.trunk/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
*out
*logs
*actions
*notifications
plugins
user_trunk.yaml
user.yaml

View File

@@ -0,0 +1,2 @@
[settings]
profile=black

View File

@@ -0,0 +1,10 @@
# Autoformatter friendly markdownlint config (all formatting rules disabled)
default: true
blank_lines: false
bullet: false
html: false
indentation: false
line_length: false
spaces: false
url: false
whitespace: false

View File

@@ -0,0 +1,7 @@
enable=all
source-path=SCRIPTDIR
disable=SC2154
# If you're having issues with shellcheck following source, disable the errors via:
# disable=SC1090
# disable=SC1091

View File

@@ -0,0 +1,10 @@
rules:
quoted-strings:
required: only-when-needed
extra-allowed: ["{|}"]
empty-values:
forbid-in-block-mappings: true
forbid-in-flow-mappings: true
key-duplicates: {}
octal-values:
forbid-implicit-octal: true

5
.trunk/configs/ruff.toml Normal file
View File

@@ -0,0 +1,5 @@
# Generic, formatter-friendly config.
select = ["B", "D3", "D4", "E", "F"]
# Never enforce `E501` (line length violations). This should be handled by formatters.
ignore = ["E501"]

39
.trunk/trunk.yaml Normal file
View File

@@ -0,0 +1,39 @@
version: 0.1
cli:
version: 1.7.0
plugins:
sources:
- id: trunk
ref: v0.0.14
uri: https://github.com/trunk-io/plugins
lint:
ignore:
- linters: [ALL]
paths:
# Ignore generated files
- meshtastic/*_pb2.py
enabled:
- actionlint@1.6.23
- black@23.3.0
- git-diff-check
- gitleaks@8.16.2
- isort@5.12.0
- markdownlint@0.33.0
- prettier@2.8.7
- pylint@2.17.1
- ruff@0.0.260
- shellcheck@0.9.0
- shfmt@3.5.0
- yamllint@1.30.0
runtimes:
enabled:
- go@1.19.5
- node@18.12.1
- python@3.10.8
actions:
disabled:
- trunk-announce
- trunk-check-pre-push
- trunk-fmt-pre-commit
enabled:
- trunk-upgrade-available

93
.vscode/launch.json vendored
View File

@@ -42,7 +42,87 @@
"request": "launch", "request": "launch",
"module": "meshtastic", "module": "meshtastic",
"justMyCode": true, "justMyCode": true,
"args": ["--debug" ] "args": ["--debug"]
},
{
"name": "meshtastic debug getPref",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--get", "power.is_power_saving"]
},
{
"name": "meshtastic debug getPref telemetry",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--get", "telemetry.environment_update_interval"]
},
{
"name": "meshtastic debug info",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--info"]
},
{
"name": "meshtastic debug set region",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "lora.region", "TW"]
},
{
"name": "meshtastic debug set bluetooth fixed pin",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "bluetooth.fixed_pin", "555555"]
},
{
"name": "meshtastic debug get bluetooth fixed pin",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--get", "bluetooth.fixed_pin"]
},
{
"name": "meshtastic debug setPref",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "power.is_power_saving", "1"]
},
{
"name": "meshtastic debug setPref telemetry.environment_measurement_enabled",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "telemetry.environment_measurement_enabled", "1"]
},
{
"name": "meshtastic debug setPref telemetry.environment_screen_enabled",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "telemetry.environment_screen_enabled", "1"]
},
{
"name": "meshtastic debug setPref telemetry",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--set", "telemetry.environment_measurement_enabled", "1"]
}, },
{ {
"name": "meshtastic setpref", "name": "meshtastic setpref",
@@ -52,6 +132,15 @@
"justMyCode": true, "justMyCode": true,
"args": ["--debug", "--setchan", "psk", ""] "args": ["--debug", "--setchan", "psk", ""]
}, },
{
"name": "meshtastic --ch-set",
"type": "python",
"request": "launch",
"module": "meshtastic",
"justMyCode": true,
"args": ["--debug", "--ch-set", "channel_num", "0", "--ch-index", "0"]
},
{ {
"name": "meshtastic seturl", "name": "meshtastic seturl",
"type": "python", "type": "python",
@@ -92,7 +181,7 @@
"module": "meshtastic", "module": "meshtastic",
"justMyCode": true, "justMyCode": true,
"args": ["--debug", "--sendtext", "pytest"] "args": ["--debug", "--sendtext", "pytest"]
} },
{ {
"name": "meshtastic showNodes", "name": "meshtastic showNodes",
"type": "python", "type": "python",

View File

@@ -6,6 +6,10 @@ test:
virt: virt:
pytest -m smokevirt pytest -m smokevirt
# run the smoke1 test (after doing a factory reset and unplugging/replugging in device)
smoke1:
pytest -m smoke1 -s -vv
# local install # local install
install: install:
pip install . pip install .
@@ -16,12 +20,18 @@ docs:
# lint the codebase # lint the codebase
lint: lint:
pylint meshtastic pylint meshtastic examples
# show the slowest unit tests # show the slowest unit tests
slow: slow:
pytest -m unit --durations=5 pytest -m unit --durations=5
protobufs: FORCE
git submodule update --init --recursive
git pull --rebase
git submodule update --remote --merge
./bin/regen-protobufs.sh
# run the coverage report and open results in a browser # run the coverage report and open results in a browser
cov: cov:
pytest --cov-report html --cov=meshtastic pytest --cov-report html --cov=meshtastic

View File

@@ -1,15 +1,23 @@
# Meshtastic-python # Meshtastic Python
[![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/meshtastic/Meshtastic-python)
![Unit Tests](https://github.com/meshtastic/Meshtastic-python/actions/workflows/ci.yml/badge.svg)
[![codecov](https://codecov.io/gh/meshtastic/Meshtastic-python/branch/master/graph/badge.svg?token=TIWPJL73KV)](https://codecov.io/gh/meshtastic/Meshtastic-python) [![codecov](https://codecov.io/gh/meshtastic/Meshtastic-python/branch/master/graph/badge.svg?token=TIWPJL73KV)](https://codecov.io/gh/meshtastic/Meshtastic-python)
![PyPI - Downloads](https://img.shields.io/pypi/dm/meshtastic) ![PyPI - Downloads](https://img.shields.io/pypi/dm/meshtastic)
[![CI](https://img.shields.io/github/actions/workflow/status/meshtastic/python/ci.yml?branch=master&label=actions&logo=github&color=yellow)](https://github.com/meshtastic/python/actions/workflows/ci.yml)
[![CLA assistant](https://cla-assistant.io/readme/badge/meshtastic/python)](https://cla-assistant.io/meshtastic/python)
[![Fiscal Contributors](https://opencollective.com/meshtastic/tiers/badge.svg?label=Fiscal%20Contributors&color=deeppink)](https://opencollective.com/meshtastic/)
[![Vercel](https://img.shields.io/static/v1?label=Powered%20by&message=Vercel&style=flat&logo=vercel&color=000000)](https://vercel.com?utm_source=meshtastic&utm_campaign=oss)
A python client for using [Meshtastic](https://www.meshtastic.org) devices. This small library (and example application) provides an easy API for sending and receiving messages over mesh radios. It also provides access to any of the operations/data available in the device user interface or the Android application. Events are delivered using a publish-subscribe model, and you can subscribe to only the message types you are interested in. ## Overview
Full documentation including examples [here](https://meshtastic.org/docs/software/python/python-installation). A Python client for use with Meshtastic devices.
This small library (and example application) provides an easy API for sending and receiving messages over mesh radios.
It also provides access to any of the operations/data available in the device user interface or the Android application.
Events are delivered using a publish-subscribe model, and you can subscribe to only the message types you are interested in.
The library api is documented [here](https://meshtastic-python.vercel.app/meshtastic/index.html) **[Getting Started Guide](https://meshtastic.org/docs/software/python/cli/installation)**
**[Documentation/API Reference](https://python.meshtastic.org/)**
[![Powered by Vercel](https://raw.githubusercontent.com/abumalick/powered-by-vercel/master/powered-by-vercel.svg)](https://vercel.com?utm_source=meshtastic&utm_campaign=oss) ## Stats
![Alt](https://repobeats.axiom.co/api/embed/c71ee8fc4a79690402e5d2807a41eec5e96d9039.svg "Repobeats analytics image")

View File

@@ -34,7 +34,7 @@ Basic functionality is complete now.
- DONE add fromId and toId to received messages dictionaries - DONE add fromId and toId to received messages dictionaries
- make command line options for displaying/changing config - make command line options for displaying/changing config
- update nodedb as nodes change - update nodedb as nodes change
- radioConfig - getter/setter syntax: https://www.python-course.eu/python3_properties.php - localConfig - getter/setter syntax: https://www.python-course.eu/python3_properties.php
- let user change radio params via commandline options - let user change radio params via commandline options
- keep nodedb up-to-date based on received MeshPackets - keep nodedb up-to-date based on received MeshPackets
- handle radio reboots and redownload db when that happens. Look for a special FromRadio.rebooted packet - handle radio reboots and redownload db when that happens. Look for a special FromRadio.rebooted packet

30
bin/bump_version.py Executable file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env python
"""Bump the version number"""
import re
version_filename = "setup.py"
lines = None
with open(version_filename, "r", encoding="utf-8") as f:
lines = f.readlines()
with open(version_filename, "w", encoding="utf-8") as f:
for line in lines:
if line.lstrip().startswith("version="):
# get rid of quotes around the version
line = line.replace('"', "")
# get rid of trailing comma
line = line.replace(",", "")
# split on '='
words = line.split("=")
# split the version into parts (by period)
v = words[1].split(".")
build_num = re.findall(r"\d+", v[2])[0]
new_build_num = str(int(build_num) + 1)
ver = f"{v[0]}.{v[1]}.{v[2].replace(build_num, new_build_num)}".replace(
"\n", ""
)
f.write(f' version="{ver}",\n')
else:
f.write(line)

View File

@@ -1,4 +1,4 @@
# Note: Docs are generated from this command below, albeit from Vercel. # Note: Docs are generated from this command below, albeit from Vercel.
# The docs/ dir is not used and is no longer commited. # The docs/ dir is not used and is no longer committed.
# see sachaw if you have questions # see sachaw if you have questions
pdoc3 --html -f --output-dir docs meshtastic pdoc3 --html -f --output-dir docs meshtastic

19
bin/regen-protobufs.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
#Uncomment to run hack
#gsed -i 's/import "\//import ".\//g' ./protobufs/meshtastic/*
#gsed -i 's/package meshtastic;//g' ./protobufs/meshtastic/*
./nanopb-0.4.6/generator-bin/protoc -I=protobufs --python_out ./ ./protobufs/meshtastic/*.proto
# workaround for import bug in protoc https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-690618628
if [[ $OSTYPE == 'darwin'* ]]; then
sed -i '' -E 's/^(import.*_pb2)/from . \1/' meshtastic/*.py
# automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/protobufs/issues/27)
sed -i '' -E "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py
else
sed -i -e 's/^import.*_pb2/from . \0/' meshtastic/*.py
# automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/protobufs/issues/27)
sed -i -e "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py
fi

View File

@@ -1,15 +0,0 @@
#!/bin/bash
./nanopb-0.4.4/generator-bin/protoc -I=proto --python_out meshtastic `ls proto/*.proto`
# workaround for import bug in protoc https://github.com/protocolbuffers/protobuf/issues/1491#issuecomment-690618628
if [[ $OSTYPE == 'darwin'* ]]; then
sed -i '' -E 's/^(import.*_pb2)/from . \1/' meshtastic/*.py
# automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/Meshtastic-protobufs/issues/27)
sed -i '' -E "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py
else
sed -i -e 's/^import.*_pb2/from . \0/' meshtastic/*.py
# automate the current workaround (may be related to Meshtastic-protobufs issue #27 https://github.com/meshtastic/Meshtastic-protobufs/issues/27)
sed -i -e "s/^None = 0/globals()['None'] = 0/" meshtastic/mesh_pb2.py
fi

20
bin/show_version.py Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env python
"""Show the version number"""
version_filename = "setup.py"
lines = None
with open(version_filename, "r", encoding="utf-8") as f:
lines = f.readlines()
for line in lines:
if line.lstrip().startswith("version="):
# get rid of quotes around the version
line2 = line.replace('"', "")
# get rid of the trailing comma
line2 = line2.replace(",", "")
# split on =
words = line2.split("=")
# Note: This format is for github actions
print(f"::set-output name=version::{words[1].strip()}")

View File

@@ -7,4 +7,4 @@ python3 setup.py sdist bdist_wheel
python3 -m twine check dist/* python3 -m twine check dist/*
# test the upload # test the upload
python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*
echo "view the upload at https://test.pypi.org/ it it looks good upload for real" echo "view the upload at https://test.pypi.org/ it it looks good upload for real"

View File

@@ -11,7 +11,6 @@ location:
userPrefs: userPrefs:
region: 1 region: 1
isAlwaysPowered: 'true' isAlwaysPowered: "true"
sendOwnerInterval: 2
screenOnSecs: 31536000 screenOnSecs: 31536000
waitBluetoothSecs: 31536000 waitBluetoothSecs: 31536000

View File

@@ -2,17 +2,42 @@
owner: Bob TBeam owner: Bob TBeam
owner_short: BOB owner_short: BOB
channel_url: https://www.meshtastic.org/d/#CgUYAyIBAQ channel_url: https://www.meshtastic.org/e/#CgMSAQESCDgBQANIAVAe
location: location:
lat: 35.88888 lat: 35.88888
lon: -93.88888 lon: -93.88888
alt: 304 alt: 304
user_prefs: config:
region: 1 bluetooth:
is_always_powered: 'true' enabled: true
send_owner_interval: 2 fixedPin: 123456
screen_on_secs: 31536000 device:
wait_bluetooth_secs: 31536000 serialEnabled: true
location_share: 'LocEnabled' display:
screenOnSecs: 600
lora:
region: US
hopLimit: 3
txEnabled: true
txPower: 30
network:
ntpServer: 0.pool.ntp.org
position:
gpsAttemptTime: 900
gpsEnabled: true
gpsUpdateInterval: 120
positionBroadcastSecs: 900
positionBroadcastSmartEnabled: true
positionFlags: 3
power:
lsSecs: 300
meshSdsTimeoutSecs: 7200
minWakeSecs: 10
sdsSecs: 4294967295
module_config:
telemetry:
deviceUpdateInterval: 900
environmentUpdateInterval: 900

View File

@@ -3,6 +3,7 @@
""" """
import sys import sys
import meshtastic import meshtastic
import meshtastic.serial_interface import meshtastic.serial_interface
@@ -15,6 +16,6 @@ if len(sys.argv) != 1:
iface = meshtastic.serial_interface.SerialInterface() iface = meshtastic.serial_interface.SerialInterface()
if iface.nodes: if iface.nodes:
for n in iface.nodes.values(): for n in iface.nodes.values():
if n['num'] == iface.myInfo.my_node_num: if n["num"] == iface.myInfo.my_node_num:
print(n['user']['hwModel']) print(n["user"]["hwModel"])
iface.close() iface.close()

View File

@@ -3,6 +3,7 @@
""" """
import sys import sys
import meshtastic import meshtastic
import meshtastic.serial_interface import meshtastic.serial_interface

20
examples/info_example.py Normal file
View File

@@ -0,0 +1,20 @@
"""Simple program to demo how to use meshtastic library.
To run: python examples/info.py
"""
import meshtastic
import meshtastic.serial_interface
iface = meshtastic.serial_interface.SerialInterface()
# call showInfo() just to ensure values are populated
# info = iface.showInfo()
if iface.nodes:
for n in iface.nodes.values():
if n["num"] == iface.myInfo.my_node_num:
print(n["user"]["hwModel"])
break
iface.close()

View File

@@ -3,6 +3,7 @@
""" """
import sys import sys
from pubsub import pub from pubsub import pub
import meshtastic import meshtastic
@@ -13,10 +14,13 @@ if len(sys.argv) < 2:
print(f"usage: {sys.argv[0]} host") print(f"usage: {sys.argv[0]} host")
sys.exit(1) sys.exit(1)
def onConnection(interface, topic=pub.AUTO_TOPIC):
def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument
"""This is called when we (re)connect to the radio.""" """This is called when we (re)connect to the radio."""
print(interface.myInfo) print(interface.myInfo)
interface.close() interface.close()
pub.subscribe(onConnection, "meshtastic.connection.established") pub.subscribe(onConnection, "meshtastic.connection.established")
try: try:

View File

@@ -4,6 +4,7 @@
import sys import sys
import time import time
from pubsub import pub from pubsub import pub
import meshtastic import meshtastic
@@ -14,15 +15,18 @@ if len(sys.argv) < 2:
print(f"usage: {sys.argv[0]} host") print(f"usage: {sys.argv[0]} host")
sys.exit(1) sys.exit(1)
def onReceive(packet, interface):
def onReceive(packet, interface): # pylint: disable=unused-argument
"""called when a packet arrives""" """called when a packet arrives"""
print(f"Received: {packet}") print(f"Received: {packet}")
def onConnection(interface, topic=pub.AUTO_TOPIC):
def onConnection(interface, topic=pub.AUTO_TOPIC): # pylint: disable=unused-argument
"""called when we (re)connect to the radio""" """called when we (re)connect to the radio"""
# defaults to broadcast, specify a destination ID if you wish # defaults to broadcast, specify a destination ID if you wish
interface.sendText("hello mesh") interface.sendText("hello mesh")
pub.subscribe(onReceive, "meshtastic.receive") pub.subscribe(onReceive, "meshtastic.receive")
pub.subscribe(onConnection, "meshtastic.connection.established") pub.subscribe(onConnection, "meshtastic.connection.established")
try: try:
@@ -30,6 +34,6 @@ try:
while True: while True:
time.sleep(1000) time.sleep(1000)
iface.close() iface.close()
except(Exception) as ex: except Exception as ex:
print(f"Error: Could not connect to {sys.argv[1]} {ex}") print(f"Error: Could not connect to {sys.argv[1]} {ex}")
sys.exit(1) sys.exit(1)

View File

@@ -3,9 +3,12 @@
""" """
import sys import sys
import meshtastic
from meshtastic.supported_device import get_unique_vendor_ids, active_ports_on_supported_devices from meshtastic.util import (
from meshtastic.util import detect_supported_devices active_ports_on_supported_devices,
detect_supported_devices,
get_unique_vendor_ids,
)
# simple arg check # simple arg check
if len(sys.argv) != 1: if len(sys.argv) != 1:
@@ -14,13 +17,13 @@ if len(sys.argv) != 1:
sys.exit(3) sys.exit(3)
vids = get_unique_vendor_ids() vids = get_unique_vendor_ids()
print(f'Searching for all devices with these vendor ids {vids}') print(f"Searching for all devices with these vendor ids {vids}")
sds = detect_supported_devices() sds = detect_supported_devices()
if len(sds) > 0: if len(sds) > 0:
print('Detected possible devices:') print("Detected possible devices:")
for d in sds: for d in sds:
print(f' name:{d.name}{d.version} firmware:{d.for_firmware}') print(f" name:{d.name}{d.version} firmware:{d.for_firmware}")
ports = active_ports_on_supported_devices(sds) ports = active_ports_on_supported_devices(sds)
print(f'ports:{ports}') print(f"ports:{ports}")

View File

@@ -3,6 +3,7 @@
""" """
import sys import sys
import meshtastic import meshtastic
import meshtastic.serial_interface import meshtastic.serial_interface

6
examples/show_ports.py Normal file
View File

@@ -0,0 +1,6 @@
"""Simple program to show serial ports.
"""
from meshtastic.util import findPorts
print(findPorts())

View File

@@ -0,0 +1,14 @@
"""Demonstration of how to look up a radio's location via its LAN connection.
Before running, connect your machine to the same WiFi network as the radio.
"""
import meshtastic
import meshtastic.tcp_interface
radio_hostname = "meshtastic.local" # Can also be an IP
iface = meshtastic.tcp_interface.TCPInterface(radio_hostname)
my_node_num = iface.myInfo.my_node_num
pos = iface.nodesByNum[my_node_num]["position"]
print(pos)
iface.close()

30
info/mac/nano_g1.txt Normal file
View File

@@ -0,0 +1,30 @@
meshtastic detected port: /dev/cu.wchusbserial53820208781
ioreg -p IOUSB
shows this:
| +-o USB Single Serial@14300000 <class AppleUSBDevice, id 0x1000407a5, registered, matched, active, busy 0 (18 ms), retain 14>
system_profiler SPUSBDataType > /tmp/a
with device plugged in
system_profiler SPUSBDataType > /tmp/b
with device not plugged in
diff /tmp/a /tmp/b
< USB Single Serial:
<
< Product ID: 0x55d4
< Vendor ID: 0x1a86
< Version: 4.43
< Serial Number: 5382020878
< Speed: Up to 12 Mb/s
< Location ID: 0x14300000 / 63
< Current Available (mA): 500
< Current Required (mA): 134
< Extra Operating Current (mA): 0

52
info/mac/rak11200.txt Normal file
View File

@@ -0,0 +1,52 @@
% ioreg -p IOUSB > /tmp/a
# only a solid red light (pins GRND and BOOT0 jumpered)
% ioreg -p IOUSB > /tmp/b
# solid red light and solid green light
% ioreg -p IOUSB > /tmp/c
# nothing plugged in
% diff /tmp/a /tmp/c
13c13
< +-o AppleUSBXHCI Root Hub Simulation@14000000 <class AppleUSBRootHubDevice, id 0x100000589, registered, matched, active, busy 0 (15 ms), retain 11>
---
> +-o AppleUSBXHCI Root Hub Simulation@14000000 <class AppleUSBRootHubDevice, id 0x100000589, registered, matched, active, busy 0 (15 ms), retain 10>
18d17
< +-o USB Serial@14300000 <class AppleUSBDevice, id 0x100004ca4, registered, matched, active, busy 0 (10 ms), retain 12>
% diff /tmp/b /tmp/c
13c13
< +-o AppleUSBXHCI Root Hub Simulation@14000000 <class AppleUSBRootHubDevice, id 0x100000589, registered, matched, active, busy 0 (15 ms), retain 11>
---
> +-o AppleUSBXHCI Root Hub Simulation@14000000 <class AppleUSBRootHubDevice, id 0x100000589, registered, matched, active, busy 0 (15 ms), retain 10>
18d17
< +-o USB Serial@14300000 <class AppleUSBDevice, id 0x100004ce5, registered, matched, active, busy 0 (11 ms), retain 12>
system_profiler SPUSBDataType > /tmp/d
# red solid
system_profiler SPUSBDataType > /tmp/e
# nothing
% diff /tmp/d /tmp/e
38,48d37
< USB Serial:
<
< Product ID: 0x7523
< Vendor ID: 0x1a86
< Version: 2.64
< Speed: Up to 12 Mb/s
< Location ID: 0x14300000 / 33
< Current Available (mA): 500
< Current Required (mA): 98
< Extra Operating Current (mA): 0

View File

@@ -30,3 +30,11 @@ drwxrwxrwx 1 bob staff 512 Feb 1 16:47 .fseventsd
% diff /tmp/a /tmp/c % diff /tmp/a /tmp/c
<snip> <snip>
> | +-o T-Echo v1@14300000 <class AppleUSBDevice, id 0x100061000, registered, matched, active, busy 0 (25 ms), retain 16> > | +-o T-Echo v1@14300000 <class AppleUSBDevice, id 0x100061000, registered, matched, active, busy 0 (25 ms), retain 16>
contents of: INFO_UF2.TXT
UF2 Bootloader 0.6.1-2-g1224915 lib/nrfx (v2.0.0) lib/tinyusb (0.10.1-293-gaf8e5a90) lib/uf2 (remotes/origin/configupdate-9-gadbb8c7)
Model: LilyGo T-Echo
Board-ID: nRF52840-TEcho-v1
SoftDevice: S140 version 6.1.1
Date: Oct 13 2021

91
info/ubuntu/nano_g1.txt Normal file
View File

@@ -0,0 +1,91 @@
lsusb -d 1a86: -v
Bus 001 Device 013: ID 1a86:55d4 QinHeng Electronics
Couldn't open device, some information will be missing
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 2 Communications
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x1a86 QinHeng Electronics
idProduct 0x55d4
bcdDevice 4.43
iManufacturer 0
iProduct 2
iSerial 3
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0043
bNumInterfaces 2
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xa0
(Bus Powered)
Remote Wakeup
MaxPower 134mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 2 Communications
bInterfaceSubClass 2 Abstract (modem)
bInterfaceProtocol 1 AT-commands (v.25ter)
iInterface 0
CDC Header:
bcdCDC 1.10
CDC Call Management:
bmCapabilities 0x00
bDataInterface 1
CDC ACM:
bmCapabilities 0x02
line coding and serial state
CDC Union:
bMasterInterface 0
bSlaveInterface 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0010 1x 16 bytes
bInterval 1
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 10 CDC Data
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0020 1x 32 bytes
bInterval 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x82 EP 2 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 0

39
info/windows/nano_g1.txt Normal file
View File

@@ -0,0 +1,39 @@
Get-PnpDevice -PresentOnly | Format-List >a
Get-PnpDevice -PresentOnly | Format-List >b
Compare-Object (get-content a) (Get-Content b)
InputObject SideIndicator
----------- -------------
Caption : USB-Enhanced-SERIAL CH9102 (COM9) =>
Description : USB-Enhanced-SERIAL CH9102 =>
Name : USB-Enhanced-SERIAL CH9102 (COM9) =>
DeviceID : USB\VID_1A86&PID_55D4\5382020745 =>
PNPDeviceID : USB\VID_1A86&PID_55D4\5382020745 =>
ClassGuid : {4d36e978-e325-11ce-bfc1-08002be10318} =>
CompatibleID : {USB\Class_02&SubClass_02&Prot_01, USB\Class_02&SubClass_02, USB\Class_02} =>
HardwareID : {USB\VID_1A86&PID_55D4&REV_0443, USB\VID_1A86&PID_55D4} =>
Manufacturer : wch.cn =>
PNPClass : Ports =>
Service : CH343SER_A64 =>
Class : Ports =>
FriendlyName : USB-Enhanced-SERIAL CH9102 (COM9) =>
InstanceId : USB\VID_1A86&PID_55D4\5382020745 =>
InstallDate : =>
Status : OK =>
Availability : =>
ConfigManagerErrorCode : CM_PROB_NONE =>
ConfigManagerUserConfig : False =>
CreationClassName : Win32_PnPEntity =>
ErrorCleared : =>
ErrorDescription : =>
LastErrorCode : =>
PowerManagementCapabilities : =>
PowerManagementSupported : =>
StatusInfo : =>
SystemCreationClassName : Win32_ComputerSystem =>
SystemName : DESKTOP-FRFQN8H =>
Present : True =>
PSComputerName : =>
Problem : CM_PROB_NONE =>
ProblemDescription : =>
=>

View File

@@ -3,11 +3,11 @@
Primary class: SerialInterface Primary class: SerialInterface
Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)" Install with pip: "[pip3 install meshtastic](https://pypi.org/project/meshtastic/)"
Source code on [github](https://github.com/meshtastic/Meshtastic-python) Source code on [github](https://github.com/meshtastic/python)
properties of SerialInterface: properties of SerialInterface:
- radioConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to - localConfig - Current radio configuration and device settings, if you write to this the new settings will be applied to
the device. the device.
- nodes - The database of received nodes. Includes always up-to-date location and username information for each - nodes - The database of received nodes. Includes always up-to-date location and username information for each
node in the mesh. This is a read-only datastructure. node in the mesh. This is a read-only datastructure.
@@ -62,39 +62,48 @@ import os
import platform import platform
import random import random
import socket import socket
import sys
import stat import stat
import sys
import threading import threading
import traceback
import time import time
import traceback
from datetime import datetime from datetime import datetime
from typing import * from typing import *
import google.protobuf.json_format
import serial import serial
import timeago import timeago
import google.protobuf.json_format
from pubsub import pub
from dotmap import DotMap from dotmap import DotMap
from tabulate import tabulate
from google.protobuf.json_format import MessageToJson from google.protobuf.json_format import MessageToJson
from meshtastic.util import fixme, catchAndIgnore, stripnl, DeferredExecution, Timeout from pubsub import pub
from meshtastic.node import Node from tabulate import tabulate
from meshtastic import (mesh_pb2, portnums_pb2, apponly_pb2, admin_pb2,
environmental_measurement_pb2, remote_hardware_pb2,
channel_pb2, radioconfig_pb2, util)
from meshtastic import (
admin_pb2,
apponly_pb2,
channel_pb2,
config_pb2,
mesh_pb2,
portnums_pb2,
remote_hardware_pb2,
telemetry_pb2,
util,
)
from meshtastic.node import Node
from meshtastic.util import DeferredExecution, Timeout, catchAndIgnore, fixme, stripnl
# Note: To follow PEP224, comments should be after the module variable. # Note: To follow PEP224, comments should be after the module variable.
LOCAL_ADDR = "^local" LOCAL_ADDR = "^local"
"""A special ID that means the local node""" """A special ID that means the local node"""
BROADCAST_NUM = 0xffffffff BROADCAST_NUM = 0xFFFFFFFF
"""if using 8 bit nodenums this will be shortend on the target""" """if using 8 bit nodenums this will be shortened on the target"""
BROADCAST_ADDR = "^all" BROADCAST_ADDR = "^all"
"""A special ID that means broadcast""" """A special ID that means broadcast"""
OUR_APP_VERSION = 20200 OUR_APP_VERSION = 20300
"""The numeric buildnumber (shared with android apps) specifying the """The numeric buildnumber (shared with android apps) specifying the
level of device code we are guaranteed to understand level of device code we are guaranteed to understand
@@ -106,6 +115,7 @@ publishingThread = DeferredExecution("publishing")
class ResponseHandler(NamedTuple): class ResponseHandler(NamedTuple):
"""A pending response callback, waiting for a response to one of our messages""" """A pending response callback, waiting for a response to one of our messages"""
# requestId: int - used only as a key # requestId: int - used only as a key
callback: Callable callback: Callable
# FIXME, add timestamp and age out old requests # FIXME, add timestamp and age out old requests
@@ -113,6 +123,7 @@ class ResponseHandler(NamedTuple):
class KnownProtocol(NamedTuple): class KnownProtocol(NamedTuple):
"""Used to automatically decode known protocol payloads""" """Used to automatically decode known protocol payloads"""
name: str name: str
# portnum: int, now a key # portnum: int, now a key
# If set, will be called to prase as a protocol buffer # If set, will be called to prase as a protocol buffer
@@ -129,7 +140,7 @@ def _onTextReceive(iface, asDict):
# #
# Usually btw this problem is caused by apps sending binary data but setting the payload type to # Usually btw this problem is caused by apps sending binary data but setting the payload type to
# text. # text.
logging.debug(f'in _onTextReceive() asDict:{asDict}') logging.debug(f"in _onTextReceive() asDict:{asDict}")
try: try:
asBytes = asDict["decoded"]["payload"] asBytes = asDict["decoded"]["payload"]
asDict["decoded"]["text"] = asBytes.decode("utf-8") asDict["decoded"]["text"] = asBytes.decode("utf-8")
@@ -140,28 +151,28 @@ def _onTextReceive(iface, asDict):
def _onPositionReceive(iface, asDict): def _onPositionReceive(iface, asDict):
"""Special auto parsing for received messages""" """Special auto parsing for received messages"""
logging.debug(f'in _onPositionReceive() asDict:{asDict}') logging.debug(f"in _onPositionReceive() asDict:{asDict}")
if 'decoded' in asDict: if "decoded" in asDict:
if 'position' in asDict['decoded'] and 'from' in asDict: if "position" in asDict["decoded"] and "from" in asDict:
p = asDict["decoded"]["position"] p = asDict["decoded"]["position"]
logging.debug(f'p:{p}') logging.debug(f"p:{p}")
p = iface._fixupPosition(p) p = iface._fixupPosition(p)
logging.debug(f'after fixup p:{p}') logging.debug(f"after fixup p:{p}")
# update node DB as needed # update node DB as needed
iface._getOrCreateByNum(asDict["from"])["position"] = p iface._getOrCreateByNum(asDict["from"])["position"] = p
def _onNodeInfoReceive(iface, asDict): def _onNodeInfoReceive(iface, asDict):
"""Special auto parsing for received messages""" """Special auto parsing for received messages"""
logging.debug(f'in _onNodeInfoReceive() asDict:{asDict}') logging.debug(f"in _onNodeInfoReceive() asDict:{asDict}")
if 'decoded' in asDict: if "decoded" in asDict:
if 'user' in asDict['decoded'] and 'from' in asDict: if "user" in asDict["decoded"] and "from" in asDict:
p = asDict["decoded"]["user"] p = asDict["decoded"]["user"]
# decode user protobufs and update nodedb, provide decoded version as "position" in the published msg # decode user protobufs and update nodedb, provide decoded version as "position" in the published msg
# update node DB as needed # update node DB as needed
n = iface._getOrCreateByNum(asDict["from"]) n = iface._getOrCreateByNum(asDict["from"])
n["user"] = p n["user"] = p
# We now have a node ID, make sure it is uptodate in that table # We now have a node ID, make sure it is up-to-date in that table
iface.nodes[p["id"]] = n iface.nodes[p["id"]] = n
_receiveInfoUpdate(iface, asDict) _receiveInfoUpdate(iface, asDict)
@@ -176,12 +187,25 @@ def _receiveInfoUpdate(iface, asDict):
"""Well known message payloads can register decoders for automatic protobuf parsing""" """Well known message payloads can register decoders for automatic protobuf parsing"""
protocols = { protocols = {
portnums_pb2.PortNum.TEXT_MESSAGE_APP: KnownProtocol("text", onReceive=_onTextReceive), portnums_pb2.PortNum.TEXT_MESSAGE_APP: KnownProtocol(
portnums_pb2.PortNum.POSITION_APP: KnownProtocol("position", mesh_pb2.Position, _onPositionReceive), "text", onReceive=_onTextReceive
portnums_pb2.PortNum.NODEINFO_APP: KnownProtocol("user", mesh_pb2.User, _onNodeInfoReceive), ),
portnums_pb2.PortNum.POSITION_APP: KnownProtocol(
"position", mesh_pb2.Position, _onPositionReceive
),
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),
portnums_pb2.PortNum.ROUTING_APP: KnownProtocol("routing", mesh_pb2.Routing), portnums_pb2.PortNum.ROUTING_APP: KnownProtocol("routing", mesh_pb2.Routing),
portnums_pb2.PortNum.ENVIRONMENTAL_MEASUREMENT_APP: KnownProtocol("environmental", environmental_measurement_pb2.EnvironmentalMeasurement), portnums_pb2.PortNum.TELEMETRY_APP: KnownProtocol(
"telemetry", telemetry_pb2.Telemetry
),
portnums_pb2.PortNum.REMOTE_HARDWARE_APP: KnownProtocol( portnums_pb2.PortNum.REMOTE_HARDWARE_APP: KnownProtocol(
"remotehw", remote_hardware_pb2.HardwareMessage) "remotehw", remote_hardware_pb2.HardwareMessage
),
portnums_pb2.PortNum.SIMULATOR_APP: KnownProtocol("simulator", mesh_pb2.Compressed),
portnums_pb2.PortNum.TRACEROUTE_APP: KnownProtocol(
"traceroute", mesh_pb2.RouteDiscovery
),
} }

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: admin.proto # source: meshtastic/admin.proto
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import descriptor_pool as _descriptor_pool
@@ -12,28 +12,56 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
from . import cannedmessages_pb2 as cannedmessages__pb2 from meshtastic import channel_pb2 as meshtastic_dot_channel__pb2
from . import channel_pb2 as channel__pb2 from meshtastic import config_pb2 as meshtastic_dot_config__pb2
from . import mesh_pb2 as mesh__pb2 from meshtastic import connection_status_pb2 as meshtastic_dot_connection__status__pb2
from . import radioconfig_pb2 as radioconfig__pb2 from meshtastic import deviceonly_pb2 as meshtastic_dot_deviceonly__pb2
from meshtastic import mesh_pb2 as meshtastic_dot_mesh__pb2
from meshtastic import module_config_pb2 as meshtastic_dot_module__config__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x61\x64min.proto\x1a\x14\x63\x61nnedmessages.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\x87\x0c\n\x0c\x41\x64minMessage\x12!\n\tset_radio\x18\x01 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1a\n\tset_owner\x18\x02 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18\x03 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_radio_request\x18\x04 \x01(\x08H\x00\x12*\n\x12get_radio_response\x18\x05 \x01(\x0b\x32\x0c.RadioConfigH\x00\x12\x1d\n\x13get_channel_request\x18\x06 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x07 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x08 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\t \x01(\x0b\x32\x05.UserH\x00\x12\x1d\n\x13\x63onfirm_set_channel\x18 \x01(\x08H\x00\x12\x1b\n\x11\x63onfirm_set_radio\x18! \x01(\x08H\x00\x12\x18\n\x0e\x65xit_simulator\x18\" \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18# \x01(\x05H\x00\x12\x31\n\'get_canned_message_plugin_part1_request\x18$ \x01(\x08H\x00\x12T\n(get_canned_message_plugin_part1_response\x18% \x01(\x0b\x32 .CannedMessagePluginMessagePart1H\x00\x12\x31\n\'get_canned_message_plugin_part2_request\x18& \x01(\x08H\x00\x12T\n(get_canned_message_plugin_part2_response\x18\' \x01(\x0b\x32 .CannedMessagePluginMessagePart2H\x00\x12\x31\n\'get_canned_message_plugin_part3_request\x18( \x01(\x08H\x00\x12T\n(get_canned_message_plugin_part3_response\x18) \x01(\x0b\x32 .CannedMessagePluginMessagePart3H\x00\x12\x31\n\'get_canned_message_plugin_part4_request\x18* \x01(\x08H\x00\x12T\n(get_canned_message_plugin_part4_response\x18+ \x01(\x0b\x32 .CannedMessagePluginMessagePart4H\x00\x12\x31\n\'get_canned_message_plugin_part5_request\x18, \x01(\x08H\x00\x12T\n(get_canned_message_plugin_part5_response\x18- \x01(\x0b\x32 .CannedMessagePluginMessagePart5H\x00\x12K\n\x1fset_canned_message_plugin_part1\x18. \x01(\x0b\x32 .CannedMessagePluginMessagePart1H\x00\x12K\n\x1fset_canned_message_plugin_part2\x18/ \x01(\x0b\x32 .CannedMessagePluginMessagePart2H\x00\x12K\n\x1fset_canned_message_plugin_part3\x18\x30 \x01(\x0b\x32 .CannedMessagePluginMessagePart3H\x00\x12K\n\x1fset_canned_message_plugin_part4\x18\x31 \x01(\x0b\x32 .CannedMessagePluginMessagePart4H\x00\x12K\n\x1fset_canned_message_plugin_part5\x18\x32 \x01(\x0b\x32 .CannedMessagePluginMessagePart5H\x00\x12\x1a\n\x10shutdown_seconds\x18\x33 \x01(\x05H\x00\x42\t\n\x07variantBG\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16meshtastic/admin.proto\x1a\x18meshtastic/channel.proto\x1a\x17meshtastic/config.proto\x1a\"meshtastic/connection_status.proto\x1a\x1bmeshtastic/deviceonly.proto\x1a\x15meshtastic/mesh.proto\x1a\x1emeshtastic/module_config.proto\"\xad\x0e\n\x0c\x41\x64minMessage\x12\x1d\n\x13get_channel_request\x18\x01 \x01(\rH\x00\x12(\n\x14get_channel_response\x18\x02 \x01(\x0b\x32\x08.ChannelH\x00\x12\x1b\n\x11get_owner_request\x18\x03 \x01(\x08H\x00\x12#\n\x12get_owner_response\x18\x04 \x01(\x0b\x32\x05.UserH\x00\x12\x36\n\x12get_config_request\x18\x05 \x01(\x0e\x32\x18.AdminMessage.ConfigTypeH\x00\x12&\n\x13get_config_response\x18\x06 \x01(\x0b\x32\x07.ConfigH\x00\x12\x43\n\x19get_module_config_request\x18\x07 \x01(\x0e\x32\x1e.AdminMessage.ModuleConfigTypeH\x00\x12\x33\n\x1aget_module_config_response\x18\x08 \x01(\x0b\x32\r.ModuleConfigH\x00\x12\x34\n*get_canned_message_module_messages_request\x18\n \x01(\x08H\x00\x12\x35\n+get_canned_message_module_messages_response\x18\x0b \x01(\tH\x00\x12%\n\x1bget_device_metadata_request\x18\x0c \x01(\x08H\x00\x12\x37\n\x1cget_device_metadata_response\x18\r \x01(\x0b\x32\x0f.DeviceMetadataH\x00\x12\x1e\n\x14get_ringtone_request\x18\x0e \x01(\x08H\x00\x12\x1f\n\x15get_ringtone_response\x18\x0f \x01(\tH\x00\x12.\n$get_device_connection_status_request\x18\x10 \x01(\x08H\x00\x12H\n%get_device_connection_status_response\x18\x11 \x01(\x0b\x32\x17.DeviceConnectionStatusH\x00\x12&\n\x0cset_ham_mode\x18\x12 \x01(\x0b\x32\x0e.HamParametersH\x00\x12/\n%get_node_remote_hardware_pins_request\x18\x13 \x01(\x08H\x00\x12Q\n&get_node_remote_hardware_pins_response\x18\x14 \x01(\x0b\x32\x1f.NodeRemoteHardwarePinsResponseH\x00\x12\x1a\n\tset_owner\x18 \x01(\x0b\x32\x05.UserH\x00\x12\x1f\n\x0bset_channel\x18! \x01(\x0b\x32\x08.ChannelH\x00\x12\x1d\n\nset_config\x18\" \x01(\x0b\x32\x07.ConfigH\x00\x12*\n\x11set_module_config\x18# \x01(\x0b\x32\r.ModuleConfigH\x00\x12,\n\"set_canned_message_module_messages\x18$ \x01(\tH\x00\x12\x1e\n\x14set_ringtone_message\x18% \x01(\tH\x00\x12\x1d\n\x13\x62\x65gin_edit_settings\x18@ \x01(\x08H\x00\x12\x1e\n\x14\x63ommit_edit_settings\x18\x41 \x01(\x08H\x00\x12\x1c\n\x12reboot_ota_seconds\x18_ \x01(\x05H\x00\x12\x18\n\x0e\x65xit_simulator\x18` \x01(\x08H\x00\x12\x18\n\x0ereboot_seconds\x18\x61 \x01(\x05H\x00\x12\x1a\n\x10shutdown_seconds\x18\x62 \x01(\x05H\x00\x12\x17\n\rfactory_reset\x18\x63 \x01(\x05H\x00\x12\x16\n\x0cnodedb_reset\x18\x64 \x01(\x05H\x00\"\x95\x01\n\nConfigType\x12\x11\n\rDEVICE_CONFIG\x10\x00\x12\x13\n\x0fPOSITION_CONFIG\x10\x01\x12\x10\n\x0cPOWER_CONFIG\x10\x02\x12\x12\n\x0eNETWORK_CONFIG\x10\x03\x12\x12\n\x0e\x44ISPLAY_CONFIG\x10\x04\x12\x0f\n\x0bLORA_CONFIG\x10\x05\x12\x14\n\x10\x42LUETOOTH_CONFIG\x10\x06\"\xa4\x02\n\x10ModuleConfigType\x12\x0f\n\x0bMQTT_CONFIG\x10\x00\x12\x11\n\rSERIAL_CONFIG\x10\x01\x12\x13\n\x0f\x45XTNOTIF_CONFIG\x10\x02\x12\x17\n\x13STOREFORWARD_CONFIG\x10\x03\x12\x14\n\x10RANGETEST_CONFIG\x10\x04\x12\x14\n\x10TELEMETRY_CONFIG\x10\x05\x12\x14\n\x10\x43\x41NNEDMSG_CONFIG\x10\x06\x12\x10\n\x0c\x41UDIO_CONFIG\x10\x07\x12\x19\n\x15REMOTEHARDWARE_CONFIG\x10\x08\x12\x17\n\x13NEIGHBORINFO_CONFIG\x10\t\x12\x1a\n\x16\x41MBIENTLIGHTING_CONFIG\x10\n\x12\x1a\n\x16\x44\x45TECTIONSENSOR_CONFIG\x10\x0b\x42\x11\n\x0fpayload_variant\"[\n\rHamParameters\x12\x11\n\tcall_sign\x18\x01 \x01(\t\x12\x10\n\x08tx_power\x18\x02 \x01(\x05\x12\x11\n\tfrequency\x18\x03 \x01(\x02\x12\x12\n\nshort_name\x18\x04 \x01(\t\"[\n\x1eNodeRemoteHardwarePinsResponse\x12\x39\n\x19node_remote_hardware_pins\x18\x01 \x03(\x0b\x32\x16.NodeRemoteHardwarePinB`\n\x13\x63om.geeksville.meshB\x0b\x41\x64minProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_ADMINMESSAGE = DESCRIPTOR.message_types_by_name['AdminMessage'] _ADMINMESSAGE = DESCRIPTOR.message_types_by_name['AdminMessage']
_HAMPARAMETERS = DESCRIPTOR.message_types_by_name['HamParameters']
_NODEREMOTEHARDWAREPINSRESPONSE = DESCRIPTOR.message_types_by_name['NodeRemoteHardwarePinsResponse']
_ADMINMESSAGE_CONFIGTYPE = _ADMINMESSAGE.enum_types_by_name['ConfigType']
_ADMINMESSAGE_MODULECONFIGTYPE = _ADMINMESSAGE.enum_types_by_name['ModuleConfigType']
AdminMessage = _reflection.GeneratedProtocolMessageType('AdminMessage', (_message.Message,), { AdminMessage = _reflection.GeneratedProtocolMessageType('AdminMessage', (_message.Message,), {
'DESCRIPTOR' : _ADMINMESSAGE, 'DESCRIPTOR' : _ADMINMESSAGE,
'__module__' : 'admin_pb2' '__module__' : 'meshtastic.admin_pb2'
# @@protoc_insertion_point(class_scope:AdminMessage) # @@protoc_insertion_point(class_scope:AdminMessage)
}) })
_sym_db.RegisterMessage(AdminMessage) _sym_db.RegisterMessage(AdminMessage)
HamParameters = _reflection.GeneratedProtocolMessageType('HamParameters', (_message.Message,), {
'DESCRIPTOR' : _HAMPARAMETERS,
'__module__' : 'meshtastic.admin_pb2'
# @@protoc_insertion_point(class_scope:HamParameters)
})
_sym_db.RegisterMessage(HamParameters)
NodeRemoteHardwarePinsResponse = _reflection.GeneratedProtocolMessageType('NodeRemoteHardwarePinsResponse', (_message.Message,), {
'DESCRIPTOR' : _NODEREMOTEHARDWAREPINSRESPONSE,
'__module__' : 'meshtastic.admin_pb2'
# @@protoc_insertion_point(class_scope:NodeRemoteHardwarePinsResponse)
})
_sym_db.RegisterMessage(NodeRemoteHardwarePinsResponse)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\013AdminProtosH\003Z!github.com/meshtastic/gomeshproto' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\013AdminProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_ADMINMESSAGE._serialized_start=84 _ADMINMESSAGE._serialized_start=198
_ADMINMESSAGE._serialized_end=1627 _ADMINMESSAGE._serialized_end=2035
_ADMINMESSAGE_CONFIGTYPE._serialized_start=1572
_ADMINMESSAGE_CONFIGTYPE._serialized_end=1721
_ADMINMESSAGE_MODULECONFIGTYPE._serialized_start=1724
_ADMINMESSAGE_MODULECONFIGTYPE._serialized_end=2016
_HAMPARAMETERS._serialized_start=2037
_HAMPARAMETERS._serialized_end=2128
_NODEREMOTEHARDWAREPINSRESPONSE._serialized_start=2130
_NODEREMOTEHARDWAREPINSRESPONSE._serialized_end=2221
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: apponly.proto # source: meshtastic/apponly.proto
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import descriptor_pool as _descriptor_pool
@@ -12,17 +12,18 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
from . import channel_pb2 as channel__pb2 from meshtastic import channel_pb2 as meshtastic_dot_channel__pb2
from meshtastic import config_pb2 as meshtastic_dot_config__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rapponly.proto\x1a\rchannel.proto\"0\n\nChannelSet\x12\"\n\x08settings\x18\x01 \x03(\x0b\x32\x10.ChannelSettingsBI\n\x13\x63om.geeksville.meshB\rAppOnlyProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18meshtastic/apponly.proto\x1a\x18meshtastic/channel.proto\x1a\x17meshtastic/config.proto\"Y\n\nChannelSet\x12\"\n\x08settings\x18\x01 \x03(\x0b\x32\x10.ChannelSettings\x12\'\n\x0blora_config\x18\x02 \x01(\x0b\x32\x12.Config.LoRaConfigBb\n\x13\x63om.geeksville.meshB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_CHANNELSET = DESCRIPTOR.message_types_by_name['ChannelSet'] _CHANNELSET = DESCRIPTOR.message_types_by_name['ChannelSet']
ChannelSet = _reflection.GeneratedProtocolMessageType('ChannelSet', (_message.Message,), { ChannelSet = _reflection.GeneratedProtocolMessageType('ChannelSet', (_message.Message,), {
'DESCRIPTOR' : _CHANNELSET, 'DESCRIPTOR' : _CHANNELSET,
'__module__' : 'apponly_pb2' '__module__' : 'meshtastic.apponly_pb2'
# @@protoc_insertion_point(class_scope:ChannelSet) # @@protoc_insertion_point(class_scope:ChannelSet)
}) })
_sym_db.RegisterMessage(ChannelSet) _sym_db.RegisterMessage(ChannelSet)
@@ -30,7 +31,7 @@ _sym_db.RegisterMessage(ChannelSet)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rAppOnlyProtosH\003Z!github.com/meshtastic/gomeshproto' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rAppOnlyProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_CHANNELSET._serialized_start=32 _CHANNELSET._serialized_start=79
_CHANNELSET._serialized_end=80 _CHANNELSET._serialized_end=168
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -6,12 +6,11 @@ import platform
from meshtastic.mesh_interface import MeshInterface from meshtastic.mesh_interface import MeshInterface
from meshtastic.util import our_exit from meshtastic.util import our_exit
if platform.system() == 'Linux': if platform.system() == "Linux":
# pylint: disable=E0401 # pylint: disable=E0401
import pygatt import pygatt
# Our standard BLE characteristics # Our standard BLE characteristics
TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7" TORADIO_UUID = "f75c76d2-129e-4dad-a1dd-7866124401e7"
FROMRADIO_UUID = "8ba2bcc2-ee02-4a55-a531-c525c5e454d5" FROMRADIO_UUID = "8ba2bcc2-ee02-4a55-a531-c525c5e454d5"
@@ -22,7 +21,7 @@ class BLEInterface(MeshInterface):
"""A not quite ready - FIXME - BLE interface to devices""" """A not quite ready - FIXME - BLE interface to devices"""
def __init__(self, address, noProto=False, debugOut=None): def __init__(self, address, noProto=False, debugOut=None):
if platform.system() != 'Linux': if platform.system() != "Linux":
our_exit("Linux is the only platform with experimental BLE support.", 1) our_exit("Linux is the only platform with experimental BLE support.", 1)
self.address = address self.address = address
if not noProto: if not noProto:
@@ -39,7 +38,7 @@ class BLEInterface(MeshInterface):
self._readFromRadio() # read the initial responses self._readFromRadio() # read the initial responses
def handle_data(handle, data): # pylint: disable=W0613 def handle_data(handle, data): # pylint: disable=W0613
self._handleFromRadio(data) self._handleFromRadio(data)
if self.device: if self.device:
@@ -47,7 +46,7 @@ class BLEInterface(MeshInterface):
def _sendToRadioImpl(self, toRadio): def _sendToRadioImpl(self, toRadio):
"""Send a ToRadio protobuf to the device""" """Send a ToRadio protobuf to the device"""
#logging.debug(f"Sending: {stripnl(toRadio)}") # logging.debug(f"Sending: {stripnl(toRadio)}")
b = toRadio.SerializeToString() b = toRadio.SerializeToString()
self.device.char_write(TORADIO_UUID, b) self.device.char_write(TORADIO_UUID, b)

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: cannedmessages.proto # source: meshtastic/cannedmessages.proto
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import descriptor_pool as _descriptor_pool
@@ -14,62 +14,22 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x63\x61nnedmessages.proto\"/\n\x1f\x43\x61nnedMessagePluginMessagePart1\x12\x0c\n\x04text\x18\x01 \x01(\t\"/\n\x1f\x43\x61nnedMessagePluginMessagePart2\x12\x0c\n\x04text\x18\x01 \x01(\t\"/\n\x1f\x43\x61nnedMessagePluginMessagePart3\x12\x0c\n\x04text\x18\x01 \x01(\t\"/\n\x1f\x43\x61nnedMessagePluginMessagePart4\x12\x0c\n\x04text\x18\x01 \x01(\t\"/\n\x1f\x43\x61nnedMessagePluginMessagePart5\x12\x0c\n\x04text\x18\x01 \x01(\tBU\n\x13\x63om.geeksville.meshB\x19\x43\x61nnedMessageConfigProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fmeshtastic/cannedmessages.proto\"-\n\x19\x43\x61nnedMessageModuleConfig\x12\x10\n\x08messages\x18\x01 \x01(\tBn\n\x13\x63om.geeksville.meshB\x19\x43\x61nnedMessageConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_CANNEDMESSAGEPLUGINMESSAGEPART1 = DESCRIPTOR.message_types_by_name['CannedMessagePluginMessagePart1'] _CANNEDMESSAGEMODULECONFIG = DESCRIPTOR.message_types_by_name['CannedMessageModuleConfig']
_CANNEDMESSAGEPLUGINMESSAGEPART2 = DESCRIPTOR.message_types_by_name['CannedMessagePluginMessagePart2'] CannedMessageModuleConfig = _reflection.GeneratedProtocolMessageType('CannedMessageModuleConfig', (_message.Message,), {
_CANNEDMESSAGEPLUGINMESSAGEPART3 = DESCRIPTOR.message_types_by_name['CannedMessagePluginMessagePart3'] 'DESCRIPTOR' : _CANNEDMESSAGEMODULECONFIG,
_CANNEDMESSAGEPLUGINMESSAGEPART4 = DESCRIPTOR.message_types_by_name['CannedMessagePluginMessagePart4'] '__module__' : 'meshtastic.cannedmessages_pb2'
_CANNEDMESSAGEPLUGINMESSAGEPART5 = DESCRIPTOR.message_types_by_name['CannedMessagePluginMessagePart5'] # @@protoc_insertion_point(class_scope:CannedMessageModuleConfig)
CannedMessagePluginMessagePart1 = _reflection.GeneratedProtocolMessageType('CannedMessagePluginMessagePart1', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEPLUGINMESSAGEPART1,
'__module__' : 'cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessagePluginMessagePart1)
}) })
_sym_db.RegisterMessage(CannedMessagePluginMessagePart1) _sym_db.RegisterMessage(CannedMessageModuleConfig)
CannedMessagePluginMessagePart2 = _reflection.GeneratedProtocolMessageType('CannedMessagePluginMessagePart2', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEPLUGINMESSAGEPART2,
'__module__' : 'cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessagePluginMessagePart2)
})
_sym_db.RegisterMessage(CannedMessagePluginMessagePart2)
CannedMessagePluginMessagePart3 = _reflection.GeneratedProtocolMessageType('CannedMessagePluginMessagePart3', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEPLUGINMESSAGEPART3,
'__module__' : 'cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessagePluginMessagePart3)
})
_sym_db.RegisterMessage(CannedMessagePluginMessagePart3)
CannedMessagePluginMessagePart4 = _reflection.GeneratedProtocolMessageType('CannedMessagePluginMessagePart4', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEPLUGINMESSAGEPART4,
'__module__' : 'cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessagePluginMessagePart4)
})
_sym_db.RegisterMessage(CannedMessagePluginMessagePart4)
CannedMessagePluginMessagePart5 = _reflection.GeneratedProtocolMessageType('CannedMessagePluginMessagePart5', (_message.Message,), {
'DESCRIPTOR' : _CANNEDMESSAGEPLUGINMESSAGEPART5,
'__module__' : 'cannedmessages_pb2'
# @@protoc_insertion_point(class_scope:CannedMessagePluginMessagePart5)
})
_sym_db.RegisterMessage(CannedMessagePluginMessagePart5)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\031CannedMessageConfigProtosH\003Z!github.com/meshtastic/gomeshproto' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\031CannedMessageConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_CANNEDMESSAGEPLUGINMESSAGEPART1._serialized_start=24 _CANNEDMESSAGEMODULECONFIG._serialized_start=35
_CANNEDMESSAGEPLUGINMESSAGEPART1._serialized_end=71 _CANNEDMESSAGEMODULECONFIG._serialized_end=80
_CANNEDMESSAGEPLUGINMESSAGEPART2._serialized_start=73
_CANNEDMESSAGEPLUGINMESSAGEPART2._serialized_end=120
_CANNEDMESSAGEPLUGINMESSAGEPART3._serialized_start=122
_CANNEDMESSAGEPLUGINMESSAGEPART3._serialized_end=169
_CANNEDMESSAGEPLUGINMESSAGEPART4._serialized_start=171
_CANNEDMESSAGEPLUGINMESSAGEPART4._serialized_end=218
_CANNEDMESSAGEPLUGINMESSAGEPART5._serialized_start=220
_CANNEDMESSAGEPLUGINMESSAGEPART5._serialized_end=267
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: channel.proto # source: meshtastic/channel.proto
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import descriptor_pool as _descriptor_pool
@@ -14,24 +14,23 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rchannel.proto\"\x91\x03\n\x0f\x43hannelSettings\x12\x10\n\x08tx_power\x18\x01 \x01(\x05\x12\x32\n\x0cmodem_config\x18\x03 \x01(\x0e\x32\x1c.ChannelSettings.ModemConfig\x12\x11\n\tbandwidth\x18\x06 \x01(\r\x12\x15\n\rspread_factor\x18\x07 \x01(\r\x12\x13\n\x0b\x63oding_rate\x18\x08 \x01(\r\x12\x13\n\x0b\x63hannel_num\x18\t \x01(\r\x12\x0b\n\x03psk\x18\x04 \x01(\x0c\x12\x0c\n\x04name\x18\x05 \x01(\t\x12\n\n\x02id\x18\n \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x10 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x11 \x01(\x08\"\x8a\x01\n\x0bModemConfig\x12\x12\n\x0e\x42w125Cr45Sf128\x10\x00\x12\x12\n\x0e\x42w500Cr45Sf128\x10\x01\x12\x14\n\x10\x42w31_25Cr48Sf512\x10\x02\x12\x13\n\x0f\x42w125Cr48Sf4096\x10\x03\x12\x13\n\x0f\x42w250Cr46Sf2048\x10\x04\x12\x13\n\x0f\x42w250Cr47Sf1024\x10\x05\"\x8b\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12\"\n\x08settings\x18\x02 \x01(\x0b\x32\x10.ChannelSettings\x12\x1b\n\x04role\x18\x03 \x01(\x0e\x32\r.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42I\n\x13\x63om.geeksville.meshB\rChannelProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18meshtastic/channel.proto\"\x83\x01\n\x0f\x43hannelSettings\x12\x17\n\x0b\x63hannel_num\x18\x01 \x01(\rB\x02\x18\x01\x12\x0b\n\x03psk\x18\x02 \x01(\x0c\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\n\n\x02id\x18\x04 \x01(\x07\x12\x16\n\x0euplink_enabled\x18\x05 \x01(\x08\x12\x18\n\x10\x64ownlink_enabled\x18\x06 \x01(\x08\"\x8b\x01\n\x07\x43hannel\x12\r\n\x05index\x18\x01 \x01(\x05\x12\"\n\x08settings\x18\x02 \x01(\x0b\x32\x10.ChannelSettings\x12\x1b\n\x04role\x18\x03 \x01(\x0e\x32\r.Channel.Role\"0\n\x04Role\x12\x0c\n\x08\x44ISABLED\x10\x00\x12\x0b\n\x07PRIMARY\x10\x01\x12\r\n\tSECONDARY\x10\x02\x42\x62\n\x13\x63om.geeksville.meshB\rChannelProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_CHANNELSETTINGS = DESCRIPTOR.message_types_by_name['ChannelSettings'] _CHANNELSETTINGS = DESCRIPTOR.message_types_by_name['ChannelSettings']
_CHANNEL = DESCRIPTOR.message_types_by_name['Channel'] _CHANNEL = DESCRIPTOR.message_types_by_name['Channel']
_CHANNELSETTINGS_MODEMCONFIG = _CHANNELSETTINGS.enum_types_by_name['ModemConfig']
_CHANNEL_ROLE = _CHANNEL.enum_types_by_name['Role'] _CHANNEL_ROLE = _CHANNEL.enum_types_by_name['Role']
ChannelSettings = _reflection.GeneratedProtocolMessageType('ChannelSettings', (_message.Message,), { ChannelSettings = _reflection.GeneratedProtocolMessageType('ChannelSettings', (_message.Message,), {
'DESCRIPTOR' : _CHANNELSETTINGS, 'DESCRIPTOR' : _CHANNELSETTINGS,
'__module__' : 'channel_pb2' '__module__' : 'meshtastic.channel_pb2'
# @@protoc_insertion_point(class_scope:ChannelSettings) # @@protoc_insertion_point(class_scope:ChannelSettings)
}) })
_sym_db.RegisterMessage(ChannelSettings) _sym_db.RegisterMessage(ChannelSettings)
Channel = _reflection.GeneratedProtocolMessageType('Channel', (_message.Message,), { Channel = _reflection.GeneratedProtocolMessageType('Channel', (_message.Message,), {
'DESCRIPTOR' : _CHANNEL, 'DESCRIPTOR' : _CHANNEL,
'__module__' : 'channel_pb2' '__module__' : 'meshtastic.channel_pb2'
# @@protoc_insertion_point(class_scope:Channel) # @@protoc_insertion_point(class_scope:Channel)
}) })
_sym_db.RegisterMessage(Channel) _sym_db.RegisterMessage(Channel)
@@ -39,13 +38,13 @@ _sym_db.RegisterMessage(Channel)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rChannelProtosH\003Z!github.com/meshtastic/gomeshproto' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\rChannelProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_CHANNELSETTINGS._serialized_start=18 _CHANNELSETTINGS.fields_by_name['channel_num']._options = None
_CHANNELSETTINGS._serialized_end=419 _CHANNELSETTINGS.fields_by_name['channel_num']._serialized_options = b'\030\001'
_CHANNELSETTINGS_MODEMCONFIG._serialized_start=281 _CHANNELSETTINGS._serialized_start=29
_CHANNELSETTINGS_MODEMCONFIG._serialized_end=419 _CHANNELSETTINGS._serialized_end=160
_CHANNEL._serialized_start=422 _CHANNEL._serialized_start=163
_CHANNEL._serialized_end=561 _CHANNEL._serialized_end=302
_CHANNEL_ROLE._serialized_start=513 _CHANNEL_ROLE._serialized_start=254
_CHANNEL_ROLE._serialized_end=561 _CHANNEL_ROLE._serialized_end=302
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/clientonly.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 message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from meshtastic import localonly_pb2 as meshtastic_dot_localonly__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bmeshtastic/clientonly.proto\x1a\x1ameshtastic/localonly.proto\"\xf7\x01\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!\n\x06\x63onfig\x18\x04 \x01(\x0b\x32\x0c.LocalConfigH\x03\x88\x01\x01\x12.\n\rmodule_config\x18\x05 \x01(\x0b\x32\x12.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')
_DEVICEPROFILE = DESCRIPTOR.message_types_by_name['DeviceProfile']
DeviceProfile = _reflection.GeneratedProtocolMessageType('DeviceProfile', (_message.Message,), {
'DESCRIPTOR' : _DEVICEPROFILE,
'__module__' : 'meshtastic.clientonly_pb2'
# @@protoc_insertion_point(class_scope:DeviceProfile)
})
_sym_db.RegisterMessage(DeviceProfile)
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'
_DEVICEPROFILE._serialized_start=60
_DEVICEPROFILE._serialized_end=307
# @@protoc_insertion_point(module_scope)

156
meshtastic/config_pb2.py Normal file
View File

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/connection_status.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 message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"meshtastic/connection_status.proto\"\x85\x02\n\x16\x44\x65viceConnectionStatus\x12(\n\x04wifi\x18\x01 \x01(\x0b\x32\x15.WifiConnectionStatusH\x00\x88\x01\x01\x12\x30\n\x08\x65thernet\x18\x02 \x01(\x0b\x32\x19.EthernetConnectionStatusH\x01\x88\x01\x01\x12\x32\n\tbluetooth\x18\x03 \x01(\x0b\x32\x1a.BluetoothConnectionStatusH\x02\x88\x01\x01\x12,\n\x06serial\x18\x04 \x01(\x0b\x32\x17.SerialConnectionStatusH\x03\x88\x01\x01\x42\x07\n\x05_wifiB\x0b\n\t_ethernetB\x0c\n\n_bluetoothB\t\n\x07_serial\"\\\n\x14WifiConnectionStatus\x12(\n\x06status\x18\x01 \x01(\x0b\x32\x18.NetworkConnectionStatus\x12\x0c\n\x04ssid\x18\x02 \x01(\t\x12\x0c\n\x04rssi\x18\x03 \x01(\x05\"D\n\x18\x45thernetConnectionStatus\x12(\n\x06status\x18\x01 \x01(\x0b\x32\x18.NetworkConnectionStatus\"{\n\x17NetworkConnectionStatus\x12\x12\n\nip_address\x18\x01 \x01(\x07\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x12\x19\n\x11is_mqtt_connected\x18\x03 \x01(\x08\x12\x1b\n\x13is_syslog_connected\x18\x04 \x01(\x08\"L\n\x19\x42luetoothConnectionStatus\x12\x0b\n\x03pin\x18\x01 \x01(\r\x12\x0c\n\x04rssi\x18\x02 \x01(\x05\x12\x14\n\x0cis_connected\x18\x03 \x01(\x08\"<\n\x16SerialConnectionStatus\x12\x0c\n\x04\x62\x61ud\x18\x01 \x01(\r\x12\x14\n\x0cis_connected\x18\x02 \x01(\x08\x42\x65\n\x13\x63om.geeksville.meshB\x10\x43onnStatusProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_DEVICECONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['DeviceConnectionStatus']
_WIFICONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['WifiConnectionStatus']
_ETHERNETCONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['EthernetConnectionStatus']
_NETWORKCONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['NetworkConnectionStatus']
_BLUETOOTHCONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['BluetoothConnectionStatus']
_SERIALCONNECTIONSTATUS = DESCRIPTOR.message_types_by_name['SerialConnectionStatus']
DeviceConnectionStatus = _reflection.GeneratedProtocolMessageType('DeviceConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _DEVICECONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:DeviceConnectionStatus)
})
_sym_db.RegisterMessage(DeviceConnectionStatus)
WifiConnectionStatus = _reflection.GeneratedProtocolMessageType('WifiConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _WIFICONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:WifiConnectionStatus)
})
_sym_db.RegisterMessage(WifiConnectionStatus)
EthernetConnectionStatus = _reflection.GeneratedProtocolMessageType('EthernetConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _ETHERNETCONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:EthernetConnectionStatus)
})
_sym_db.RegisterMessage(EthernetConnectionStatus)
NetworkConnectionStatus = _reflection.GeneratedProtocolMessageType('NetworkConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _NETWORKCONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:NetworkConnectionStatus)
})
_sym_db.RegisterMessage(NetworkConnectionStatus)
BluetoothConnectionStatus = _reflection.GeneratedProtocolMessageType('BluetoothConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _BLUETOOTHCONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:BluetoothConnectionStatus)
})
_sym_db.RegisterMessage(BluetoothConnectionStatus)
SerialConnectionStatus = _reflection.GeneratedProtocolMessageType('SerialConnectionStatus', (_message.Message,), {
'DESCRIPTOR' : _SERIALCONNECTIONSTATUS,
'__module__' : 'meshtastic.connection_status_pb2'
# @@protoc_insertion_point(class_scope:SerialConnectionStatus)
})
_sym_db.RegisterMessage(SerialConnectionStatus)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\020ConnStatusProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_DEVICECONNECTIONSTATUS._serialized_start=39
_DEVICECONNECTIONSTATUS._serialized_end=300
_WIFICONNECTIONSTATUS._serialized_start=302
_WIFICONNECTIONSTATUS._serialized_end=394
_ETHERNETCONNECTIONSTATUS._serialized_start=396
_ETHERNETCONNECTIONSTATUS._serialized_end=464
_NETWORKCONNECTIONSTATUS._serialized_start=466
_NETWORKCONNECTIONSTATUS._serialized_end=589
_BLUETOOTHCONNECTIONSTATUS._serialized_start=591
_BLUETOOTHCONNECTIONSTATUS._serialized_end=667
_SERIALCONNECTIONSTATUS._serialized_start=669
_SERIALCONNECTIONSTATUS._serialized_end=729
# @@protoc_insertion_point(module_scope)

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/device_metadata.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 message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from meshtastic import config_pb2 as meshtastic_dot_config__pb2
from meshtastic import mesh_pb2 as meshtastic_dot_mesh__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/device_metadata.proto\x1a\x17meshtastic/config.proto\x1a\x15meshtastic/mesh.proto\"\xfc\x01\n\x0e\x44\x65viceMetadata\x12\x18\n\x10\x66irmware_version\x18\x01 \x01(\t\x12\x1c\n\x14\x64\x65vice_state_version\x18\x02 \x01(\r\x12\x13\n\x0b\x63\x61nShutdown\x18\x03 \x01(\x08\x12\x0f\n\x07hasWifi\x18\x04 \x01(\x08\x12\x14\n\x0chasBluetooth\x18\x05 \x01(\x08\x12\x13\n\x0bhasEthernet\x18\x06 \x01(\x08\x12\'\n\x04role\x18\x07 \x01(\x0e\x32\x19.Config.DeviceConfig.Role\x12\x16\n\x0eposition_flags\x18\x08 \x01(\r\x12 \n\x08hw_model\x18\t \x01(\x0e\x32\x0e.HardwareModelBi\n\x13\x63om.geeksville.meshB\x14\x44\x65viceMetadataProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_DEVICEMETADATA = DESCRIPTOR.message_types_by_name['DeviceMetadata']
DeviceMetadata = _reflection.GeneratedProtocolMessageType('DeviceMetadata', (_message.Message,), {
'DESCRIPTOR' : _DEVICEMETADATA,
'__module__' : 'meshtastic.device_metadata_pb2'
# @@protoc_insertion_point(class_scope:DeviceMetadata)
})
_sym_db.RegisterMessage(DeviceMetadata)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\024DeviceMetadataProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_DEVICEMETADATA._serialized_start=85
_DEVICEMETADATA._serialized_end=337
# @@protoc_insertion_point(module_scope)

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: deviceonly.proto # source: meshtastic/deviceonly.proto
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message from google.protobuf import message as _message
@@ -12,58 +13,86 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
from . import channel_pb2 as channel__pb2 from meshtastic import channel_pb2 as meshtastic_dot_channel__pb2
from . import mesh_pb2 as mesh__pb2 from meshtastic import localonly_pb2 as meshtastic_dot_localonly__pb2
from . import radioconfig_pb2 as radioconfig__pb2 from meshtastic import mesh_pb2 as meshtastic_dot_mesh__pb2
from meshtastic import telemetry_pb2 as meshtastic_dot_telemetry__pb2
from meshtastic import module_config_pb2 as meshtastic_dot_module__config__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x64\x65viceonly.proto\x1a\rchannel.proto\x1a\nmesh.proto\x1a\x11radioconfig.proto\"\x80\x01\n\x11LegacyRadioConfig\x12\x39\n\x0bpreferences\x18\x01 \x01(\x0b\x32$.LegacyRadioConfig.LegacyPreferences\x1a\x30\n\x11LegacyPreferences\x12\x1b\n\x06region\x18\x0f \x01(\x0e\x32\x0b.RegionCode\"\xf0\x03\n\x0b\x44\x65viceState\x12\'\n\x0blegacyRadio\x18\x01 \x01(\x0b\x32\x12.LegacyRadioConfig\x12\x1c\n\x07my_node\x18\x02 \x01(\x0b\x32\x0b.MyNodeInfo\x12\x14\n\x05owner\x18\x03 \x01(\x0b\x32\x05.User\x12\x1a\n\x07node_db\x18\x04 \x03(\x0b\x32\t.NodeInfo\x12\"\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12$\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07no_save\x18\t \x01(\x08\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08\x12+\n#canned_message_plugin_message_part1\x18\r \x01(\t\x12+\n#canned_message_plugin_message_part2\x18\x0e \x01(\t\x12+\n#canned_message_plugin_message_part3\x18\x0f \x01(\t\x12+\n#canned_message_plugin_message_part4\x18\x10 \x01(\t\x12+\n#canned_message_plugin_message_part5\x18\x11 \x01(\tJ\x04\x08\x0c\x10\r\")\n\x0b\x43hannelFile\x12\x1a\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x08.ChannelBF\n\x13\x63om.geeksville.meshB\nDeviceOnlyH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1bmeshtastic/deviceonly.proto\x1a\x18meshtastic/channel.proto\x1a\x1ameshtastic/localonly.proto\x1a\x15meshtastic/mesh.proto\x1a\x1ameshtastic/telemetry.proto\x1a\x1emeshtastic/module_config.proto\"\xc6\x02\n\x0b\x44\x65viceState\x12\x1c\n\x07my_node\x18\x02 \x01(\x0b\x32\x0b.MyNodeInfo\x12\x14\n\x05owner\x18\x03 \x01(\x0b\x32\x05.User\x12\"\n\rreceive_queue\x18\x05 \x03(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07version\x18\x08 \x01(\r\x12$\n\x0frx_text_message\x18\x07 \x01(\x0b\x32\x0b.MeshPacket\x12\x0f\n\x07no_save\x18\t \x01(\x08\x12\x15\n\rdid_gps_reset\x18\x0b \x01(\x08\x12 \n\x0brx_waypoint\x18\x0c \x01(\x0b\x32\x0b.MeshPacket\x12\x39\n\x19node_remote_hardware_pins\x18\r \x03(\x0b\x32\x16.NodeRemoteHardwarePin\x12#\n\x0cnode_db_lite\x18\x0e \x03(\x0b\x32\r.NodeInfoLite\"\xab\x01\n\x0cNodeInfoLite\x12\x0b\n\x03num\x18\x01 \x01(\r\x12\x13\n\x04user\x18\x02 \x01(\x0b\x32\x05.User\x12\x1f\n\x08position\x18\x03 \x01(\x0b\x32\r.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\x0e.DeviceMetrics\x12\x0f\n\x07\x63hannel\x18\x07 \x01(\r\"\x85\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\x13.Position.LocSource\":\n\x0b\x43hannelFile\x12\x1a\n\x08\x63hannels\x18\x01 \x03(\x0b\x32\x08.Channel\x12\x0f\n\x07version\x18\x02 \x01(\r\"\xf6\x01\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\x1e\n\x08oem_font\x18\x04 \x01(\x0e\x32\x0c.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\x0c.LocalConfig\x12\x33\n\x17oem_local_module_config\x18\x08 \x01(\x0b\x32\x12.LocalModuleConfig\"J\n\x15NodeRemoteHardwarePin\x12\x10\n\x08node_num\x18\x01 \x01(\r\x12\x1f\n\x03pin\x18\x02 \x01(\x0b\x32\x12.RemoteHardwarePin*>\n\x0bScreenFonts\x12\x0e\n\nFONT_SMALL\x10\x00\x12\x0f\n\x0b\x46ONT_MEDIUM\x10\x01\x12\x0e\n\nFONT_LARGE\x10\x02\x42_\n\x13\x63om.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_SCREENFONTS = DESCRIPTOR.enum_types_by_name['ScreenFonts']
ScreenFonts = enum_type_wrapper.EnumTypeWrapper(_SCREENFONTS)
FONT_SMALL = 0
FONT_MEDIUM = 1
FONT_LARGE = 2
_LEGACYRADIOCONFIG = DESCRIPTOR.message_types_by_name['LegacyRadioConfig']
_LEGACYRADIOCONFIG_LEGACYPREFERENCES = _LEGACYRADIOCONFIG.nested_types_by_name['LegacyPreferences']
_DEVICESTATE = DESCRIPTOR.message_types_by_name['DeviceState'] _DEVICESTATE = DESCRIPTOR.message_types_by_name['DeviceState']
_NODEINFOLITE = DESCRIPTOR.message_types_by_name['NodeInfoLite']
_POSITIONLITE = DESCRIPTOR.message_types_by_name['PositionLite']
_CHANNELFILE = DESCRIPTOR.message_types_by_name['ChannelFile'] _CHANNELFILE = DESCRIPTOR.message_types_by_name['ChannelFile']
LegacyRadioConfig = _reflection.GeneratedProtocolMessageType('LegacyRadioConfig', (_message.Message,), { _OEMSTORE = DESCRIPTOR.message_types_by_name['OEMStore']
_NODEREMOTEHARDWAREPIN = DESCRIPTOR.message_types_by_name['NodeRemoteHardwarePin']
'LegacyPreferences' : _reflection.GeneratedProtocolMessageType('LegacyPreferences', (_message.Message,), {
'DESCRIPTOR' : _LEGACYRADIOCONFIG_LEGACYPREFERENCES,
'__module__' : 'deviceonly_pb2'
# @@protoc_insertion_point(class_scope:LegacyRadioConfig.LegacyPreferences)
})
,
'DESCRIPTOR' : _LEGACYRADIOCONFIG,
'__module__' : 'deviceonly_pb2'
# @@protoc_insertion_point(class_scope:LegacyRadioConfig)
})
_sym_db.RegisterMessage(LegacyRadioConfig)
_sym_db.RegisterMessage(LegacyRadioConfig.LegacyPreferences)
DeviceState = _reflection.GeneratedProtocolMessageType('DeviceState', (_message.Message,), { DeviceState = _reflection.GeneratedProtocolMessageType('DeviceState', (_message.Message,), {
'DESCRIPTOR' : _DEVICESTATE, 'DESCRIPTOR' : _DEVICESTATE,
'__module__' : 'deviceonly_pb2' '__module__' : 'meshtastic.deviceonly_pb2'
# @@protoc_insertion_point(class_scope:DeviceState) # @@protoc_insertion_point(class_scope:DeviceState)
}) })
_sym_db.RegisterMessage(DeviceState) _sym_db.RegisterMessage(DeviceState)
NodeInfoLite = _reflection.GeneratedProtocolMessageType('NodeInfoLite', (_message.Message,), {
'DESCRIPTOR' : _NODEINFOLITE,
'__module__' : 'meshtastic.deviceonly_pb2'
# @@protoc_insertion_point(class_scope:NodeInfoLite)
})
_sym_db.RegisterMessage(NodeInfoLite)
PositionLite = _reflection.GeneratedProtocolMessageType('PositionLite', (_message.Message,), {
'DESCRIPTOR' : _POSITIONLITE,
'__module__' : 'meshtastic.deviceonly_pb2'
# @@protoc_insertion_point(class_scope:PositionLite)
})
_sym_db.RegisterMessage(PositionLite)
ChannelFile = _reflection.GeneratedProtocolMessageType('ChannelFile', (_message.Message,), { ChannelFile = _reflection.GeneratedProtocolMessageType('ChannelFile', (_message.Message,), {
'DESCRIPTOR' : _CHANNELFILE, 'DESCRIPTOR' : _CHANNELFILE,
'__module__' : 'deviceonly_pb2' '__module__' : 'meshtastic.deviceonly_pb2'
# @@protoc_insertion_point(class_scope:ChannelFile) # @@protoc_insertion_point(class_scope:ChannelFile)
}) })
_sym_db.RegisterMessage(ChannelFile) _sym_db.RegisterMessage(ChannelFile)
OEMStore = _reflection.GeneratedProtocolMessageType('OEMStore', (_message.Message,), {
'DESCRIPTOR' : _OEMSTORE,
'__module__' : 'meshtastic.deviceonly_pb2'
# @@protoc_insertion_point(class_scope:OEMStore)
})
_sym_db.RegisterMessage(OEMStore)
NodeRemoteHardwarePin = _reflection.GeneratedProtocolMessageType('NodeRemoteHardwarePin', (_message.Message,), {
'DESCRIPTOR' : _NODEREMOTEHARDWAREPIN,
'__module__' : 'meshtastic.deviceonly_pb2'
# @@protoc_insertion_point(class_scope:NodeRemoteHardwarePin)
})
_sym_db.RegisterMessage(NodeRemoteHardwarePin)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nDeviceOnlyH\003Z!github.com/meshtastic/gomeshproto' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nDeviceOnlyZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_LEGACYRADIOCONFIG._serialized_start=67 _SCREENFONTS._serialized_start=1192
_LEGACYRADIOCONFIG._serialized_end=195 _SCREENFONTS._serialized_end=1254
_LEGACYRADIOCONFIG_LEGACYPREFERENCES._serialized_start=147 _DEVICESTATE._serialized_start=169
_LEGACYRADIOCONFIG_LEGACYPREFERENCES._serialized_end=195 _DEVICESTATE._serialized_end=495
_DEVICESTATE._serialized_start=198 _NODEINFOLITE._serialized_start=498
_DEVICESTATE._serialized_end=694 _NODEINFOLITE._serialized_end=669
_CHANNELFILE._serialized_start=696 _POSITIONLITE._serialized_start=672
_CHANNELFILE._serialized_end=737 _POSITIONLITE._serialized_end=805
_CHANNELFILE._serialized_start=807
_CHANNELFILE._serialized_end=865
_OEMSTORE._serialized_start=868
_OEMSTORE._serialized_end=1114
_NODEREMOTEHARDWAREPIN._serialized_start=1116
_NODEREMOTEHARDWAREPIN._serialized_end=1190
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -1,35 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: environmental_measurement.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 message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1f\x65nvironmental_measurement.proto\"\xa1\x01\n\x18\x45nvironmentalMeasurement\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x12\x16\n\x0egas_resistance\x18\x04 \x01(\x02\x12\x0f\n\x07voltage\x18\x05 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x02\x42#Z!github.com/meshtastic/gomeshprotob\x06proto3')
_ENVIRONMENTALMEASUREMENT = DESCRIPTOR.message_types_by_name['EnvironmentalMeasurement']
EnvironmentalMeasurement = _reflection.GeneratedProtocolMessageType('EnvironmentalMeasurement', (_message.Message,), {
'DESCRIPTOR' : _ENVIRONMENTALMEASUREMENT,
'__module__' : 'environmental_measurement_pb2'
# @@protoc_insertion_point(class_scope:EnvironmentalMeasurement)
})
_sym_db.RegisterMessage(EnvironmentalMeasurement)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'Z!github.com/meshtastic/gomeshproto'
_ENVIRONMENTALMEASUREMENT._serialized_start=36
_ENVIRONMENTALMEASUREMENT._serialized_end=197
# @@protoc_insertion_point(module_scope)

View File

@@ -8,8 +8,10 @@
""" """
class Globals: class Globals:
"""Globals class is a Singleton.""" """Globals class is a Singleton."""
__instance = None __instance = None
@staticmethod @staticmethod

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/localonly.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 message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
from meshtastic import config_pb2 as meshtastic_dot_config__pb2
from meshtastic import module_config_pb2 as meshtastic_dot_module__config__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/localonly.proto\x1a\x17meshtastic/config.proto\x1a\x1emeshtastic/module_config.proto\"\xb0\x02\n\x0bLocalConfig\x12$\n\x06\x64\x65vice\x18\x01 \x01(\x0b\x32\x14.Config.DeviceConfig\x12(\n\x08position\x18\x02 \x01(\x0b\x32\x16.Config.PositionConfig\x12\"\n\x05power\x18\x03 \x01(\x0b\x32\x13.Config.PowerConfig\x12&\n\x07network\x18\x04 \x01(\x0b\x32\x15.Config.NetworkConfig\x12&\n\x07\x64isplay\x18\x05 \x01(\x0b\x32\x15.Config.DisplayConfig\x12 \n\x04lora\x18\x06 \x01(\x0b\x32\x12.Config.LoRaConfig\x12*\n\tbluetooth\x18\x07 \x01(\x0b\x32\x17.Config.BluetoothConfig\x12\x0f\n\x07version\x18\x08 \x01(\r\"\xb8\x05\n\x11LocalModuleConfig\x12&\n\x04mqtt\x18\x01 \x01(\x0b\x32\x18.ModuleConfig.MQTTConfig\x12*\n\x06serial\x18\x02 \x01(\x0b\x32\x1a.ModuleConfig.SerialConfig\x12G\n\x15\x65xternal_notification\x18\x03 \x01(\x0b\x32(.ModuleConfig.ExternalNotificationConfig\x12\x37\n\rstore_forward\x18\x04 \x01(\x0b\x32 .ModuleConfig.StoreForwardConfig\x12\x31\n\nrange_test\x18\x05 \x01(\x0b\x32\x1d.ModuleConfig.RangeTestConfig\x12\x30\n\ttelemetry\x18\x06 \x01(\x0b\x32\x1d.ModuleConfig.TelemetryConfig\x12\x39\n\x0e\x63\x61nned_message\x18\x07 \x01(\x0b\x32!.ModuleConfig.CannedMessageConfig\x12(\n\x05\x61udio\x18\t \x01(\x0b\x32\x19.ModuleConfig.AudioConfig\x12;\n\x0fremote_hardware\x18\n \x01(\x0b\x32\".ModuleConfig.RemoteHardwareConfig\x12\x37\n\rneighbor_info\x18\x0b \x01(\x0b\x32 .ModuleConfig.NeighborInfoConfig\x12=\n\x10\x61mbient_lighting\x18\x0c \x01(\x0b\x32#.ModuleConfig.AmbientLightingConfig\x12=\n\x10\x64\x65tection_sensor\x18\r \x01(\x0b\x32#.ModuleConfig.DetectionSensorConfig\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')
_LOCALCONFIG = DESCRIPTOR.message_types_by_name['LocalConfig']
_LOCALMODULECONFIG = DESCRIPTOR.message_types_by_name['LocalModuleConfig']
LocalConfig = _reflection.GeneratedProtocolMessageType('LocalConfig', (_message.Message,), {
'DESCRIPTOR' : _LOCALCONFIG,
'__module__' : 'meshtastic.localonly_pb2'
# @@protoc_insertion_point(class_scope:LocalConfig)
})
_sym_db.RegisterMessage(LocalConfig)
LocalModuleConfig = _reflection.GeneratedProtocolMessageType('LocalModuleConfig', (_message.Message,), {
'DESCRIPTOR' : _LOCALMODULECONFIG,
'__module__' : 'meshtastic.localonly_pb2'
# @@protoc_insertion_point(class_scope:LocalModuleConfig)
})
_sym_db.RegisterMessage(LocalModuleConfig)
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'
_LOCALCONFIG._serialized_start=88
_LOCALCONFIG._serialized_end=392
_LOCALMODULECONFIG._serialized_start=395
_LOCALMODULECONFIG._serialized_end=1091
# @@protoc_insertion_point(module_scope)

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: mqtt.proto # source: meshtastic/mqtt.proto
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import descriptor_pool as _descriptor_pool
@@ -12,17 +12,17 @@ from google.protobuf import symbol_database as _symbol_database
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
from . import mesh_pb2 as mesh__pb2 from meshtastic import mesh_pb2 as meshtastic_dot_mesh__pb2
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\nmqtt.proto\x1a\nmesh.proto\"V\n\x0fServiceEnvelope\x12\x1b\n\x06packet\x18\x01 \x01(\x0b\x32\x0b.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\tBF\n\x13\x63om.geeksville.meshB\nMQTTProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15meshtastic/mqtt.proto\x1a\x15meshtastic/mesh.proto\"V\n\x0fServiceEnvelope\x12\x1b\n\x06packet\x18\x01 \x01(\x0b\x32\x0b.MeshPacket\x12\x12\n\nchannel_id\x18\x02 \x01(\t\x12\x12\n\ngateway_id\x18\x03 \x01(\tB_\n\x13\x63om.geeksville.meshB\nMQTTProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_SERVICEENVELOPE = DESCRIPTOR.message_types_by_name['ServiceEnvelope'] _SERVICEENVELOPE = DESCRIPTOR.message_types_by_name['ServiceEnvelope']
ServiceEnvelope = _reflection.GeneratedProtocolMessageType('ServiceEnvelope', (_message.Message,), { ServiceEnvelope = _reflection.GeneratedProtocolMessageType('ServiceEnvelope', (_message.Message,), {
'DESCRIPTOR' : _SERVICEENVELOPE, 'DESCRIPTOR' : _SERVICEENVELOPE,
'__module__' : 'mqtt_pb2' '__module__' : 'meshtastic.mqtt_pb2'
# @@protoc_insertion_point(class_scope:ServiceEnvelope) # @@protoc_insertion_point(class_scope:ServiceEnvelope)
}) })
_sym_db.RegisterMessage(ServiceEnvelope) _sym_db.RegisterMessage(ServiceEnvelope)
@@ -30,7 +30,7 @@ _sym_db.RegisterMessage(ServiceEnvelope)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nMQTTProtosH\003Z!github.com/meshtastic/gomeshproto' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\nMQTTProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_SERVICEENVELOPE._serialized_start=26 _SERVICEENVELOPE._serialized_start=48
_SERVICEENVELOPE._serialized_end=112 _SERVICEENVELOPE._serialized_end=134
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -1,40 +1,59 @@
"""Node class """Node class
""" """
import logging
import base64 import base64
import logging
import time
from google.protobuf.json_format import MessageToJson from google.protobuf.json_format import MessageToJson
from meshtastic import portnums_pb2, apponly_pb2, admin_pb2, channel_pb2
from meshtastic.util import pskToString, stripnl, Timeout, our_exit, fromPSK from meshtastic import admin_pb2, apponly_pb2, channel_pb2, localonly_pb2, portnums_pb2
from meshtastic.util import (
Timeout,
camel_to_snake,
fromPSK,
our_exit,
pskToString,
stripnl,
)
class Node: class Node:
"""A model of a (local or remote) node in the mesh """A model of a (local or remote) node in the mesh
Includes methods for radioConfig and channels Includes methods for localConfig, moduleConfig and channels
""" """
def __init__(self, iface, nodeNum, noProto=False): def __init__(self, iface, nodeNum, noProto=False):
"""Constructor""" """Constructor"""
self.iface = iface self.iface = iface
self.nodeNum = nodeNum self.nodeNum = nodeNum
self.radioConfig = None self.localConfig = localonly_pb2.LocalConfig()
self.moduleConfig = localonly_pb2.LocalModuleConfig()
self.channels = None self.channels = None
self._timeout = Timeout(maxSecs=300) self._timeout = Timeout(maxSecs=300)
self.partialChannels = None self.partialChannels = None
self.noProto = noProto self.noProto = noProto
self.cannedPluginMessage = None
self.cannedPluginMessageMessages = None
self.ringtone = None
self.ringtonePart = None
self.gotResponse = None
def showChannels(self): def showChannels(self):
"""Show human readable description of our channels.""" """Show human readable description of our channels."""
print("Channels:") print("Channels:")
if self.channels: if self.channels:
logging.debug(f'self.channels:{self.channels}') logging.debug(f"self.channels:{self.channels}")
for c in self.channels: for c in self.channels:
#print('c.settings.psk:', c.settings.psk) # print('c.settings.psk:', c.settings.psk)
cStr = stripnl(MessageToJson(c.settings)) cStr = stripnl(MessageToJson(c.settings))
# only show if there is no psk (meaning disabled channel) # don't show disabled channels
if c.settings.psk: if channel_pb2.Channel.Role.Name(c.role) != "DISABLED":
print(f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}") print(
f" {channel_pb2.Channel.Role.Name(c.role)} psk={pskToString(c.settings.psk)} {cStr}"
)
publicURL = self.getURL(includeAll=False) publicURL = self.getURL(includeAll=False)
adminURL = self.getURL(includeAll=True) adminURL = self.getURL(includeAll=True)
print(f"\nPrimary channel URL: {publicURL}") print(f"\nPrimary channel URL: {publicURL}")
@@ -44,19 +63,75 @@ class Node:
def showInfo(self): def showInfo(self):
"""Show human readable description of our node""" """Show human readable description of our node"""
prefs = "" prefs = ""
if self.radioConfig and self.radioConfig.preferences: if self.localConfig:
prefs = stripnl(MessageToJson(self.radioConfig.preferences)) prefs = stripnl(MessageToJson(self.localConfig))
print(f"Preferences: {prefs}\n") print(f"Preferences: {prefs}\n")
prefs = ""
if self.moduleConfig:
prefs = stripnl(MessageToJson(self.moduleConfig))
print(f"Module preferences: {prefs}\n")
self.showChannels() self.showChannels()
def requestConfig(self): def requestChannels(self):
"""Send regular MeshPackets to ask for settings and channels.""" """Send regular MeshPackets to ask channels."""
logging.debug(f"requestConfig for nodeNum:{self.nodeNum}") logging.debug(f"requestChannels for nodeNum:{self.nodeNum}")
self.radioConfig = None
self.channels = None self.channels = None
self.partialChannels = [] # We keep our channels in a temp array until finished self.partialChannels = [] # We keep our channels in a temp array until finished
self._requestSettings() self._requestChannel(0)
def onResponseRequestSettings(self, p):
"""Handle the response packets for requesting settings _requestSettings()"""
logging.debug(f"onResponseRequestSetting() p:{p}")
if "routing" in p["decoded"]:
if p["decoded"]["routing"]["errorReason"] != "NONE":
print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}')
self.iface._acknowledgment.receivedNak = True
else:
self.iface._acknowledgment.receivedAck = True
print("")
adminMessage = p["decoded"]["admin"]
if "getConfigResponse" in adminMessage:
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)
elif "getModuleConfigResponse" in adminMessage:
resp = adminMessage["getModuleConfigResponse"]
field = list(resp.keys())[0]
config_type = self.moduleConfig.DESCRIPTOR.fields_by_name.get(
camel_to_snake(field)
)
config_values = getattr(self.moduleConfig, config_type.name)
else:
print(
"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)}")
def requestConfig(self, configType):
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onResponseRequestSettings
print("Requesting current config from remote node (this can take a while).")
msgIndex = configType.index
if configType.containing_type.full_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)
if onResponse:
self.iface.waitForAckNak()
def turnOffEncryptionOnPrimaryChannel(self): def turnOffEncryptionOnPrimaryChannel(self):
"""Turn off encryption on primary channel.""" """Turn off encryption on primary channel."""
@@ -64,34 +139,81 @@ class Node:
print("Writing modified channels to device") print("Writing modified channels to device")
self.writeChannel(0) self.writeChannel(0)
def waitForConfig(self): def waitForConfig(self, attribute="channels"):
"""Block until radio config is received. Returns True if config has been received.""" """Block until radio config is received. Returns True if config has been received."""
return self._timeout.waitForSet(self, attrs=('radioConfig', 'channels')) return self._timeout.waitForSet(self, attrs=("localConfig", attribute))
def writeConfig(self): def writeConfig(self, config_name):
"""Write the current (edited) radioConfig to the device""" """Write the current (edited) localConfig to the device"""
if self.radioConfig is None: if self.localConfig is None:
our_exit("Error: No RadioConfig has been read") our_exit("Error: No localConfig has been read")
p = admin_pb2.AdminMessage() p = admin_pb2.AdminMessage()
p.set_radio.CopyFrom(self.radioConfig)
self._sendAdmin(p) if config_name == "device":
logging.debug("Wrote config") p.set_config.device.CopyFrom(self.localConfig.device)
elif config_name == "position":
p.set_config.position.CopyFrom(self.localConfig.position)
elif config_name == "power":
p.set_config.power.CopyFrom(self.localConfig.power)
elif config_name == "network":
p.set_config.network.CopyFrom(self.localConfig.network)
elif config_name == "display":
p.set_config.display.CopyFrom(self.localConfig.display)
elif config_name == "lora":
p.set_config.lora.CopyFrom(self.localConfig.lora)
elif config_name == "bluetooth":
p.set_config.bluetooth.CopyFrom(self.localConfig.bluetooth)
elif config_name == "mqtt":
p.set_module_config.mqtt.CopyFrom(self.moduleConfig.mqtt)
elif config_name == "serial":
p.set_module_config.serial.CopyFrom(self.moduleConfig.serial)
elif config_name == "external_notification":
p.set_module_config.external_notification.CopyFrom(
self.moduleConfig.external_notification
)
elif config_name == "store_forward":
p.set_module_config.store_forward.CopyFrom(self.moduleConfig.store_forward)
elif config_name == "range_test":
p.set_module_config.range_test.CopyFrom(self.moduleConfig.range_test)
elif config_name == "telemetry":
p.set_module_config.telemetry.CopyFrom(self.moduleConfig.telemetry)
elif config_name == "canned_message":
p.set_module_config.canned_message.CopyFrom(
self.moduleConfig.canned_message
)
elif config_name == "audio":
p.set_module_config.audio.CopyFrom(self.moduleConfig.audio)
elif config_name == "remote_hardware":
p.set_module_config.remote_hardware.CopyFrom(
self.moduleConfig.remote_hardware
)
elif config_name == "neighbor_info":
p.set_module_config.neighbor_info.CopyFrom(self.moduleConfig.neighbor_info)
elif config_name == "detection_sensor":
p.set_module_config.detection_sensor.CopyFrom(self.moduleConfig.detection_sensor)
else:
our_exit(f"Error: No valid config with name {config_name}")
logging.debug(f"Wrote: {config_name}")
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
self._sendAdmin(p, onResponse=onResponse)
def writeChannel(self, channelIndex, adminIndex=0): def writeChannel(self, channelIndex, adminIndex=0):
"""Write the current (edited) channel to the device""" """Write the current (edited) channel to the device"""
p = admin_pb2.AdminMessage() p = admin_pb2.AdminMessage()
p.set_channel.CopyFrom(self.channels[channelIndex]) p.set_channel.CopyFrom(self.channels[channelIndex])
self._sendAdmin(p, adminIndex=adminIndex) self._sendAdmin(p, adminIndex=adminIndex)
logging.debug(f"Wrote channel {channelIndex}") logging.debug(f"Wrote channel {channelIndex}")
def getChannelByChannelIndex(self, channelIndex): def getChannelByChannelIndex(self, channelIndex):
"""Get channel by channelIndex """Get channel by channelIndex
channelIndex: number, typically 0-7; based on max number channels channelIndex: number, typically 0-7; based on max number channels
returns: None if there is no channel found returns: None if there is no channel found
""" """
ch = None ch = None
if self.channels and 0 <= channelIndex < len(self.channels): if self.channels and 0 <= channelIndex < len(self.channels):
@@ -99,9 +221,12 @@ class Node:
return ch return ch
def deleteChannel(self, channelIndex): def deleteChannel(self, channelIndex):
"""Delete the specifed channelIndex and shift other channels up""" """Delete the specified channelIndex and shift other channels up"""
ch = self.channels[channelIndex] ch = self.channels[channelIndex]
if ch.role not in (channel_pb2.Channel.Role.SECONDARY, channel_pb2.Channel.Role.DISABLED): if ch.role not in (
channel_pb2.Channel.Role.SECONDARY,
channel_pb2.Channel.Role.DISABLED,
):
our_exit("Warning: Only SECONDARY channels can be deleted") our_exit("Warning: Only SECONDARY channels can be deleted")
# we are careful here because if we move the "admin" channel the channelIndex we need to use # we are careful here because if we move the "admin" channel the channelIndex we need to use
@@ -112,7 +237,7 @@ class Node:
self._fixupChannels() # expand back to 8 channels self._fixupChannels() # expand back to 8 channels
index = channelIndex index = channelIndex
while index < self.iface.myInfo.max_channels: while index < 8:
self.writeChannel(index, adminIndex=adminIndex) self.writeChannel(index, adminIndex=adminIndex)
index += 1 index += 1
@@ -125,7 +250,7 @@ class Node:
def getChannelByName(self, name): def getChannelByName(self, name):
"""Try to find the named channel or return None""" """Try to find the named channel or return None"""
for c in (self.channels or []): for c in self.channels or []:
if c.settings and c.settings.name == name: if c.settings and c.settings.name == name:
return c return c
return None return None
@@ -145,44 +270,33 @@ class Node:
else: else:
return 0 return 0
def setOwner(self, long_name=None, short_name=None, is_licensed=False, team=None): def setOwner(self, long_name=None, short_name=None, is_licensed=False):
"""Set device owner name""" """Set device owner name"""
logging.debug(f"in setOwner nodeNum:{self.nodeNum}") logging.debug(f"in setOwner nodeNum:{self.nodeNum}")
nChars = 3
minChars = 2
if long_name is not None:
long_name = long_name.strip()
if short_name is None:
words = long_name.split()
if len(long_name) <= nChars:
short_name = long_name
elif len(words) >= minChars:
short_name = ''.join(map(lambda word: word[0], words))
else:
trans = str.maketrans(dict.fromkeys('aeiouAEIOU'))
short_name = long_name[0] + long_name[1:].translate(trans)
if len(short_name) < nChars:
short_name = long_name[:nChars]
p = admin_pb2.AdminMessage() p = admin_pb2.AdminMessage()
nChars = 4
if long_name is not None: if long_name is not None:
long_name = long_name.strip()
p.set_owner.long_name = long_name p.set_owner.long_name = long_name
p.set_owner.is_licensed = is_licensed
if short_name is not None: if short_name is not None:
short_name = short_name.strip() short_name = short_name.strip()
if len(short_name) > nChars: if len(short_name) > nChars:
short_name = short_name[:nChars] short_name = short_name[:nChars]
print(f"Maximum is 4 characters, truncated to {short_name}")
p.set_owner.short_name = short_name p.set_owner.short_name = short_name
p.set_owner.is_licensed = is_licensed
if team is not None:
p.set_owner.team = team
# Note: These debug lines are used in unit tests # Note: These debug lines are used in unit tests
logging.debug(f'p.set_owner.long_name:{p.set_owner.long_name}:') logging.debug(f"p.set_owner.long_name:{p.set_owner.long_name}:")
logging.debug(f'p.set_owner.short_name:{p.set_owner.short_name}:') logging.debug(f"p.set_owner.short_name:{p.set_owner.short_name}:")
logging.debug(f'p.set_owner.is_licensed:{p.set_owner.is_licensed}') logging.debug(f"p.set_owner.is_licensed:{p.set_owner.is_licensed}")
logging.debug(f'p.set_owner.team:{p.set_owner.team}') # If sending to a remote node, wait for ACK/NAK
return self._sendAdmin(p) if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def getURL(self, includeAll: bool = True): def getURL(self, includeAll: bool = True):
"""The sharable URL that describes the current channel""" """The sharable URL that describes the current channel"""
@@ -190,18 +304,23 @@ class Node:
channelSet = apponly_pb2.ChannelSet() channelSet = apponly_pb2.ChannelSet()
if self.channels: if self.channels:
for c in self.channels: for c in self.channels:
if c.role == channel_pb2.Channel.Role.PRIMARY or (includeAll and c.role == channel_pb2.Channel.Role.SECONDARY): if c.role == channel_pb2.Channel.Role.PRIMARY or (
includeAll and c.role == channel_pb2.Channel.Role.SECONDARY
):
channelSet.settings.append(c.settings) channelSet.settings.append(c.settings)
channelSet.lora_config.CopyFrom(self.localConfig.lora)
some_bytes = channelSet.SerializeToString() some_bytes = channelSet.SerializeToString()
s = base64.urlsafe_b64encode(some_bytes).decode('ascii') s = base64.urlsafe_b64encode(some_bytes).decode("ascii")
return f"https://www.meshtastic.org/d/#{s}".replace("=", "") s = s.replace("=", "").replace("+", "-").replace("/", "_")
return f"https://meshtastic.org/e/#{s}"
def setURL(self, url): def setURL(self, url):
"""Set mesh network URL""" """Set mesh network URL"""
if self.radioConfig is None: if self.localConfig is None:
our_exit("Warning: No RadioConfig has been read") our_exit("Warning: No Config has been read")
# URLs are of the form https://www.meshtastic.org/d/#{base64_channel_set} # URLs are of the form https://meshtastic.org/d/#{base64_channel_set}
# Split on '/#' to find the base64 encoded channel settings # Split on '/#' to find the base64 encoded channel settings
splitURL = url.split("/#") splitURL = url.split("/#")
b64 = splitURL[-1] b64 = splitURL[-1]
@@ -211,69 +330,186 @@ class Node:
# per https://stackoverflow.com/a/9807138 # per https://stackoverflow.com/a/9807138
missing_padding = len(b64) % 4 missing_padding = len(b64) % 4
if missing_padding: if missing_padding:
b64 += '=' * (4 - missing_padding) b64 += "=" * (4 - missing_padding)
decodedURL = base64.urlsafe_b64decode(b64) decodedURL = base64.urlsafe_b64decode(b64)
channelSet = apponly_pb2.ChannelSet() channelSet = apponly_pb2.ChannelSet()
channelSet.ParseFromString(decodedURL) channelSet.ParseFromString(decodedURL)
if len(channelSet.settings) == 0: if len(channelSet.settings) == 0:
our_exit("Warning: There were no settings.") our_exit("Warning: There were no settings.")
i = 0 i = 0
for chs in channelSet.settings: for chs in channelSet.settings:
ch = channel_pb2.Channel() ch = channel_pb2.Channel()
ch.role = channel_pb2.Channel.Role.PRIMARY if i == 0 else channel_pb2.Channel.Role.SECONDARY ch.role = (
channel_pb2.Channel.Role.PRIMARY
if i == 0
else channel_pb2.Channel.Role.SECONDARY
)
ch.index = i ch.index = i
ch.settings.CopyFrom(chs) ch.settings.CopyFrom(chs)
self.channels[ch.index] = ch self.channels[ch.index] = ch
logging.debug(f'Channel i:{i} ch:{ch}') logging.debug(f"Channel i:{i} ch:{ch}")
self.writeChannel(ch.index) self.writeChannel(ch.index)
i = i + 1 i = i + 1
p = admin_pb2.AdminMessage()
p.set_config.lora.CopyFrom(channelSet.lora_config)
self._sendAdmin(p)
def onResponseRequestSettings(self, p): def onResponseRequestRingtone(self, p):
"""Handle the response packet for requesting settings _requestSettings()""" """Handle the response packet for requesting ringtone part 1"""
logging.debug(f'onResponseRequestSetting() p:{p}') logging.debug(f"onResponseRequestRingtone() p:{p}")
errorFound = False errorFound = False
if 'routing' in p["decoded"]: if "routing" in p["decoded"]:
if p["decoded"]["routing"]["errorReason"] != "NONE": if p["decoded"]["routing"]["errorReason"] != "NONE":
errorFound = True errorFound = True
print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}') print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}')
if errorFound is False: if errorFound is False:
self.radioConfig = p["decoded"]["admin"]["raw"].get_radio_response if "decoded" in p:
logging.debug(f'self.radioConfig:{self.radioConfig}') if "admin" in p["decoded"]:
logging.debug("Received radio config, now fetching channels...") if "raw" in p["decoded"]["admin"]:
self._timeout.reset() # We made foreward progress self.ringtonePart = p["decoded"]["admin"][
self._requestChannel(0) # now start fetching channels "raw"
].get_ringtone_response
logging.debug(f"self.ringtonePart:{self.ringtonePart}")
self.gotResponse = True
def get_ringtone(self):
"""Get the ringtone. Concatenate all pieces together and return a single string."""
logging.debug(f"in get_ringtone()")
if not self.ringtone:
p1 = admin_pb2.AdminMessage()
p1.get_ringtone_request = True
self.gotResponse = False
self._sendAdmin(
p1, wantResponse=True, onResponse=self.onResponseRequestRingtone
)
while self.gotResponse is False:
time.sleep(0.1)
def _requestSettings(self): logging.debug(f"self.ringtone:{self.ringtone}")
"""Done with initial config messages, now send regular
MeshPackets to ask for settings."""
p = admin_pb2.AdminMessage()
p.get_radio_request = True
# TODO: should we check that localNode has an 'admin' channel? self.ringtone = ""
# Show progress message for super slow operations if self.ringtonePart:
if self != self.iface.localNode: self.ringtone += self.ringtonePart
print("Requesting preferences from remote node.")
print("Be sure:")
print(" 1. There is a SECONDARY channel named 'admin'.")
print(" 2. The '--seturl' was used to configure.")
print(" 3. All devices have the same modem config. (i.e., '--ch-longfast')")
print(" 4. All devices have been rebooted after all of the above. (optional, but recommended)")
print("Note: This could take a while (it requests remote channel configs, then writes config)")
return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestSettings) print(f"ringtone:{self.ringtone}")
logging.debug(f"ringtone:{self.ringtone}")
return self.ringtone
def set_ringtone(self, ringtone):
"""Set the ringtone. The ringtone length must be less than 230 character."""
if len(ringtone) > 230:
our_exit("Warning: The ringtone must be less than 230 characters.")
# split into chunks
chunks = []
chunks_size = 230
for i in range(0, len(ringtone), chunks_size):
chunks.append(ringtone[i : i + chunks_size])
# for each chunk, send a message to set the values
# for i in range(0, len(chunks)):
for i, chunk in enumerate(chunks):
p = admin_pb2.AdminMessage()
# TODO: should be a way to improve this
if i == 0:
p.set_ringtone_message = chunk
logging.debug(f"Setting ringtone '{chunk}' part {i+1}")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def onResponseRequestCannedMessagePluginMessageMessages(self, p):
"""Handle the response packet for requesting canned message plugin message part 1"""
logging.debug(f"onResponseRequestCannedMessagePluginMessageMessages() p:{p}")
errorFound = False
if "routing" in p["decoded"]:
if p["decoded"]["routing"]["errorReason"] != "NONE":
errorFound = True
print(f'Error on response: {p["decoded"]["routing"]["errorReason"]}')
if errorFound is False:
if "decoded" in p:
if "admin" in p["decoded"]:
if "raw" in p["decoded"]["admin"]:
self.cannedPluginMessageMessages = p["decoded"]["admin"][
"raw"
].get_canned_message_module_messages_response
logging.debug(
f"self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}"
)
self.gotResponse = True
def get_canned_message(self):
"""Get the canned message string. Concatenate all pieces together and return a single string."""
logging.debug(f"in get_canned_message()")
if not self.cannedPluginMessage:
p1 = admin_pb2.AdminMessage()
p1.get_canned_message_module_messages_request = True
self.gotResponse = False
self._sendAdmin(
p1,
wantResponse=True,
onResponse=self.onResponseRequestCannedMessagePluginMessageMessages,
)
while self.gotResponse is False:
time.sleep(0.1)
logging.debug(
f"self.cannedPluginMessageMessages:{self.cannedPluginMessageMessages}"
)
self.cannedPluginMessage = ""
if self.cannedPluginMessageMessages:
self.cannedPluginMessage += self.cannedPluginMessageMessages
print(f"canned_plugin_message:{self.cannedPluginMessage}")
logging.debug(f"canned_plugin_message:{self.cannedPluginMessage}")
return self.cannedPluginMessage
def set_canned_message(self, message):
"""Set the canned message. The canned messages length must be less than 200 character."""
if len(message) > 200:
our_exit("Warning: The canned message must be less than 200 characters.")
# split into chunks
chunks = []
chunks_size = 200
for i in range(0, len(message), chunks_size):
chunks.append(message[i : i + chunks_size])
# for each chunk, send a message to set the values
# for i in range(0, len(chunks)):
for i, chunk in enumerate(chunks):
p = admin_pb2.AdminMessage()
# TODO: should be a way to improve this
if i == 0:
p.set_canned_message_module_messages = chunk
logging.debug(f"Setting canned message '{chunk}' part {i+1}")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def exitSimulator(self): def exitSimulator(self):
"""Tell a simulator node to exit (this message """Tell a simulator node to exit (this message
is ignored for other nodes)""" is ignored for other nodes)"""
p = admin_pb2.AdminMessage() p = admin_pb2.AdminMessage()
p.exit_simulator = True p.exit_simulator = True
logging.debug('in exitSimulator()') logging.debug("in exitSimulator()")
return self._sendAdmin(p) return self._sendAdmin(p)
@@ -283,7 +519,51 @@ class Node:
p.reboot_seconds = secs p.reboot_seconds = secs
logging.info(f"Telling node to reboot in {secs} seconds") logging.info(f"Telling node to reboot in {secs} seconds")
return self._sendAdmin(p) # If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def beginSettingsTransaction(self):
"""Tell the node to open a transaction to edit settings."""
p = admin_pb2.AdminMessage()
p.begin_edit_settings = True
logging.info(f"Telling open a transaction to edit settings")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def commitSettingsTransaction(self):
"""Tell the node to commit the open transaction for editing settings."""
p = admin_pb2.AdminMessage()
p.commit_edit_settings = True
logging.info(f"Telling node to commit open transaction for editing settings")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def rebootOTA(self, secs: int = 10):
"""Tell the node to reboot into factory firmware."""
p = admin_pb2.AdminMessage()
p.reboot_ota_seconds = secs
logging.info(f"Telling node to reboot to OTA in {secs} seconds")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def shutdown(self, secs: int = 10): def shutdown(self, secs: int = 10):
"""Tell the node to shutdown.""" """Tell the node to shutdown."""
@@ -291,7 +571,48 @@ class Node:
p.shutdown_seconds = secs p.shutdown_seconds = secs
logging.info(f"Telling node to shutdown in {secs} seconds") logging.info(f"Telling node to shutdown in {secs} seconds")
return self._sendAdmin(p) # If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def getMetadata(self):
"""Get the node's metadata."""
p = admin_pb2.AdminMessage()
p.get_device_metadata_request = True
logging.info(f"Requesting device metadata")
return self._sendAdmin(
p, wantResponse=True, onResponse=self.onRequestGetMetadata
)
def factoryReset(self):
"""Tell the node to factory reset."""
p = admin_pb2.AdminMessage()
p.factory_reset = True
logging.info(f"Telling node to factory reset")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def resetNodeDb(self):
"""Tell the node to reset its list of nodes."""
p = admin_pb2.AdminMessage()
p.nodedb_reset = True
logging.info(f"Telling node to reset the NodeDB")
# If sending to a remote node, wait for ACK/NAK
if self == self.iface.localNode:
onResponse = None
else:
onResponse = self.onAckNak
return self._sendAdmin(p, onResponse=onResponse)
def _fixupChannels(self): def _fixupChannels(self):
"""Fixup indexes and add disabled channels as needed""" """Fixup indexes and add disabled channels as needed"""
@@ -308,20 +629,59 @@ class Node:
# Add extra disabled channels as needed # Add extra disabled channels as needed
index = len(self.channels) index = len(self.channels)
while index < self.iface.myInfo.max_channels: while index < 8:
ch = channel_pb2.Channel() ch = channel_pb2.Channel()
ch.role = channel_pb2.Channel.Role.DISABLED ch.role = channel_pb2.Channel.Role.DISABLED
ch.index = index ch.index = index
self.channels.append(ch) self.channels.append(ch)
index += 1 index += 1
def onRequestGetMetadata(self, p):
"""Handle the response packet for requesting device metadata getMetadata()"""
logging.debug(f"onRequestGetMetadata() p:{p}")
if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name(
portnums_pb2.PortNum.ROUTING_APP
):
if p["decoded"]["routing"]["errorReason"] != "NONE":
logging.warning(
f'Metadata request failed, error reason: {p["decoded"]["routing"]["errorReason"]}'
)
self._timeout.expireTime = time.time() # Do not wait any longer
return # Don't try to parse this routing message
logging.debug(f"Retrying metadata request.")
self.getMetadata()
return
c = p["decoded"]["admin"]["raw"].get_device_metadata_response
self._timeout.reset() # We made forward progress
logging.debug(f"Received metadata {stripnl(c)}")
print(f"\nfirmware_version: {c.firmware_version}")
print(f"device_state_version: {c.device_state_version}")
def onResponseRequestChannel(self, p): def onResponseRequestChannel(self, p):
"""Handle the response packet for requesting a channel _requestChannel()""" """Handle the response packet for requesting a channel _requestChannel()"""
logging.debug(f'onResponseRequestChannel() p:{p}') logging.debug(f"onResponseRequestChannel() p:{p}")
if p["decoded"]["portnum"] == portnums_pb2.PortNum.Name(
portnums_pb2.PortNum.ROUTING_APP
):
if p["decoded"]["routing"]["errorReason"] != "NONE":
logging.warning(
f'Channel request failed, error reason: {p["decoded"]["routing"]["errorReason"]}'
)
self._timeout.expireTime = time.time() # Do not wait any longer
return # Don't try to parse this routing message
lastTried = 0
if len(self.partialChannels) > 0:
lastTried = self.partialChannels[-1].index
logging.debug(f"Retrying previous channel request.")
self._requestChannel(lastTried)
return
c = p["decoded"]["admin"]["raw"].get_channel_response c = p["decoded"]["admin"]["raw"].get_channel_response
self.partialChannels.append(c) self.partialChannels.append(c)
self._timeout.reset() # We made foreward progress self._timeout.reset() # We made forward progress
logging.debug(f"Received channel {stripnl(c)}") logging.debug(f"Received channel {stripnl(c)}")
index = c.index index = c.index
@@ -330,9 +690,11 @@ class Node:
# Once we see a response that has NO settings, assume # Once we see a response that has NO settings, assume
# we are at the end of channels and stop fetching # we are at the end of channels and stop fetching
quitEarly = (c.role == channel_pb2.Channel.Role.DISABLED) and fastChannelDownload quitEarly = (
c.role == channel_pb2.Channel.Role.DISABLED
) and fastChannelDownload
if quitEarly or index >= self.iface.myInfo.max_channels - 1: if quitEarly or index >= 8 - 1:
logging.debug("Finished downloading channels") logging.debug("Finished downloading channels")
self.channels = self.partialChannels self.channels = self.partialChannels
@@ -343,37 +705,70 @@ class Node:
else: else:
self._requestChannel(index + 1) self._requestChannel(index + 1)
def onAckNak(self, p):
if p["decoded"]["routing"]["errorReason"] != "NONE":
print(
f'Received a NAK, error reason: {p["decoded"]["routing"]["errorReason"]}'
)
self.iface._acknowledgment.receivedNak = True
else:
if int(p["from"]) == self.iface.localNode.nodeNum:
print(
f"Received an implicit ACK. Packet will likely arrive, but cannot be guaranteed."
)
self.iface._acknowledgment.receivedImplAck = True
else:
print(f"Received an ACK.")
self.iface._acknowledgment.receivedAck = True
def _requestChannel(self, channelNum: int): def _requestChannel(self, channelNum: int):
"""Done with initial config messages, now send regular """Done with initial config messages, now send regular
MeshPackets to ask for settings""" MeshPackets to ask for settings"""
p = admin_pb2.AdminMessage() p = admin_pb2.AdminMessage()
p.get_channel_request = channelNum + 1 p.get_channel_request = channelNum + 1
# Show progress message for super slow operations # Show progress message for super slow operations
if self != self.iface.localNode: if self != self.iface.localNode:
print(f"Requesting channel {channelNum} info from remote node (this could take a while)") print(
logging.debug(f"Requesting channel {channelNum} info from remote node (this could take a while)") f"Requesting channel {channelNum} info from remote node (this could take a while)"
)
logging.debug(
f"Requesting channel {channelNum} info from remote node (this could take a while)"
)
else: else:
logging.debug(f"Requesting channel {channelNum}") logging.debug(f"Requesting channel {channelNum}")
return self._sendAdmin(p, wantResponse=True, onResponse=self.onResponseRequestChannel) return self._sendAdmin(
p, wantResponse=True, onResponse=self.onResponseRequestChannel
)
# pylint: disable=R1710 # pylint: disable=R1710
def _sendAdmin(self, p: admin_pb2.AdminMessage, wantResponse=False, def _sendAdmin(
onResponse=None, adminIndex=0): self,
p: admin_pb2.AdminMessage,
wantResponse=True,
onResponse=None,
adminIndex=0,
):
"""Send an admin message to the specified node (or the local node if destNodeNum is zero)""" """Send an admin message to the specified node (or the local node if destNodeNum is zero)"""
if self.noProto: if self.noProto:
logging.warning(f"Not sending packet because protocol use is disabled by noProto") logging.warning(
f"Not sending packet because protocol use is disabled by noProto"
)
else: else:
if adminIndex == 0: # unless a special channel index was used, we want to use the admin index if (
adminIndex == 0
): # unless a special channel index was used, we want to use the admin index
adminIndex = self.iface.localNode._getAdminChannelIndex() adminIndex = self.iface.localNode._getAdminChannelIndex()
logging.debug(f'adminIndex:{adminIndex}') logging.debug(f"adminIndex:{adminIndex}")
return self.iface.sendData(p, self.nodeNum, return self.iface.sendData(
portNum=portnums_pb2.PortNum.ADMIN_APP, p,
wantAck=True, self.nodeNum,
wantResponse=wantResponse, portNum=portnums_pb2.PortNum.ADMIN_APP,
onResponse=onResponse, wantAck=False,
channelIndex=adminIndex) wantResponse=wantResponse,
onResponse=onResponse,
channelIndex=adminIndex,
)

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: portnums.proto # source: meshtastic/portnums.proto
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf.internal import enum_type_wrapper from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
@@ -15,7 +15,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eportnums.proto*\xcb\x02\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\r\n\tREPLY_APP\x10 \x12\x11\n\rIP_TUNNEL_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!\n\x1d\x45NVIRONMENTAL_MEASUREMENT_APP\x10\x43\x12\x0b\n\x07ZPS_APP\x10\x44\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\x44\n\x13\x63om.geeksville.meshB\x08PortnumsH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x19meshtastic/portnums.proto*\xd4\x03\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\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\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')
_PORTNUM = DESCRIPTOR.enum_types_by_name['PortNum'] _PORTNUM = DESCRIPTOR.enum_types_by_name['PortNum']
PortNum = enum_type_wrapper.EnumTypeWrapper(_PORTNUM) PortNum = enum_type_wrapper.EnumTypeWrapper(_PORTNUM)
@@ -26,13 +26,20 @@ POSITION_APP = 3
NODEINFO_APP = 4 NODEINFO_APP = 4
ROUTING_APP = 5 ROUTING_APP = 5
ADMIN_APP = 6 ADMIN_APP = 6
TEXT_MESSAGE_COMPRESSED_APP = 7
WAYPOINT_APP = 8
AUDIO_APP = 9
DETECTION_SENSOR_APP = 10
REPLY_APP = 32 REPLY_APP = 32
IP_TUNNEL_APP = 33 IP_TUNNEL_APP = 33
SERIAL_APP = 64 SERIAL_APP = 64
STORE_FORWARD_APP = 65 STORE_FORWARD_APP = 65
RANGE_TEST_APP = 66 RANGE_TEST_APP = 66
ENVIRONMENTAL_MEASUREMENT_APP = 67 TELEMETRY_APP = 67
ZPS_APP = 68 ZPS_APP = 68
SIMULATOR_APP = 69
TRACEROUTE_APP = 70
NEIGHBORINFO_APP = 71
PRIVATE_APP = 256 PRIVATE_APP = 256
ATAK_FORWARDER = 257 ATAK_FORWARDER = 257
MAX = 511 MAX = 511
@@ -41,7 +48,7 @@ MAX = 511
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\010PortnumsH\003Z!github.com/meshtastic/gomeshproto' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\010PortnumsZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_PORTNUM._serialized_start=19 _PORTNUM._serialized_start=30
_PORTNUM._serialized_end=350 _PORTNUM._serialized_end=498
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +1,15 @@
"""Remote hardware """Remote hardware
""" """
import logging import logging
from pubsub import pub from pubsub import pub
from meshtastic import portnums_pb2, remote_hardware_pb2 from meshtastic import portnums_pb2, remote_hardware_pb2
from meshtastic.util import our_exit from meshtastic.util import our_exit
def onGPIOreceive(packet, interface): def onGPIOreceive(packet, interface):
"""Callback for received GPIO responses """Callback for received GPIO responses"""
"""
logging.debug(f"packet:{packet} interface:{interface}") logging.debug(f"packet:{packet} interface:{interface}")
gpioValue = 0 gpioValue = 0
hw = packet["decoded"]["remotehw"] hw = packet["decoded"]["remotehw"]
@@ -21,9 +22,11 @@ def onGPIOreceive(packet, interface):
# so, we set it here # so, we set it here
gpioValue = 0 gpioValue = 0
#print(f'mask:{interface.mask}') # print(f'mask:{interface.mask}')
value = int(gpioValue) & int(interface.mask) value = int(gpioValue) & int(interface.mask)
print(f'Received RemoteHardware typ={hw["typ"]}, gpio_value={gpioValue} value={value}') print(
f'Received RemoteHardware type={hw["type"]}, gpio_value={gpioValue} value={value}'
)
interface.gotResponse = True interface.gotResponse = True
@@ -44,46 +47,55 @@ class RemoteHardwareClient:
ch = iface.localNode.getChannelByName("gpio") ch = iface.localNode.getChannelByName("gpio")
if not ch: if not ch:
our_exit( our_exit(
"Warning: No channel named 'gpio' was found.\n"\ "Warning: No channel named 'gpio' was found.\n"
"On the sending and receive nodes create a channel named 'gpio'.\n"\ "On the sending and receive nodes create a channel named 'gpio'.\n"
"For example, run '--ch-add gpio' on one device, then '--seturl' on\n"\ "For example, run '--ch-add gpio' on one device, then '--seturl' on\n"
"the other devices using the url from the device where the channel was added.") "the other devices using the url from the device where the channel was added."
)
self.channelIndex = ch.index self.channelIndex = ch.index
pub.subscribe(onGPIOreceive, "meshtastic.receive.remotehw") pub.subscribe(onGPIOreceive, "meshtastic.receive.remotehw")
def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None): def _sendHardware(self, nodeid, r, wantResponse=False, onResponse=None):
if not nodeid: if not nodeid:
our_exit(r"Warning: Must use a destination node ID for this operation (use --dest \!xxxxxxxxx)") our_exit(
return self.iface.sendData(r, nodeid, portnums_pb2.REMOTE_HARDWARE_APP, r"Warning: Must use a destination node ID for this operation (use --dest \!xxxxxxxxx)"
wantAck=True, channelIndex=self.channelIndex, )
wantResponse=wantResponse, onResponse=onResponse) return self.iface.sendData(
r,
nodeid,
portnums_pb2.REMOTE_HARDWARE_APP,
wantAck=True,
channelIndex=self.channelIndex,
wantResponse=wantResponse,
onResponse=onResponse,
)
def writeGPIOs(self, nodeid, mask, vals): def writeGPIOs(self, nodeid, mask, vals):
""" """
Write the specified vals bits to the device GPIOs. Only bits in mask that Write the specified vals bits to the device GPIOs. Only bits in mask that
are 1 will be changed are 1 will be changed
""" """
logging.debug(f'writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}') logging.debug(f"writeGPIOs nodeid:{nodeid} mask:{mask} vals:{vals}")
r = remote_hardware_pb2.HardwareMessage() r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS r.type = remote_hardware_pb2.HardwareMessage.Type.WRITE_GPIOS
r.gpio_mask = mask r.gpio_mask = mask
r.gpio_value = vals r.gpio_value = vals
return self._sendHardware(nodeid, r) return self._sendHardware(nodeid, r)
def readGPIOs(self, nodeid, mask, onResponse = None): def readGPIOs(self, nodeid, mask, onResponse=None):
"""Read the specified bits from GPIO inputs on the device""" """Read the specified bits from GPIO inputs on the device"""
logging.debug(f'readGPIOs nodeid:{nodeid} mask:{mask}') logging.debug(f"readGPIOs nodeid:{nodeid} mask:{mask}")
r = remote_hardware_pb2.HardwareMessage() r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS r.type = remote_hardware_pb2.HardwareMessage.Type.READ_GPIOS
r.gpio_mask = mask r.gpio_mask = mask
return self._sendHardware(nodeid, r, wantResponse=True, onResponse=onResponse) return self._sendHardware(nodeid, r, wantResponse=True, onResponse=onResponse)
def watchGPIOs(self, nodeid, mask): def watchGPIOs(self, nodeid, mask):
"""Watch the specified bits from GPIO inputs on the device for changes""" """Watch the specified bits from GPIO inputs on the device for changes"""
logging.debug(f'watchGPIOs nodeid:{nodeid} mask:{mask}') logging.debug(f"watchGPIOs nodeid:{nodeid} mask:{mask}")
r = remote_hardware_pb2.HardwareMessage() r = remote_hardware_pb2.HardwareMessage()
r.typ = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS r.type = remote_hardware_pb2.HardwareMessage.Type.WATCH_GPIOS
r.gpio_mask = mask r.gpio_mask = mask
self.iface.mask = mask self.iface.mask = mask
return self._sendHardware(nodeid, r) return self._sendHardware(nodeid, r)

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: remote_hardware.proto # source: meshtastic/remote_hardware.proto
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import descriptor_pool as _descriptor_pool
@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15remote_hardware.proto\"\xca\x01\n\x0fHardwareMessage\x12\"\n\x03typ\x18\x01 \x01(\x0e\x32\x15.HardwareMessage.Type\x12\x11\n\tgpio_mask\x18\x02 \x01(\x04\x12\x12\n\ngpio_value\x18\x03 \x01(\x04\"l\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0f\n\x0bWRITE_GPIOS\x10\x01\x12\x0f\n\x0bWATCH_GPIOS\x10\x02\x12\x11\n\rGPIOS_CHANGED\x10\x03\x12\x0e\n\nREAD_GPIOS\x10\x04\x12\x14\n\x10READ_GPIOS_REPLY\x10\x05\x42J\n\x13\x63om.geeksville.meshB\x0eRemoteHardwareH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n meshtastic/remote_hardware.proto\"\xcb\x01\n\x0fHardwareMessage\x12#\n\x04type\x18\x01 \x01(\x0e\x32\x15.HardwareMessage.Type\x12\x11\n\tgpio_mask\x18\x02 \x01(\x04\x12\x12\n\ngpio_value\x18\x03 \x01(\x04\"l\n\x04Type\x12\t\n\x05UNSET\x10\x00\x12\x0f\n\x0bWRITE_GPIOS\x10\x01\x12\x0f\n\x0bWATCH_GPIOS\x10\x02\x12\x11\n\rGPIOS_CHANGED\x10\x03\x12\x0e\n\nREAD_GPIOS\x10\x04\x12\x14\n\x10READ_GPIOS_REPLY\x10\x05\x42\x63\n\x13\x63om.geeksville.meshB\x0eRemoteHardwareZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
@@ -22,7 +22,7 @@ _HARDWAREMESSAGE = DESCRIPTOR.message_types_by_name['HardwareMessage']
_HARDWAREMESSAGE_TYPE = _HARDWAREMESSAGE.enum_types_by_name['Type'] _HARDWAREMESSAGE_TYPE = _HARDWAREMESSAGE.enum_types_by_name['Type']
HardwareMessage = _reflection.GeneratedProtocolMessageType('HardwareMessage', (_message.Message,), { HardwareMessage = _reflection.GeneratedProtocolMessageType('HardwareMessage', (_message.Message,), {
'DESCRIPTOR' : _HARDWAREMESSAGE, 'DESCRIPTOR' : _HARDWAREMESSAGE,
'__module__' : 'remote_hardware_pb2' '__module__' : 'meshtastic.remote_hardware_pb2'
# @@protoc_insertion_point(class_scope:HardwareMessage) # @@protoc_insertion_point(class_scope:HardwareMessage)
}) })
_sym_db.RegisterMessage(HardwareMessage) _sym_db.RegisterMessage(HardwareMessage)
@@ -30,9 +30,9 @@ _sym_db.RegisterMessage(HardwareMessage)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016RemoteHardwareH\003Z!github.com/meshtastic/gomeshproto' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\016RemoteHardwareZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_HARDWAREMESSAGE._serialized_start=26 _HARDWAREMESSAGE._serialized_start=37
_HARDWAREMESSAGE._serialized_end=228 _HARDWAREMESSAGE._serialized_end=240
_HARDWAREMESSAGE_TYPE._serialized_start=120 _HARDWAREMESSAGE_TYPE._serialized_start=132
_HARDWAREMESSAGE_TYPE._serialized_end=228 _HARDWAREMESSAGE_TYPE._serialized_end=240
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

35
meshtastic/rtttl_pb2.py Normal file
View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/rtttl.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 message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16meshtastic/rtttl.proto\"\x1f\n\x0bRTTTLConfig\x12\x10\n\x08ringtone\x18\x01 \x01(\tBf\n\x13\x63om.geeksville.meshB\x11RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_RTTTLCONFIG = DESCRIPTOR.message_types_by_name['RTTTLConfig']
RTTTLConfig = _reflection.GeneratedProtocolMessageType('RTTTLConfig', (_message.Message,), {
'DESCRIPTOR' : _RTTTLCONFIG,
'__module__' : 'meshtastic.rtttl_pb2'
# @@protoc_insertion_point(class_scope:RTTTLConfig)
})
_sym_db.RegisterMessage(RTTTLConfig)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\021RTTTLConfigProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_RTTTLCONFIG._serialized_start=26
_RTTTLCONFIG._serialized_end=57
# @@protoc_insertion_point(module_scope)

View File

@@ -1,16 +1,18 @@
""" Serial interface class """ Serial interface class
""" """
import logging import logging
import time
import platform import platform
import time
import serial import serial
import meshtastic.util import meshtastic.util
from meshtastic.stream_interface import StreamInterface from meshtastic.stream_interface import StreamInterface
if platform.system() != 'Windows': if platform.system() != "Windows":
import termios import termios
class SerialInterface(StreamInterface): class SerialInterface(StreamInterface):
"""Interface class for meshtastic devices over a serial link""" """Interface class for meshtastic devices over a serial link"""
@@ -24,8 +26,10 @@ class SerialInterface(StreamInterface):
""" """
self.noProto = noProto self.noProto = noProto
if devPath is None: self.devPath = devPath
ports = meshtastic.util.findPorts()
if self.devPath is None:
ports = meshtastic.util.findPorts(True)
logging.debug(f"ports:{ports}") logging.debug(f"ports:{ports}")
if len(ports) == 0: if len(ports) == 0:
meshtastic.util.our_exit("Warning: No Meshtastic devices detected.") meshtastic.util.our_exit("Warning: No Meshtastic devices detected.")
@@ -34,25 +38,29 @@ class SerialInterface(StreamInterface):
message += f" Ports detected:{ports}" message += f" Ports detected:{ports}"
meshtastic.util.our_exit(message) meshtastic.util.our_exit(message)
else: else:
devPath = ports[0] self.devPath = ports[0]
logging.debug(f"Connecting to {devPath}") logging.debug(f"Connecting to {self.devPath}")
# first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR # first we need to set the HUPCL so the device will not reboot based on RTS and/or DTR
# see https://github.com/pyserial/pyserial/issues/124 # see https://github.com/pyserial/pyserial/issues/124
if platform.system() != 'Windows': if platform.system() != "Windows":
with open(devPath, encoding='utf8') as f: with open(self.devPath, encoding="utf8") as f:
attrs = termios.tcgetattr(f) attrs = termios.tcgetattr(f)
attrs[2] = attrs[2] & ~termios.HUPCL attrs[2] = attrs[2] & ~termios.HUPCL
termios.tcsetattr(f, termios.TCSAFLUSH, attrs) termios.tcsetattr(f, termios.TCSAFLUSH, attrs)
f.close() f.close()
time.sleep(0.1) time.sleep(0.1)
self.stream = serial.Serial(devPath, 921600, exclusive=True, timeout=0.5, write_timeout=0) self.stream = serial.Serial(
self.devPath, 115200, exclusive=True, timeout=0.5, write_timeout=0
)
self.stream.flush() self.stream.flush()
time.sleep(0.1) time.sleep(0.1)
StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, connectNow=connectNow) StreamInterface.__init__(
self, debugOut=debugOut, noProto=noProto, connectNow=connectNow
)
def close(self): def close(self):
"""Close a connection to the device""" """Close a connection to the device"""

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# source: storeforward.proto # source: meshtastic/storeforward.proto
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import descriptor_pool as _descriptor_pool
@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12storeforward.proto\"\x8a\x06\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12*\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.Statistics\x12)\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.History\x12-\n\theartbeat\x18\x04 \x01(\x0b\x32\x1a.StoreAndForward.Heartbeat\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\xf7\x01\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0c\x43LIENT_ERROR\x10\x65\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x66\x12\x10\n\x0c\x43LIENT_STATS\x10g\x12\x0f\n\x0b\x43LIENT_PING\x10h\x12\x0f\n\x0b\x43LIENT_PONG\x10i\x12\x10\n\x0c\x43LIENT_ABORT\x10jBQ\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosH\x03Z!github.com/meshtastic/gomeshprotob\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1dmeshtastic/storeforward.proto\"\xbe\x06\n\x0fStoreAndForward\x12,\n\x02rr\x18\x01 \x01(\x0e\x32 .StoreAndForward.RequestResponse\x12,\n\x05stats\x18\x02 \x01(\x0b\x32\x1b.StoreAndForward.StatisticsH\x00\x12+\n\x07history\x18\x03 \x01(\x0b\x32\x18.StoreAndForward.HistoryH\x00\x12/\n\theartbeat\x18\x04 \x01(\x0b\x32\x1a.StoreAndForward.HeartbeatH\x00\x12\x0f\n\x05\x65mpty\x18\x05 \x01(\x08H\x00\x1a\xcd\x01\n\nStatistics\x12\x16\n\x0emessages_total\x18\x01 \x01(\r\x12\x16\n\x0emessages_saved\x18\x02 \x01(\r\x12\x14\n\x0cmessages_max\x18\x03 \x01(\r\x12\x0f\n\x07up_time\x18\x04 \x01(\r\x12\x10\n\x08requests\x18\x05 \x01(\r\x12\x18\n\x10requests_history\x18\x06 \x01(\r\x12\x11\n\theartbeat\x18\x07 \x01(\x08\x12\x12\n\nreturn_max\x18\x08 \x01(\r\x12\x15\n\rreturn_window\x18\t \x01(\r\x1aI\n\x07History\x12\x18\n\x10history_messages\x18\x01 \x01(\r\x12\x0e\n\x06window\x18\x02 \x01(\r\x12\x14\n\x0clast_request\x18\x03 \x01(\r\x1a.\n\tHeartbeat\x12\x0e\n\x06period\x18\x01 \x01(\r\x12\x11\n\tsecondary\x18\x02 \x01(\r\"\x89\x02\n\x0fRequestResponse\x12\t\n\x05UNSET\x10\x00\x12\x10\n\x0cROUTER_ERROR\x10\x01\x12\x14\n\x10ROUTER_HEARTBEAT\x10\x02\x12\x0f\n\x0bROUTER_PING\x10\x03\x12\x0f\n\x0bROUTER_PONG\x10\x04\x12\x0f\n\x0bROUTER_BUSY\x10\x05\x12\x12\n\x0eROUTER_HISTORY\x10\x06\x12\x10\n\x0cROUTER_STATS\x10\x07\x12\x10\n\x0c\x43LIENT_ERROR\x10@\x12\x12\n\x0e\x43LIENT_HISTORY\x10\x41\x12\x10\n\x0c\x43LIENT_STATS\x10\x42\x12\x0f\n\x0b\x43LIENT_PING\x10\x43\x12\x0f\n\x0b\x43LIENT_PONG\x10\x44\x12\x10\n\x0c\x43LIENT_ABORT\x10jB\t\n\x07variantBj\n\x13\x63om.geeksville.meshB\x15StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
@@ -27,26 +27,26 @@ StoreAndForward = _reflection.GeneratedProtocolMessageType('StoreAndForward', (_
'Statistics' : _reflection.GeneratedProtocolMessageType('Statistics', (_message.Message,), { 'Statistics' : _reflection.GeneratedProtocolMessageType('Statistics', (_message.Message,), {
'DESCRIPTOR' : _STOREANDFORWARD_STATISTICS, 'DESCRIPTOR' : _STOREANDFORWARD_STATISTICS,
'__module__' : 'storeforward_pb2' '__module__' : 'meshtastic.storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward.Statistics) # @@protoc_insertion_point(class_scope:StoreAndForward.Statistics)
}) })
, ,
'History' : _reflection.GeneratedProtocolMessageType('History', (_message.Message,), { 'History' : _reflection.GeneratedProtocolMessageType('History', (_message.Message,), {
'DESCRIPTOR' : _STOREANDFORWARD_HISTORY, 'DESCRIPTOR' : _STOREANDFORWARD_HISTORY,
'__module__' : 'storeforward_pb2' '__module__' : 'meshtastic.storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward.History) # @@protoc_insertion_point(class_scope:StoreAndForward.History)
}) })
, ,
'Heartbeat' : _reflection.GeneratedProtocolMessageType('Heartbeat', (_message.Message,), { 'Heartbeat' : _reflection.GeneratedProtocolMessageType('Heartbeat', (_message.Message,), {
'DESCRIPTOR' : _STOREANDFORWARD_HEARTBEAT, 'DESCRIPTOR' : _STOREANDFORWARD_HEARTBEAT,
'__module__' : 'storeforward_pb2' '__module__' : 'meshtastic.storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward.Heartbeat) # @@protoc_insertion_point(class_scope:StoreAndForward.Heartbeat)
}) })
, ,
'DESCRIPTOR' : _STOREANDFORWARD, 'DESCRIPTOR' : _STOREANDFORWARD,
'__module__' : 'storeforward_pb2' '__module__' : 'meshtastic.storeforward_pb2'
# @@protoc_insertion_point(class_scope:StoreAndForward) # @@protoc_insertion_point(class_scope:StoreAndForward)
}) })
_sym_db.RegisterMessage(StoreAndForward) _sym_db.RegisterMessage(StoreAndForward)
@@ -57,15 +57,15 @@ _sym_db.RegisterMessage(StoreAndForward.Heartbeat)
if _descriptor._USE_C_DESCRIPTORS == False: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\025StoreAndForwardProtosH\003Z!github.com/meshtastic/gomeshproto' DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\025StoreAndForwardProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_STOREANDFORWARD._serialized_start=23 _STOREANDFORWARD._serialized_start=34
_STOREANDFORWARD._serialized_end=801 _STOREANDFORWARD._serialized_end=864
_STOREANDFORWARD_STATISTICS._serialized_start=223 _STOREANDFORWARD_STATISTICS._serialized_start=257
_STOREANDFORWARD_STATISTICS._serialized_end=428 _STOREANDFORWARD_STATISTICS._serialized_end=462
_STOREANDFORWARD_HISTORY._serialized_start=430 _STOREANDFORWARD_HISTORY._serialized_start=464
_STOREANDFORWARD_HISTORY._serialized_end=503 _STOREANDFORWARD_HISTORY._serialized_end=537
_STOREANDFORWARD_HEARTBEAT._serialized_start=505 _STOREANDFORWARD_HEARTBEAT._serialized_start=539
_STOREANDFORWARD_HEARTBEAT._serialized_end=551 _STOREANDFORWARD_HEARTBEAT._serialized_end=585
_STOREANDFORWARD_REQUESTRESPONSE._serialized_start=554 _STOREANDFORWARD_REQUESTRESPONSE._serialized_start=588
_STOREANDFORWARD_REQUESTRESPONSE._serialized_end=801 _STOREANDFORWARD_REQUESTRESPONSE._serialized_end=853
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@@ -4,15 +4,14 @@ import logging
import threading import threading
import time import time
import traceback import traceback
import serial import serial
from meshtastic.mesh_interface import MeshInterface from meshtastic.mesh_interface import MeshInterface
from meshtastic.util import stripnl from meshtastic.util import is_windows11, stripnl
START1 = 0x94 START1 = 0x94
START2 = 0xc3 START2 = 0xC3
HEADER_LEN = 4 HEADER_LEN = 4
MAX_TO_FROM_RADIO_SIZE = 512 MAX_TO_FROM_RADIO_SIZE = 512
@@ -32,12 +31,15 @@ class StreamInterface(MeshInterface):
Exception: [description] Exception: [description]
""" """
if not hasattr(self, 'stream') and not noProto: if not hasattr(self, "stream") and not noProto:
raise Exception( raise Exception(
"StreamInterface is now abstract (to update existing code create SerialInterface instead)") "StreamInterface is now abstract (to update existing code create SerialInterface instead)"
)
self._rxBuf = bytes() # empty self._rxBuf = bytes() # empty
self._wantExit = False self._wantExit = False
self.is_windows11 = is_windows11()
# FIXME, figure out why daemon=True causes reader thread to exit too early # FIXME, figure out why daemon=True causes reader thread to exit too early
self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True) self._rxThread = threading.Thread(target=self.__reader, args=(), daemon=True)
@@ -58,7 +60,7 @@ class StreamInterface(MeshInterface):
# Send some bogus UART characters to force a sleeping device to wake, and # Send some bogus UART characters to force a sleeping device to wake, and
# if the reading statemachine was parsing a bad packet make sure # if the reading statemachine was parsing a bad packet make sure
# we write enought start bytes to force it to resync (we don't use START1 # we write enough start bytes to force it to resync (we don't use START1
# because we want to ensure it is looking for START1) # because we want to ensure it is looking for START1)
p = bytearray([START2] * 32) p = bytearray([START2] * 32)
self._writeBytes(p) self._writeBytes(p)
@@ -88,8 +90,12 @@ class StreamInterface(MeshInterface):
if self.stream: # ignore writes when stream is closed if self.stream: # ignore writes when stream is closed
self.stream.write(b) self.stream.write(b)
self.stream.flush() self.stream.flush()
# we sleep here to give the TBeam a chance to work # win11 might need a bit more time, too
time.sleep(0.1) if self.is_windows11:
time.sleep(1.0)
else:
# we sleep here to give the TBeam a chance to work
time.sleep(0.1)
def _readBytes(self, length): def _readBytes(self, length):
"""Read an array of bytes from our stream""" """Read an array of bytes from our stream"""
@@ -104,8 +110,8 @@ class StreamInterface(MeshInterface):
b = toRadio.SerializeToString() b = toRadio.SerializeToString()
bufLen = len(b) bufLen = len(b)
# We convert into a string, because the TCP code doesn't work with byte arrays # We convert into a string, because the TCP code doesn't work with byte arrays
header = bytes([START1, START2, (bufLen >> 8) & 0xff, bufLen & 0xff]) header = bytes([START1, START2, (bufLen >> 8) & 0xFF, bufLen & 0xFF])
logging.debug(f'sending header:{header} b:{b}') logging.debug(f"sending header:{header} b:{b}")
self._writeBytes(header + b) self._writeBytes(header + b)
def close(self): def close(self):
@@ -120,18 +126,18 @@ class StreamInterface(MeshInterface):
def __reader(self): def __reader(self):
"""The reader thread that reads bytes from our stream""" """The reader thread that reads bytes from our stream"""
logging.debug('in __reader()') logging.debug("in __reader()")
empty = bytes() empty = bytes()
try: try:
while not self._wantExit: while not self._wantExit:
logging.debug("reading character") # logging.debug("reading character")
b = self._readBytes(1) b = self._readBytes(1)
logging.debug("In reader loop") # logging.debug("In reader loop")
#logging.debug(f"read returned {b}") # logging.debug(f"read returned {b}")
if len(b) > 0: if len(b) > 0:
c = b[0] c = b[0]
#logging.debug(f'c:{c}') # logging.debug(f'c:{c}')
ptr = len(self._rxBuf) ptr = len(self._rxBuf)
# Assume we want to append this byte, fixme use bytearray instead # Assume we want to append this byte, fixme use bytearray instead
@@ -144,38 +150,54 @@ class StreamInterface(MeshInterface):
try: try:
self.debugOut.write(b.decode("utf-8")) self.debugOut.write(b.decode("utf-8"))
except: except:
self.debugOut.write('?') self.debugOut.write("?")
elif ptr == 1: # looking for START2 elif ptr == 1: # looking for START2
if c != START2: if c != START2:
self._rxBuf = empty # failed to find start2 self._rxBuf = empty # failed to find start2
elif ptr >= HEADER_LEN - 1: # we've at least got a header elif ptr >= HEADER_LEN - 1: # we've at least got a header
#logging.debug('at least we received a header') # logging.debug('at least we received a header')
# big endian length follows header # big endian length follows header
packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3] packetlen = (self._rxBuf[2] << 8) + self._rxBuf[3]
if ptr == HEADER_LEN - 1: # we _just_ finished reading the header, validate length if (
ptr == HEADER_LEN - 1
): # we _just_ finished reading the header, validate length
if packetlen > MAX_TO_FROM_RADIO_SIZE: if packetlen > MAX_TO_FROM_RADIO_SIZE:
self._rxBuf = empty # length was out out bounds, restart self._rxBuf = (
empty # length was out out bounds, restart
)
if len(self._rxBuf) != 0 and ptr + 1 >= packetlen + HEADER_LEN: if len(self._rxBuf) != 0 and ptr + 1 >= packetlen + HEADER_LEN:
try: try:
self._handleFromRadio(self._rxBuf[HEADER_LEN:]) self._handleFromRadio(self._rxBuf[HEADER_LEN:])
except Exception as ex: except Exception as ex:
logging.error(f"Error while handling message from radio {ex}") logging.error(
f"Error while handling message from radio {ex}"
)
traceback.print_exc() traceback.print_exc()
self._rxBuf = empty self._rxBuf = empty
else: else:
# logging.debug(f"timeout") # logging.debug(f"timeout")
pass pass
except serial.SerialException as ex: except serial.SerialException as ex:
if not self._wantExit: # We might intentionally get an exception during shutdown if (
logging.warning(f"Meshtastic serial port disconnected, disconnecting... {ex}") not self._wantExit
): # We might intentionally get an exception during shutdown
logging.warning(
f"Meshtastic serial port disconnected, disconnecting... {ex}"
)
except OSError as ex: except OSError as ex:
if not self._wantExit: # We might intentionally get an exception during shutdown if (
logging.error(f"Unexpected OSError, terminating meshtastic reader... {ex}") not self._wantExit
): # We might intentionally get an exception during shutdown
logging.error(
f"Unexpected OSError, terminating meshtastic reader... {ex}"
)
except Exception as ex: except Exception as ex:
logging.error(f"Unexpected exception, terminating meshtastic reader... {ex}") logging.error(
f"Unexpected exception, terminating meshtastic reader... {ex}"
)
finally: finally:
logging.debug("reader is exiting") logging.debug("reader is exiting")
self._disconnected() self._disconnected()

View File

@@ -2,191 +2,227 @@
It is used for auto detection as to which device might be connected. It is used for auto detection as to which device might be connected.
""" """
import platform
import subprocess
import re
# Goal is to detect which device and port to use from the supported devices # Goal is to detect which device and port to use from the supported devices
# without installing any libraries that are not currently in the python meshtastic library # without installing any libraries that are not currently in the python meshtastic library
class SupportedDevice():
class SupportedDevice:
"""Devices supported on Meshtastic""" """Devices supported on Meshtastic"""
def __init__(self, name, version=None, for_firmware=None, device_class="esp32", def __init__(
baseport_on_linux=None, baseport_on_mac=None, baseport_on_windows="COM", self,
usb_vendor_id_in_hex=None, usb_product_id_in_hex=None): name,
""" constructor """ version=None,
for_firmware=None,
device_class="esp32",
baseport_on_linux=None,
baseport_on_mac=None,
baseport_on_windows="COM",
usb_vendor_id_in_hex=None,
usb_product_id_in_hex=None,
):
"""constructor"""
self.name = name self.name = name
self.version = version self.version = version
self.for_firmware = for_firmware self.for_firmware = for_firmware
self.device_class = device_class # could be "nrf52" self.device_class = device_class # could be "nrf52"
# when you run "lsusb -d xxxx:" in linux # when you run "lsusb -d xxxx:" in linux
self.usb_vendor_id_in_hex = usb_vendor_id_in_hex # store in lower case self.usb_vendor_id_in_hex = usb_vendor_id_in_hex # store in lower case
self.usb_product_id_in_hex = usb_product_id_in_hex # store in lower case self.usb_product_id_in_hex = usb_product_id_in_hex # store in lower case
self.baseport_on_linux = baseport_on_linux # ex: ttyUSB or ttyACM self.baseport_on_linux = baseport_on_linux # ex: ttyUSB or ttyACM
self.baseport_on_mac = baseport_on_mac self.baseport_on_mac = baseport_on_mac
self.baseport_on_windows = baseport_on_windows self.baseport_on_windows = baseport_on_windows
# supported devices # supported devices
tbeam_v0_7 = SupportedDevice(name="T-Beam", version="0.7", for_firmware="tbeam0.7", tbeam_v0_7 = SupportedDevice(
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", name="T-Beam",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") version="0.7",
tbeam_v1_1 = SupportedDevice(name="T-Beam", version="1.1", for_firmware="tbeam", for_firmware="tbeam0.7",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", baseport_on_linux="ttyACM",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") baseport_on_mac="cu.usbmodem",
tbeam_M8N = SupportedDevice(name="T-Beam", version="M8N", for_firmware="tbeam", usb_vendor_id_in_hex="1a86",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", usb_product_id_in_hex="55d4",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") )
tbeam_M8N_SX1262 = SupportedDevice(name="T-Beam", version="M8N_SX1262", for_firmware="tbeam", tbeam_v1_1 = SupportedDevice(
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", name="T-Beam",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") version="1.1",
tlora_v1_1 = SupportedDevice(name="T-Lora", version="1.1", for_firmware="tlora-v1", for_firmware="tbeam",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial", baseport_on_linux="ttyACM",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60") baseport_on_mac="cu.usbmodem",
tlora_v1_3 = SupportedDevice(name="T-Lora", version="1.3", for_firmware="tlora-v1-3", usb_vendor_id_in_hex="1a86",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial", usb_product_id_in_hex="55d4",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60") )
tlora_v2_0 = SupportedDevice(name="T-Lora", version="2.0", for_firmware="tlora-v2-1", tbeam_M8N = SupportedDevice(
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", name="T-Beam",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") version="M8N",
tlora_v2_1 = SupportedDevice(name="T-Lora", version="2.1", for_firmware="tlora-v2-1", for_firmware="tbeam",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", baseport_on_linux="ttyACM",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") baseport_on_mac="cu.usbmodem",
tlora_v2_1_1_6 = SupportedDevice(name="T-Lora", version="2.1-1.6", for_firmware="tlora-v2-1-1.6", usb_vendor_id_in_hex="1a86",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", usb_product_id_in_hex="55d4",
usb_vendor_id_in_hex="1a86", usb_product_id_in_hex="55d4") )
heltec_v1 = SupportedDevice(name="Heltec", version="1", for_firmware="heltec-v1", tbeam_M8N_SX1262 = SupportedDevice(
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-", name="T-Beam",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60") version="M8N_SX1262",
heltec_v2_0 = SupportedDevice(name="Heltec", version="2.0", for_firmware="heltec-v2.0", for_firmware="tbeam",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-", baseport_on_linux="ttyACM",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60") baseport_on_mac="cu.usbmodem",
heltec_v2_1 = SupportedDevice(name="Heltec", version="2.1", for_firmware="heltec-v2.1", usb_vendor_id_in_hex="1a86",
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-", usb_product_id_in_hex="55d4",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60") )
meshtastic_diy_v1 = SupportedDevice(name="Meshtastic DIY", version="1", for_firmware="meshtastic-diy-v1", tlora_v1 = SupportedDevice(
baseport_on_linux="ttyUSB", baseport_on_mac="cu.usbserial-", name="T-Lora",
usb_vendor_id_in_hex="10c4", usb_product_id_in_hex="ea60") version="1",
for_firmware="tlora-v1",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
tlora_v1_3 = SupportedDevice(
name="T-Lora",
version="1.3",
for_firmware="tlora-v1-3",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial",
usb_vendor_id_in_hex="10c4",
usb_product_id_in_hex="ea60",
)
tlora_v2 = SupportedDevice(
name="T-Lora",
version="2",
for_firmware="tlora-v2",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
tlora_v2_1_1_6 = SupportedDevice(
name="T-Lora",
version="2.1-1.6",
for_firmware="tlora-v2-1-1.6",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
heltec_v1 = SupportedDevice(
name="Heltec",
version="1",
for_firmware="heltec-v1",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4",
usb_product_id_in_hex="ea60",
)
heltec_v2_0 = SupportedDevice(
name="Heltec",
version="2.0",
for_firmware="heltec-v2.0",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4",
usb_product_id_in_hex="ea60",
)
heltec_v2_1 = SupportedDevice(
name="Heltec",
version="2.1",
for_firmware="heltec-v2.1",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4",
usb_product_id_in_hex="ea60",
)
rak11200 = SupportedDevice(
name="RAK 11200",
version="",
for_firmware="rak11200",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="7523",
)
meshtastic_diy_v1 = SupportedDevice(
name="Meshtastic DIY",
version="1",
for_firmware="meshtastic-diy-v1",
baseport_on_linux="ttyUSB",
baseport_on_mac="cu.usbserial-",
usb_vendor_id_in_hex="10c4",
usb_product_id_in_hex="ea60",
)
# Note: The T-Echo reports product id in boot mode # Note: The T-Echo reports product id in boot mode
techo_1 = SupportedDevice(name="T-Echo", version="1", for_firmware="t-echo-1", device_class="nrf52", techo_1 = SupportedDevice(
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", name="T-Echo",
usb_vendor_id_in_hex="239a", usb_product_id_in_hex="0029") version="1",
rak4631_5005 = SupportedDevice(name="RAK 4631 5005", version="", for_firmware="rak4631_5005", for_firmware="t-echo-1",
device_class="nrf52", device_class="nrf52",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", baseport_on_linux="ttyACM",
usb_vendor_id_in_hex="239a", usb_product_id_in_hex="0029") baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a",
usb_product_id_in_hex="0029",
)
rak4631_5005 = SupportedDevice(
name="RAK 4631 5005",
version="",
for_firmware="rak4631_5005",
device_class="nrf52",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a",
usb_product_id_in_hex="0029",
)
rak4631_5005_epaper = SupportedDevice(
name="RAK 4631 5005 14000 epaper",
version="",
for_firmware="rak4631_5005_epaper",
device_class="nrf52",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a",
usb_product_id_in_hex="0029",
)
# Note: The 19003 reports same product id as 5005 in boot mode # Note: The 19003 reports same product id as 5005 in boot mode
rak4631_19003 = SupportedDevice(name="RAK 4631 19003", version="", for_firmware="rak4631_19003", rak4631_19003 = SupportedDevice(
device_class="nrf52", name="RAK 4631 19003",
baseport_on_linux="ttyACM", baseport_on_mac="cu.usbmodem", version="",
usb_vendor_id_in_hex="239a", usb_product_id_in_hex="8029") for_firmware="rak4631_19003",
device_class="nrf52",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="239a",
usb_product_id_in_hex="8029",
)
nano_g1 = SupportedDevice(
name="Nano G1",
version="",
for_firmware="nano-g1",
baseport_on_linux="ttyACM",
baseport_on_mac="cu.usbmodem",
usb_vendor_id_in_hex="1a86",
usb_product_id_in_hex="55d4",
)
supported_devices = [tbeam_v0_7, tbeam_v1_1, tbeam_M8N, tbeam_M8N_SX1262, supported_devices = [
tlora_v1_1, tlora_v1_3, tlora_v2_0, tlora_v2_1, tlora_v2_1_1_6, tbeam_v0_7,
heltec_v1, heltec_v2_0, heltec_v2_1, tbeam_v1_1,
meshtastic_diy_v1, techo_1, rak4631_5005, rak4631_19003] tbeam_M8N,
tbeam_M8N_SX1262,
tlora_v1,
def get_unique_vendor_ids(): tlora_v1_3,
"""Return a set of unique vendor ids""" tlora_v2,
vids = set() tlora_v2_1_1_6,
for d in supported_devices: heltec_v1,
if d.usb_vendor_id_in_hex: heltec_v2_0,
vids.add(d.usb_vendor_id_in_hex) heltec_v2_1,
return vids meshtastic_diy_v1,
techo_1,
def get_devices_with_vendor_id(vid): rak4631_5005,
"""Return a set of unique devices with the vendor id""" rak4631_5005_epaper,
sd = set() rak4631_19003,
for d in supported_devices: rak11200,
if d.usb_vendor_id_in_hex == vid: nano_g1,
sd.add(d) ]
return sd
def active_ports_on_supported_devices(sds):
"""Return a set of active ports based on the supplied supported devices"""
ports = set()
baseports = set()
system = platform.system()
# figure out what possible base ports there are
for d in sds:
if system == "Linux":
baseports.add(d.baseport_on_linux)
elif system == "Darwin":
baseports.add(d.baseport_on_mac)
elif system == "Windows":
baseports.add(d.baseport_on_windows)
for bp in baseports:
if system == "Linux":
# see if we have any devices (ignoring any stderr output)
command = f'ls -al /dev/{bp}* 2> /dev/null'
#print(f'command:{command}')
_, ls_output = subprocess.getstatusoutput(command)
#print(f'ls_output:{ls_output}')
# if we got output, there are ports
if len(ls_output) > 0:
#print('got output')
# for each line of output
lines = ls_output.split('\n')
#print(f'lines:{lines}')
for line in lines:
parts = line.split(' ')
#print(f'parts:{parts}')
port = parts[-1]
#print(f'port:{port}')
ports.add(port)
elif system == "Darwin":
# see if we have any devices (ignoring any stderr output)
command = f'ls -al /dev/{bp}* 2> /dev/null'
#print(f'command:{command}')
_, ls_output = subprocess.getstatusoutput(command)
#print(f'ls_output:{ls_output}')
# if we got output, there are ports
if len(ls_output) > 0:
#print('got output')
# for each line of output
lines = ls_output.split('\n')
#print(f'lines:{lines}')
for line in lines:
parts = line.split(' ')
#print(f'parts:{parts}')
port = parts[-1]
#print(f'port:{port}')
ports.add(port)
elif system == "Windows":
# for each device in supported devices found
for d in sds:
# find the port(s)
com_ports = detect_windows_port(d)
#print(f'com_ports:{com_ports}')
# add all ports
for com_port in com_ports:
ports.add(com_port)
return ports
def detect_windows_port(sd):
"""detect if Windows port"""
ports = set()
if sd:
system = platform.system()
if system == "Windows":
command = ('powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;'
'Get-PnpDevice -PresentOnly | Where-Object{ ($_.DeviceId -like ')
command += f"'*{sd.usb_vendor_id_in_hex.upper()}*'"
command += ')} | Format-List"'
#print(f'command:{command}')
_, sp_output = subprocess.getstatusoutput(command)
#print(f'sp_output:{sp_output}')
p = re.compile(r'\(COM(.*)\)')
for x in p.findall(sp_output):
#print(f'x:{x}')
ports.add(f'COM{x}')
return ports

View File

@@ -6,11 +6,18 @@ from typing import AnyStr
from meshtastic.stream_interface import StreamInterface from meshtastic.stream_interface import StreamInterface
class TCPInterface(StreamInterface): class TCPInterface(StreamInterface):
"""Interface class for meshtastic devices over a TCP link""" """Interface class for meshtastic devices over a TCP link"""
def __init__(self, hostname: AnyStr, debugOut=None, noProto=False, def __init__(
connectNow=True, portNumber=4403): self,
hostname: AnyStr,
debugOut=None,
noProto=False,
connectNow=True,
portNumber=4403,
):
"""Constructor, opens a connection to a specified IP address/hostname """Constructor, opens a connection to a specified IP address/hostname
Keyword Arguments: Keyword Arguments:
@@ -30,12 +37,13 @@ class TCPInterface(StreamInterface):
else: else:
self.socket = None self.socket = None
StreamInterface.__init__(self, debugOut=debugOut, noProto=noProto, StreamInterface.__init__(
connectNow=connectNow) self, debugOut=debugOut, noProto=noProto, connectNow=connectNow
)
def _socket_shutdown(self): def _socket_shutdown(self):
"""Shutdown the socket. """Shutdown the socket.
Note: Broke out this line so the exception could be unit tested. Note: Broke out this line so the exception could be unit tested.
""" """
self.socket.shutdown(socket.SHUT_RDWR) self.socket.shutdown(socket.SHUT_RDWR)

View File

@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/telemetry.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1ameshtastic/telemetry.proto\"i\n\rDeviceMetrics\x12\x15\n\rbattery_level\x18\x01 \x01(\r\x12\x0f\n\x07voltage\x18\x02 \x01(\x02\x12\x1b\n\x13\x63hannel_utilization\x18\x03 \x01(\x02\x12\x13\n\x0b\x61ir_util_tx\x18\x04 \x01(\x02\"\x9b\x01\n\x12\x45nvironmentMetrics\x12\x13\n\x0btemperature\x18\x01 \x01(\x02\x12\x19\n\x11relative_humidity\x18\x02 \x01(\x02\x12\x1b\n\x13\x62\x61rometric_pressure\x18\x03 \x01(\x02\x12\x16\n\x0egas_resistance\x18\x04 \x01(\x02\x12\x0f\n\x07voltage\x18\x05 \x01(\x02\x12\x0f\n\x07\x63urrent\x18\x06 \x01(\x02\"\xbf\x02\n\x11\x41irQualityMetrics\x12\x15\n\rpm10_standard\x18\x01 \x01(\r\x12\x15\n\rpm25_standard\x18\x02 \x01(\r\x12\x16\n\x0epm100_standard\x18\x03 \x01(\r\x12\x1a\n\x12pm10_environmental\x18\x04 \x01(\r\x12\x1a\n\x12pm25_environmental\x18\x05 \x01(\r\x12\x1b\n\x13pm100_environmental\x18\x06 \x01(\r\x12\x16\n\x0eparticles_03um\x18\x07 \x01(\r\x12\x16\n\x0eparticles_05um\x18\x08 \x01(\r\x12\x16\n\x0eparticles_10um\x18\t \x01(\r\x12\x16\n\x0eparticles_25um\x18\n \x01(\r\x12\x16\n\x0eparticles_50um\x18\x0b \x01(\r\x12\x17\n\x0fparticles_100um\x18\x0c \x01(\r\"\xb5\x01\n\tTelemetry\x12\x0c\n\x04time\x18\x01 \x01(\x07\x12(\n\x0e\x64\x65vice_metrics\x18\x02 \x01(\x0b\x32\x0e.DeviceMetricsH\x00\x12\x32\n\x13\x65nvironment_metrics\x18\x03 \x01(\x0b\x32\x13.EnvironmentMetricsH\x00\x12\x31\n\x13\x61ir_quality_metrics\x18\x04 \x01(\x0b\x32\x12.AirQualityMetricsH\x00\x42\t\n\x07variant*\xc7\x01\n\x13TelemetrySensorType\x12\x10\n\x0cSENSOR_UNSET\x10\x00\x12\n\n\x06\x42ME280\x10\x01\x12\n\n\x06\x42ME680\x10\x02\x12\x0b\n\x07MCP9808\x10\x03\x12\n\n\x06INA260\x10\x04\x12\n\n\x06INA219\x10\x05\x12\n\n\x06\x42MP280\x10\x06\x12\t\n\x05SHTC3\x10\x07\x12\t\n\x05LPS22\x10\x08\x12\x0b\n\x07QMC6310\x10\t\x12\x0b\n\x07QMI8658\x10\n\x12\x0c\n\x08QMC5883L\x10\x0b\x12\t\n\x05SHT31\x10\x0c\x12\x0c\n\x08PMSA003I\x10\rBd\n\x13\x63om.geeksville.meshB\x0fTelemetryProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_TELEMETRYSENSORTYPE = DESCRIPTOR.enum_types_by_name['TelemetrySensorType']
TelemetrySensorType = enum_type_wrapper.EnumTypeWrapper(_TELEMETRYSENSORTYPE)
SENSOR_UNSET = 0
BME280 = 1
BME680 = 2
MCP9808 = 3
INA260 = 4
INA219 = 5
BMP280 = 6
SHTC3 = 7
LPS22 = 8
QMC6310 = 9
QMI8658 = 10
QMC5883L = 11
SHT31 = 12
PMSA003I = 13
_DEVICEMETRICS = DESCRIPTOR.message_types_by_name['DeviceMetrics']
_ENVIRONMENTMETRICS = DESCRIPTOR.message_types_by_name['EnvironmentMetrics']
_AIRQUALITYMETRICS = DESCRIPTOR.message_types_by_name['AirQualityMetrics']
_TELEMETRY = DESCRIPTOR.message_types_by_name['Telemetry']
DeviceMetrics = _reflection.GeneratedProtocolMessageType('DeviceMetrics', (_message.Message,), {
'DESCRIPTOR' : _DEVICEMETRICS,
'__module__' : 'meshtastic.telemetry_pb2'
# @@protoc_insertion_point(class_scope:DeviceMetrics)
})
_sym_db.RegisterMessage(DeviceMetrics)
EnvironmentMetrics = _reflection.GeneratedProtocolMessageType('EnvironmentMetrics', (_message.Message,), {
'DESCRIPTOR' : _ENVIRONMENTMETRICS,
'__module__' : 'meshtastic.telemetry_pb2'
# @@protoc_insertion_point(class_scope:EnvironmentMetrics)
})
_sym_db.RegisterMessage(EnvironmentMetrics)
AirQualityMetrics = _reflection.GeneratedProtocolMessageType('AirQualityMetrics', (_message.Message,), {
'DESCRIPTOR' : _AIRQUALITYMETRICS,
'__module__' : 'meshtastic.telemetry_pb2'
# @@protoc_insertion_point(class_scope:AirQualityMetrics)
})
_sym_db.RegisterMessage(AirQualityMetrics)
Telemetry = _reflection.GeneratedProtocolMessageType('Telemetry', (_message.Message,), {
'DESCRIPTOR' : _TELEMETRY,
'__module__' : 'meshtastic.telemetry_pb2'
# @@protoc_insertion_point(class_scope:Telemetry)
})
_sym_db.RegisterMessage(Telemetry)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\017TelemetryProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_TELEMETRYSENSORTYPE._serialized_start=802
_TELEMETRYSENSORTYPE._serialized_end=1001
_DEVICEMETRICS._serialized_start=30
_DEVICEMETRICS._serialized_end=135
_ENVIRONMENTMETRICS._serialized_start=138
_ENVIRONMENTMETRICS._serialized_end=293
_AIRQUALITYMETRICS._serialized_start=296
_AIRQUALITYMETRICS._serialized_end=615
_TELEMETRY._serialized_start=618
_TELEMETRY._serialized_end=799
# @@protoc_insertion_point(module_scope)

View File

@@ -2,17 +2,18 @@
messages and report back if successful. messages and report back if successful.
""" """
import logging import logging
import time
import sys import sys
import time
import traceback import traceback
from dotmap import DotMap from dotmap import DotMap
from pubsub import pub from pubsub import pub
import meshtastic.util import meshtastic.util
from meshtastic.__init__ import BROADCAST_NUM from meshtastic.__init__ import BROADCAST_NUM
from meshtastic.serial_interface import SerialInterface from meshtastic.serial_interface import SerialInterface
from meshtastic.tcp_interface import TCPInterface from meshtastic.tcp_interface import TCPInterface
"""The interfaces we are using for our tests""" """The interfaces we are using for our tests"""
interfaces = None interfaces = None
@@ -52,7 +53,9 @@ def subscribe():
pub.subscribe(onNode, "meshtastic.node") pub.subscribe(onNode, "meshtastic.node")
def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False): def testSend(
fromInterface, toInterface, isBroadcast=False, asBinary=False, wantAck=False
):
""" """
Sends one test packet between two nodes and then returns success or failure Sends one test packet between two nodes and then returns success or failure
@@ -73,19 +76,19 @@ def testSend(fromInterface, toInterface, isBroadcast=False, asBinary=False, want
else: else:
toNode = toInterface.myInfo.my_node_num toNode = toInterface.myInfo.my_node_num
logging.debug( logging.debug(f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
f"Sending test wantAck={wantAck} packet from {fromNode} to {toNode}")
# pylint: disable=W0603 # pylint: disable=W0603
global sendingInterface global sendingInterface
sendingInterface = fromInterface sendingInterface = fromInterface
if not asBinary: if not asBinary:
fromInterface.sendText(f"Test {testNumber}", toNode, wantAck=wantAck) fromInterface.sendText(f"Test {testNumber}", toNode, wantAck=wantAck)
else: else:
fromInterface.sendData((f"Binary {testNumber}").encode( fromInterface.sendData(
"utf-8"), toNode, wantAck=wantAck) (f"Binary {testNumber}").encode("utf-8"), toNode, wantAck=wantAck
)
for _ in range(60): # max of 60 secs before we timeout for _ in range(60): # max of 60 secs before we timeout
time.sleep(1) time.sleep(1)
if len(receivedPackets) >= 1: if len(receivedPackets) >= 1:
return True return True
return False # Failed to send return False # Failed to send
@@ -102,15 +105,18 @@ def runTests(numTests=50, wantAck=False, maxFailures=0):
isBroadcast = True isBroadcast = True
# asBinary=(i % 2 == 0) # asBinary=(i % 2 == 0)
success = testSend( success = testSend(
interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck) interfaces[0], interfaces[1], isBroadcast, asBinary=False, wantAck=wantAck
)
if not success: if not success:
numFail = numFail + 1 numFail = numFail + 1
logging.error( logging.error(
f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)") f"Test {testNumber} failed, expected packet not received ({numFail} failures so far)"
)
else: else:
numSuccess = numSuccess + 1 numSuccess = numSuccess + 1
logging.info( logging.info(
f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far") f"Test {testNumber} succeeded {numSuccess} successes {numFail} failures so far"
)
time.sleep(1) time.sleep(1)
@@ -140,7 +146,7 @@ def openDebugLog(portName):
"""Open the debug log file""" """Open the debug log file"""
debugname = "log" + portName.replace("/", "_") debugname = "log" + portName.replace("/", "_")
logging.info(f"Writing serial debugging to {debugname}") logging.info(f"Writing serial debugging to {debugname}")
return open(debugname, 'w+', buffering=1, encoding='utf8') return open(debugname, "w+", buffering=1, encoding="utf8")
def testAll(numTests=5): def testAll(numTests=5):
@@ -149,16 +155,24 @@ def testAll(numTests=5):
This is called from the cli with the "--test" option. This is called from the cli with the "--test" option.
""" """
ports = meshtastic.util.findPorts() ports = meshtastic.util.findPorts(True)
if len(ports) < 2: if len(ports) < 2:
meshtastic.util.our_exit("Warning: Must have at least two devices connected to USB.") meshtastic.util.our_exit(
"Warning: Must have at least two devices connected to USB."
)
pub.subscribe(onConnection, "meshtastic.connection") pub.subscribe(onConnection, "meshtastic.connection")
pub.subscribe(onReceive, "meshtastic.receive") pub.subscribe(onReceive, "meshtastic.receive")
# pylint: disable=W0603 # pylint: disable=W0603
global interfaces global interfaces
interfaces = list(map(lambda port: SerialInterface( interfaces = list(
port, debugOut=openDebugLog(port), connectNow=True), ports)) map(
lambda port: SerialInterface(
port, debugOut=openDebugLog(port), connectNow=True
),
ports,
)
)
logging.info("Ports opened, starting test") logging.info("Ports opened, starting test")
result = testThread(numTests) result = testThread(numTests)

View File

@@ -1,11 +1,12 @@
"""Common pytest code (place for fixtures).""" """Common pytest code (place for fixtures)."""
import argparse import argparse
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from meshtastic.__main__ import Globals from meshtastic.__main__ import Globals
from ..mesh_interface import MeshInterface from ..mesh_interface import MeshInterface
@@ -22,36 +23,34 @@ def reset_globals():
def iface_with_nodes(): def iface_with_nodes():
"""Fixture to setup some nodes.""" """Fixture to setup some nodes."""
nodesById = { nodesById = {
'!9388f81c': { "!9388f81c": {
'num': 2475227164, "num": 2475227164,
'user': { "user": {
'id': '!9388f81c', "id": "!9388f81c",
'longName': 'Unknown f81c', "longName": "Unknown f81c",
'shortName': '?1C', "shortName": "?1C",
'macaddr': 'RBeTiPgc', "macaddr": "RBeTiPgc",
'hwModel': 'TBEAM' "hwModel": "TBEAM",
}, },
'position': {}, "position": {},
'lastHeard': 1640204888 "lastHeard": 1640204888,
} }
} }
nodesByNum = { nodesByNum = {
2475227164: { 2475227164: {
'num': 2475227164, "num": 2475227164,
'user': { "user": {
'id': '!9388f81c', "id": "!9388f81c",
'longName': 'Unknown f81c', "longName": "Unknown f81c",
'shortName': '?1C', "shortName": "?1C",
'macaddr': 'RBeTiPgc', "macaddr": "RBeTiPgc",
'hwModel': 'TBEAM' "hwModel": "TBEAM",
}, },
'position': { "position": {"time": 1640206266},
'time': 1640206266 "lastHeard": 1640206266,
}, }
'lastHeard': 1640206266 }
}
}
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
iface.nodes = nodesById iface.nodes = nodesById
iface.nodesByNum = nodesByNum iface.nodesByNum = nodesByNum

View File

@@ -2,14 +2,16 @@
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
from ..ble_interface import BLEInterface from ..ble_interface import BLEInterface
@pytest.mark.unit @pytest.mark.unit
@patch('platform.system', return_value='Linux') @patch("platform.system", return_value="Linux")
def test_BLEInterface(mock_platform): def test_BLEInterface(mock_platform):
"""Test that we can instantiate a BLEInterface""" """Test that we can instantiate a BLEInterface"""
iface = BLEInterface('foo', debugOut=True, noProto=True) iface = BLEInterface("foo", debugOut=True, noProto=True)
iface.close() iface.close()
mock_platform.assert_called() mock_platform.assert_called()

View File

@@ -6,19 +6,24 @@ import subprocess
import pytest import pytest
@pytest.mark.examples @pytest.mark.examples
def test_examples_hello_world_serial_no_arg(): def test_examples_hello_world_serial_no_arg():
"""Test hello_world_serial without any args""" """Test hello_world_serial without any args"""
return_value, _ = subprocess.getstatusoutput('source venv/bin/activate; python3 examples/hello_world_serial.py') return_value, _ = subprocess.getstatusoutput(
"source venv/bin/activate; python3 examples/hello_world_serial.py"
)
assert return_value == 3 assert return_value == 3
@pytest.mark.examples @pytest.mark.examples
def test_examples_hello_world_serial_with_arg(capsys): def test_examples_hello_world_serial_with_arg(capsys):
"""Test hello_world_serial with arg""" """Test hello_world_serial with arg"""
return_value, _ = subprocess.getstatusoutput('source venv/bin/activate; python3 examples/hello_world_serial.py hello') return_value, _ = subprocess.getstatusoutput(
"source venv/bin/activate; python3 examples/hello_world_serial.py hello"
)
assert return_value == 1 assert return_value == 1
_, err = capsys.readouterr() _, err = capsys.readouterr()
assert err == '' assert err == ""
# TODO: Why does this not work? # TODO: Why does this not work?
# assert out == 'Warning: No Meshtastic devices detected.' # assert out == 'Warning: No Meshtastic devices detected.'

View File

@@ -1,14 +1,15 @@
"""Meshtastic unit tests for __init__.py""" """Meshtastic unit tests for __init__.py"""
import re
import logging import logging
import re
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from meshtastic.__init__ import _onTextReceive, _onPositionReceive, _onNodeInfoReceive from meshtastic.__init__ import _onNodeInfoReceive, _onPositionReceive, _onTextReceive
from ..serial_interface import SerialInterface
from ..globals import Globals from ..globals import Globals
from ..serial_interface import SerialInterface
@pytest.mark.unit @pytest.mark.unit
@@ -20,8 +21,8 @@ def test_init_onTextReceive_with_exception(caplog):
packet = {} packet = {}
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
_onTextReceive(iface, packet) _onTextReceive(iface, packet)
assert re.search(r'in _onTextReceive', caplog.text, re.MULTILINE) assert re.search(r"in _onTextReceive", caplog.text, re.MULTILINE)
assert re.search(r'Malformatted', caplog.text, re.MULTILINE) assert re.search(r"Malformatted", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -30,15 +31,10 @@ def test_init_onPositionReceive(caplog):
args = MagicMock() args = MagicMock()
Globals.getInstance().set_args(args) Globals.getInstance().set_args(args)
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
packet = { packet = {"from": "foo", "decoded": {"position": {}}}
'from': 'foo',
'decoded': {
'position': {}
}
}
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
_onPositionReceive(iface, packet) _onPositionReceive(iface, packet)
assert re.search(r'in _onPositionReceive', caplog.text, re.MULTILINE) assert re.search(r"in _onPositionReceive", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -49,13 +45,13 @@ def test_init_onNodeInfoReceive(caplog, iface_with_nodes):
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
packet = { packet = {
'from': 'foo', "from": "foo",
'decoded': { "decoded": {
'user': { "user": {
'id': 'bar', "id": "bar",
}, },
} },
} }
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
_onNodeInfoReceive(iface, packet) _onNodeInfoReceive(iface, packet)
assert re.search(r'in _onNodeInfoReceive', caplog.text, re.MULTILINE) assert re.search(r"in _onNodeInfoReceive", caplog.text, re.MULTILINE)

View File

@@ -8,39 +8,39 @@ import pytest
@pytest.mark.int @pytest.mark.int
def test_int_meshtastic_no_args(): def test_int_meshtastic_no_args():
"""Test meshtastic without any args""" """Test meshtastic without any args"""
return_value, out = subprocess.getstatusoutput('meshtastic') return_value, out = subprocess.getstatusoutput("meshtastic")
assert re.match(r'usage: meshtastic', out) assert re.match(r"usage: meshtastic", out)
assert return_value == 1 assert return_value == 1
@pytest.mark.int @pytest.mark.int
def test_int_mesh_tunnel_no_args(): def test_int_mesh_tunnel_no_args():
"""Test mesh-tunnel without any args""" """Test mesh-tunnel without any args"""
return_value, out = subprocess.getstatusoutput('mesh-tunnel') return_value, out = subprocess.getstatusoutput("mesh-tunnel")
assert re.match(r'usage: mesh-tunnel', out) assert re.match(r"usage: mesh-tunnel", out)
assert return_value == 1 assert return_value == 1
@pytest.mark.int @pytest.mark.int
def test_int_version(): def test_int_version():
"""Test '--version'.""" """Test '--version'."""
return_value, out = subprocess.getstatusoutput('meshtastic --version') return_value, out = subprocess.getstatusoutput("meshtastic --version")
assert re.match(r'[0-9]+\.[0-9]+\.[0-9]', out) assert re.match(r"[0-9]+\.[0-9]+\.[0-9]", out)
assert return_value == 0 assert return_value == 0
@pytest.mark.int @pytest.mark.int
def test_int_help(): def test_int_help():
"""Test '--help'.""" """Test '--help'."""
return_value, out = subprocess.getstatusoutput('meshtastic --help') return_value, out = subprocess.getstatusoutput("meshtastic --help")
assert re.match(r'usage: meshtastic ', out) assert re.match(r"usage: meshtastic ", out)
assert return_value == 0 assert return_value == 0
@pytest.mark.int @pytest.mark.int
def test_int_support(): def test_int_support():
"""Test '--support'.""" """Test '--support'."""
return_value, out = subprocess.getstatusoutput('meshtastic --support') return_value, out = subprocess.getstatusoutput("meshtastic --support")
assert re.search(r'System', out) assert re.search(r"System", out)
assert re.search(r'Python', out) assert re.search(r"Python", out)
assert return_value == 0 assert return_value == 0

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,18 @@
"""Meshtastic unit tests for mesh_interface.py""" """Meshtastic unit tests for mesh_interface.py"""
import re
import logging import logging
import re
from unittest.mock import MagicMock, patch
from unittest.mock import patch, MagicMock
import pytest import pytest
from .. import mesh_pb2
from ..__init__ import BROADCAST_ADDR, LOCAL_ADDR
from ..mesh_interface import MeshInterface from ..mesh_interface import MeshInterface
from ..node import Node from ..node import Node
from .. import mesh_pb2
from ..__init__ import LOCAL_ADDR, BROADCAST_ADDR # TODO
from ..radioconfig_pb2 import RadioConfig # from ..config import Config
from ..util import Timeout from ..util import Timeout
@@ -19,24 +21,24 @@ from ..util import Timeout
def test_MeshInterface(capsys): def test_MeshInterface(capsys):
"""Test that we can instantiate a MeshInterface""" """Test that we can instantiate a MeshInterface"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = Node('foo', 'bar') anode = Node("foo", "bar")
nodes = { nodes = {
'!9388f81c': { "!9388f81c": {
'num': 2475227164, "num": 2475227164,
'user': { "user": {
'id': '!9388f81c', "id": "!9388f81c",
'longName': 'Unknown f81c', "longName": "Unknown f81c",
'shortName': '?1C', "shortName": "?1C",
'macaddr': 'RBeTiPgc', "macaddr": "RBeTiPgc",
'hwModel': 'TBEAM' "hwModel": "TBEAM",
}, },
'position': {}, "position": {},
'lastHeard': 1640204888 "lastHeard": 1640204888,
} }
} }
iface.nodesByNum = {1: anode } iface.nodesByNum = {1: anode}
iface.nodes = nodes iface.nodes = nodes
myInfo = MagicMock() myInfo = MagicMock()
@@ -45,15 +47,15 @@ def test_MeshInterface(capsys):
iface.showInfo() iface.showInfo()
iface.localNode.showInfo() iface.localNode.showInfo()
iface.showNodes() iface.showNodes()
iface.sendText('hello') iface.sendText("hello")
iface.close() iface.close()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Owner: None \(None\)', out, re.MULTILINE) assert re.search(r"Owner: None \(None\)", out, re.MULTILINE)
assert re.search(r'Nodes', out, re.MULTILINE) assert re.search(r"Nodes", out, re.MULTILINE)
assert re.search(r'Preferences', out, re.MULTILINE) assert re.search(r"Preferences", out, re.MULTILINE)
assert re.search(r'Channels', out, re.MULTILINE) assert re.search(r"Channels", out, re.MULTILINE)
assert re.search(r'Primary channel URL', out, re.MULTILINE) assert re.search(r"Primary channel URL", out, re.MULTILINE)
assert err == '' assert err == ""
@pytest.mark.unit @pytest.mark.unit
@@ -64,7 +66,7 @@ def test_getMyUser(iface_with_nodes):
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
myuser = iface.getMyUser() myuser = iface.getMyUser()
assert myuser is not None assert myuser is not None
assert myuser["id"] == '!9388f81c' assert myuser["id"] == "!9388f81c"
@pytest.mark.unit @pytest.mark.unit
@@ -74,7 +76,7 @@ def test_getLongName(iface_with_nodes):
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
mylongname = iface.getLongName() mylongname = iface.getLongName()
assert mylongname == 'Unknown f81c' assert mylongname == "Unknown f81c"
@pytest.mark.unit @pytest.mark.unit
@@ -84,7 +86,7 @@ def test_getShortName(iface_with_nodes):
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
myshortname = iface.getShortName() myshortname = iface.getShortName()
assert myshortname == '?1C' assert myshortname == "?1C"
@pytest.mark.unit @pytest.mark.unit
@@ -95,24 +97,24 @@ def test_handlePacketFromRadio_no_from(capsys):
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
iface._handlePacketFromRadio(meshPacket) iface._handlePacketFromRadio(meshPacket)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Device returned a packet we sent, ignoring', out, re.MULTILINE) assert re.search(r"Device returned a packet we sent, ignoring", out, re.MULTILINE)
assert err == '' assert err == ""
@pytest.mark.unit @pytest.mark.unit
@pytest.mark.usefixtures("reset_globals") @pytest.mark.usefixtures("reset_globals")
def test_handlePacketFromRadio_with_a_portnum(caplog): def test_handlePacketFromRadio_with_a_portnum(caplog):
"""Test _handlePacketFromRadio with a portnum """Test _handlePacketFromRadio with a portnum
Since we have an attribute called 'from', we cannot simply 'set' it. Since we have an attribute called 'from', we cannot simply 'set' it.
Had to implement a hack just to be able to test some code. Had to implement a hack just to be able to test some code.
""" """
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
meshPacket.decoded.payload = b'' meshPacket.decoded.payload = b""
meshPacket.decoded.portnum = 1 meshPacket.decoded.portnum = 1
with caplog.at_level(logging.WARNING): with caplog.at_level(logging.WARNING):
iface._handlePacketFromRadio(meshPacket, hack=True) iface._handlePacketFromRadio(meshPacket, hack=True)
assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE) assert re.search(r"Not populating fromId", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -121,10 +123,10 @@ def test_handlePacketFromRadio_no_portnum(caplog):
"""Test _handlePacketFromRadio without a portnum""" """Test _handlePacketFromRadio without a portnum"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
meshPacket.decoded.payload = b'' meshPacket.decoded.payload = b""
with caplog.at_level(logging.WARNING): with caplog.at_level(logging.WARNING):
iface._handlePacketFromRadio(meshPacket, hack=True) iface._handlePacketFromRadio(meshPacket, hack=True)
assert re.search(r'Not populating fromId', caplog.text, re.MULTILINE) assert re.search(r"Not populating fromId", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -143,10 +145,10 @@ def test_getNode_not_local(caplog):
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = MagicMock(autospec=Node) anode = MagicMock(autospec=Node)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
with patch('meshtastic.node.Node', return_value=anode): with patch("meshtastic.node.Node", return_value=anode):
another_node = iface.getNode('bar2') another_node = iface.getNode("bar2")
assert another_node != iface.localNode assert another_node != iface.localNode
assert re.search(r'About to requestConfig', caplog.text, re.MULTILINE) assert re.search(r"About to requestConfig", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -156,14 +158,14 @@ def test_getNode_not_local_timeout(capsys):
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = MagicMock(autospec=Node) anode = MagicMock(autospec=Node)
anode.waitForConfig.return_value = False anode.waitForConfig.return_value = False
with patch('meshtastic.node.Node', return_value=anode): with patch("meshtastic.node.Node", return_value=anode):
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
iface.getNode('bar2') iface.getNode("bar2")
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.match(r'Error: Timed out waiting for node config', out) assert re.match(r"Error: Timed out waiting for node config", out)
assert err == '' assert err == ""
@pytest.mark.unit @pytest.mark.unit
@@ -174,35 +176,37 @@ def test_sendPosition(caplog):
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
iface.sendPosition() iface.sendPosition()
iface.close() iface.close()
assert re.search(r'p.time:', caplog.text, re.MULTILINE) assert re.search(r"p.time:", caplog.text, re.MULTILINE)
@pytest.mark.unit # TODO
@pytest.mark.usefixtures("reset_globals") # @pytest.mark.unit
def test_close_with_heartbeatTimer(caplog): # @pytest.mark.usefixtures("reset_globals")
"""Test close() with heartbeatTimer""" # def test_close_with_heartbeatTimer(caplog):
iface = MeshInterface(noProto=True) # """Test close() with heartbeatTimer"""
anode = Node('foo', 'bar') # iface = MeshInterface(noProto=True)
radioConfig = RadioConfig() # anode = Node('foo', 'bar')
radioConfig.preferences.phone_timeout_secs = 10 # aconfig = Config()
anode.radioConfig = radioConfig # aonfig.preferences.phone_timeout_secs = 10
iface.localNode = anode # anode.config = aconfig
assert iface.heartbeatTimer is None # iface.localNode = anode
with caplog.at_level(logging.DEBUG): # assert iface.heartbeatTimer is None
iface._startHeartbeat() # with caplog.at_level(logging.DEBUG):
assert iface.heartbeatTimer is not None # iface._startHeartbeat()
iface.close() # assert iface.heartbeatTimer is not None
# iface.close()
@pytest.mark.unit # TODO
@pytest.mark.usefixtures("reset_globals") # @pytest.mark.unit
def test_handleFromRadio_empty_payload(caplog): # @pytest.mark.usefixtures("reset_globals")
"""Test _handleFromRadio""" # def test_handleFromRadio_empty_payload(caplog):
iface = MeshInterface(noProto=True) # """Test _handleFromRadio"""
with caplog.at_level(logging.DEBUG): # iface = MeshInterface(noProto=True)
iface._handleFromRadio(b'') # with caplog.at_level(logging.DEBUG):
iface.close() # iface._handleFromRadio(b'')
assert re.search(r'Unexpected FromRadio payload', caplog.text, re.MULTILINE) # iface.close()
# assert re.search(r'Unexpected FromRadio payload', caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -213,22 +217,21 @@ def test_handleFromRadio_with_my_info(caplog):
# It "translates" to this: # It "translates" to this:
# my_info { # my_info {
# my_node_num: 682584012 # my_node_num: 682584012
# num_bands: 13
# firmware_version: "1.2.49.5354c49" # firmware_version: "1.2.49.5354c49"
# reboot_count: 13 # reboot_count: 13
# bitrate: 17.088470458984375 # bitrate: 17.088470458984375
# message_timeout_msec: 300000 # message_timeout_msec: 300000
# min_app_version: 20200 # min_app_version: 20200
# max_channels: 8 # max_channels: 8
# has_wifi: true
# } # }
from_radio_bytes = b'\x1a,\x08\xcc\xcf\xbd\xc5\x02\x18\r2\x0e1.2.49.5354c49P\r]0\xb5\x88Ah\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01' from_radio_bytes = b"\x1a,\x08\xcc\xcf\xbd\xc5\x02\x18\r2\x0e1.2.49.5354c49P\r]0\xb5\x88Ah\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01"
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
iface._handleFromRadio(from_radio_bytes) iface._handleFromRadio(from_radio_bytes)
iface.close() iface.close()
assert re.search(r'Received myinfo', caplog.text, re.MULTILINE) assert re.search(r"Received myinfo", caplog.text, re.MULTILINE)
assert re.search(r'num_bands: 13', caplog.text, re.MULTILINE) assert re.search(r"max_channels: 8", caplog.text, re.MULTILINE)
assert re.search(r'max_channels: 8', caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -255,16 +258,16 @@ def test_handleFromRadio_with_node_info(caplog, capsys):
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
iface._startConfig() iface._startConfig()
iface._handleFromRadio(from_radio_bytes) iface._handleFromRadio(from_radio_bytes)
assert re.search(r'Received nodeinfo', caplog.text, re.MULTILINE) assert re.search(r"Received nodeinfo", caplog.text, re.MULTILINE)
assert re.search(r'682584012', caplog.text, re.MULTILINE) assert re.search(r"682584012", caplog.text, re.MULTILINE)
assert re.search(r'HELTEC_V2_1', caplog.text, re.MULTILINE) assert re.search(r"HELTEC_V2_1", caplog.text, re.MULTILINE)
# validate some of showNodes() output # validate some of showNodes() output
iface.showNodes() iface.showNodes()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r' 1 ', out, re.MULTILINE) assert re.search(r" 1 ", out, re.MULTILINE)
assert re.search(r'│ Unknown 67cc │ ', out, re.MULTILINE) assert re.search(r"│ Unknown 67cc │ ", out, re.MULTILINE)
assert re.search(r'│ !28af67cc │ N/A │ N/A │ N/A', out, re.MULTILINE) assert re.search(r"│ !28af67cc │ N/A │ N/A │ N/A", out, re.MULTILINE)
assert err == '' assert err == ""
iface.close() iface.close()
@@ -279,16 +282,16 @@ def test_handleFromRadio_with_node_info_tbeam1(caplog, capsys):
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
iface._startConfig() iface._startConfig()
iface._handleFromRadio(from_radio_bytes) iface._handleFromRadio(from_radio_bytes)
assert re.search(r'Received nodeinfo', caplog.text, re.MULTILINE) assert re.search(r"Received nodeinfo", caplog.text, re.MULTILINE)
assert re.search(r'TBeam 1', caplog.text, re.MULTILINE) assert re.search(r"TBeam 1", caplog.text, re.MULTILINE)
assert re.search(r'2127707136', caplog.text, re.MULTILINE) assert re.search(r"2127707136", caplog.text, re.MULTILINE)
# validate some of showNodes() output # validate some of showNodes() output
iface.showNodes() iface.showNodes()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r' 1 ', out, re.MULTILINE) assert re.search(r" 1 ", out, re.MULTILINE)
assert re.search(r'│ TBeam 1 │ ', out, re.MULTILINE) assert re.search(r"│ TBeam 1 │ ", out, re.MULTILINE)
assert re.search(r'│ !7ed23c00 │', out, re.MULTILINE) assert re.search(r"│ !7ed23c00 │", out, re.MULTILINE)
assert err == '' assert err == ""
iface.close() iface.close()
@@ -310,8 +313,8 @@ def test_MeshInterface_sendToRadioImpl(caplog):
"""Test _sendToRadioImp()""" """Test _sendToRadioImp()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
iface._sendToRadioImpl('foo') iface._sendToRadioImpl("foo")
assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE) assert re.search(r"Subclass must provide toradio", caplog.text, re.MULTILINE)
iface.close() iface.close()
@@ -321,8 +324,8 @@ def test_MeshInterface_sendToRadio_no_proto(caplog):
"""Test sendToRadio()""" """Test sendToRadio()"""
iface = MeshInterface() iface = MeshInterface()
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
iface._sendToRadioImpl('foo') iface._sendToRadioImpl("foo")
assert re.search(r'Subclass must provide toradio', caplog.text, re.MULTILINE) assert re.search(r"Subclass must provide toradio", caplog.text, re.MULTILINE)
iface.close() iface.close()
@@ -331,22 +334,22 @@ def test_MeshInterface_sendToRadio_no_proto(caplog):
def test_sendData_too_long(caplog): def test_sendData_too_long(caplog):
"""Test when data payload is too big""" """Test when data payload is too big"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
some_large_text = b'This is a long text that will be too long for send text.' some_large_text = b"This is a long text that will be too long for send text."
some_large_text += b'This is a long text that will be too long for send text.' some_large_text += b"This is a long text that will be too long for send text."
some_large_text += b'This is a long text that will be too long for send text.' some_large_text += b"This is a long text that will be too long for send text."
some_large_text += b'This is a long text that will be too long for send text.' some_large_text += b"This is a long text that will be too long for send text."
some_large_text += b'This is a long text that will be too long for send text.' some_large_text += b"This is a long text that will be too long for send text."
some_large_text += b'This is a long text that will be too long for send text.' some_large_text += b"This is a long text that will be too long for send text."
some_large_text += b'This is a long text that will be too long for send text.' some_large_text += b"This is a long text that will be too long for send text."
some_large_text += b'This is a long text that will be too long for send text.' some_large_text += b"This is a long text that will be too long for send text."
some_large_text += b'This is a long text that will be too long for send text.' some_large_text += b"This is a long text that will be too long for send text."
some_large_text += b'This is a long text that will be too long for send text.' some_large_text += b"This is a long text that will be too long for send text."
some_large_text += b'This is a long text that will be too long for send text.' some_large_text += b"This is a long text that will be too long for send text."
some_large_text += b'This is a long text that will be too long for send text.' some_large_text += b"This is a long text that will be too long for send text."
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
with pytest.raises(Exception) as pytest_wrapped_e: with pytest.raises(Exception) as pytest_wrapped_e:
iface.sendData(some_large_text) iface.sendData(some_large_text)
assert re.search('Data payload too big', caplog.text, re.MULTILINE) assert re.search("Data payload too big", caplog.text, re.MULTILINE)
assert pytest_wrapped_e.type == Exception assert pytest_wrapped_e.type == Exception
iface.close() iface.close()
@@ -357,10 +360,10 @@ def test_sendData_unknown_app(capsys):
"""Test sendData when unknown app""" """Test sendData when unknown app"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
iface.sendData(b'hello', portNum=0) iface.sendData(b"hello", portNum=0)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Warning: A non-zero port number', out, re.MULTILINE) assert re.search(r"Warning: A non-zero port number", out, re.MULTILINE)
assert err == '' assert err == ""
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
@@ -372,9 +375,9 @@ def test_sendPosition_with_a_position(caplog):
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
iface.sendPosition(latitude=40.8, longitude=-111.86, altitude=201) iface.sendPosition(latitude=40.8, longitude=-111.86, altitude=201)
assert re.search(r'p.latitude_i:408', caplog.text, re.MULTILINE) assert re.search(r"p.latitude_i:408", caplog.text, re.MULTILINE)
assert re.search(r'p.longitude_i:-11186', caplog.text, re.MULTILINE) assert re.search(r"p.longitude_i:-11186", caplog.text, re.MULTILINE)
assert re.search(r'p.altitude:201', caplog.text, re.MULTILINE) assert re.search(r"p.altitude:201", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -383,10 +386,10 @@ def test_sendPacket_with_no_destination(capsys):
"""Test _sendPacket()""" """Test _sendPacket()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
iface._sendPacket(b'', destinationId=None) iface._sendPacket(b"", destinationId=None)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Warning: destinationId must not be None', out, re.MULTILINE) assert re.search(r"Warning: destinationId must not be None", out, re.MULTILINE)
assert err == '' assert err == ""
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
@@ -399,7 +402,7 @@ def test_sendPacket_with_destination_as_int(caplog):
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId=123) iface._sendPacket(meshPacket, destinationId=123)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE) assert re.search(r"Not sending packet", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -409,8 +412,8 @@ def test_sendPacket_with_destination_starting_with_a_bang(caplog):
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId='!1234') iface._sendPacket(meshPacket, destinationId="!1234")
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE) assert re.search(r"Not sending packet", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -421,7 +424,7 @@ def test_sendPacket_with_destination_as_BROADCAST_ADDR(caplog):
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId=BROADCAST_ADDR) iface._sendPacket(meshPacket, destinationId=BROADCAST_ADDR)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE) assert re.search(r"Not sending packet", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -433,8 +436,8 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_no_myInfo(capsys):
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR) iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Warning: No myInfo', out, re.MULTILINE) assert re.search(r"Warning: No myInfo", out, re.MULTILINE)
assert err == '' assert err == ""
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
@@ -450,7 +453,7 @@ def test_sendPacket_with_destination_as_LOCAL_ADDR_with_myInfo(caplog):
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR) iface._sendPacket(meshPacket, destinationId=LOCAL_ADDR)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE) assert re.search(r"Not sending packet", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -460,12 +463,12 @@ def test_sendPacket_with_destination_is_blank_with_nodes(capsys, iface_with_node
iface = iface_with_nodes iface = iface_with_nodes
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
iface._sendPacket(meshPacket, destinationId='') iface._sendPacket(meshPacket, destinationId="")
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.match(r'Warning: NodeId not found in DB', out, re.MULTILINE) assert re.match(r"Warning: NodeId not found in DB", out, re.MULTILINE)
assert err == '' assert err == ""
@pytest.mark.unit @pytest.mark.unit
@@ -476,8 +479,8 @@ def test_sendPacket_with_destination_is_blank_without_nodes(caplog, iface_with_n
iface.nodes = None iface.nodes = None
meshPacket = mesh_pb2.MeshPacket() meshPacket = mesh_pb2.MeshPacket()
with caplog.at_level(logging.WARNING): with caplog.at_level(logging.WARNING):
iface._sendPacket(meshPacket, destinationId='') iface._sendPacket(meshPacket, destinationId="")
assert re.search(r'Warning: There were no self.nodes.', caplog.text, re.MULTILINE) assert re.search(r"Warning: There were no self.nodes.", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -486,7 +489,7 @@ def test_getMyNodeInfo():
"""Test getMyNodeInfo()""" """Test getMyNodeInfo()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
anode = iface.getNode(LOCAL_ADDR) anode = iface.getNode(LOCAL_ADDR)
iface.nodesByNum = {1: anode } iface.nodesByNum = {1: anode}
assert iface.nodesByNum.get(1) == anode assert iface.nodesByNum.get(1) == anode
myInfo = MagicMock() myInfo = MagicMock()
iface.myInfo = myInfo iface.myInfo = myInfo
@@ -506,8 +509,10 @@ def test_generatePacketId(capsys):
with pytest.raises(Exception) as pytest_wrapped_e: with pytest.raises(Exception) as pytest_wrapped_e:
iface._generatePacketId() iface._generatePacketId()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Not connected yet, can not generate packet', out, re.MULTILINE) assert re.search(
assert err == '' r"Not connected yet, can not generate packet", out, re.MULTILINE
)
assert err == ""
assert pytest_wrapped_e.type == Exception assert pytest_wrapped_e.type == Exception
@@ -538,10 +543,12 @@ def test_fixupPosition():
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
pos = {"latitudeI": 1010000000, "longitudeI": 1020000000} pos = {"latitudeI": 1010000000, "longitudeI": 1020000000}
newpos = iface._fixupPosition(pos) newpos = iface._fixupPosition(pos)
assert newpos == {"latitude": 101.0, assert newpos == {
"latitudeI": 1010000000, "latitude": 101.0,
"longitude": 102.0, "latitudeI": 1010000000,
"longitudeI": 1020000000} "longitude": 102.0,
"longitudeI": 1020000000,
}
@pytest.mark.unit @pytest.mark.unit
@@ -551,7 +558,7 @@ def test_nodeNumToId(iface_with_nodes):
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
someid = iface._nodeNumToId(2475227164) someid = iface._nodeNumToId(2475227164)
assert someid == '!9388f81c' assert someid == "!9388f81c"
@pytest.mark.unit @pytest.mark.unit
@@ -570,8 +577,8 @@ def test_nodeNumToId_to_all(iface_with_nodes):
"""Test _nodeNumToId()""" """Test _nodeNumToId()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
someid = iface._nodeNumToId(0xffffffff) someid = iface._nodeNumToId(0xFFFFFFFF)
assert someid == '^all' assert someid == "^all"
@pytest.mark.unit @pytest.mark.unit
@@ -581,7 +588,7 @@ def test_getOrCreateByNum_minimal(iface_with_nodes):
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
tmp = iface._getOrCreateByNum(123) tmp = iface._getOrCreateByNum(123)
assert tmp == {'num': 123} assert tmp == {"num": 123}
@pytest.mark.unit @pytest.mark.unit
@@ -591,7 +598,7 @@ def test_getOrCreateByNum_not_found(iface_with_nodes):
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
with pytest.raises(Exception) as pytest_wrapped_e: with pytest.raises(Exception) as pytest_wrapped_e:
iface._getOrCreateByNum(0xffffffff) iface._getOrCreateByNum(0xFFFFFFFF)
assert pytest_wrapped_e.type == Exception assert pytest_wrapped_e.type == Exception
@@ -602,14 +609,15 @@ def test_getOrCreateByNum(iface_with_nodes):
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
tmp = iface._getOrCreateByNum(2475227164) tmp = iface._getOrCreateByNum(2475227164)
assert tmp['num'] == 2475227164 assert tmp["num"] == 2475227164
@pytest.mark.unit # TODO
def test_enter(): # @pytest.mark.unit
"""Test __enter__()""" # def test_enter():
iface = MeshInterface(noProto=True) # """Test __enter__()"""
assert iface == iface.__enter__() # iface = MeshInterface(noProto=True)
# assert iface == iface.__enter__()
@pytest.mark.unit @pytest.mark.unit
@@ -617,9 +625,13 @@ def test_exit_with_exception(caplog):
"""Test __exit__()""" """Test __exit__()"""
iface = MeshInterface(noProto=True) iface = MeshInterface(noProto=True)
with caplog.at_level(logging.ERROR): with caplog.at_level(logging.ERROR):
iface.__exit__('foo', 'bar', 'baz') iface.__exit__("foo", "bar", "baz")
assert re.search(r'An exception of type foo with value bar has occurred', caplog.text, re.MULTILINE) assert re.search(
assert re.search(r'Traceback: baz', caplog.text, re.MULTILINE) r"An exception of type foo with value bar has occurred",
caplog.text,
re.MULTILINE,
)
assert re.search(r"Traceback: baz", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@@ -643,8 +655,10 @@ def test_waitForConfig(capsys):
iface.waitForConfig() iface.waitForConfig()
assert pytest_wrapped_e.type == Exception assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Exception: Timed out waiting for interface config', err, re.MULTILINE) assert re.search(
assert out == '' r"Exception: Timed out waiting for interface config", err, re.MULTILINE
)
assert out == ""
@pytest.mark.unit @pytest.mark.unit
@@ -656,8 +670,8 @@ def test_waitConnected_raises_an_exception(capsys):
iface._waitConnected(0.01) iface._waitConnected(0.01)
assert pytest_wrapped_e.type == Exception assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'warn about something', err, re.MULTILINE) assert re.search(r"warn about something", err, re.MULTILINE)
assert out == '' assert out == ""
@pytest.mark.unit @pytest.mark.unit
@@ -668,5 +682,5 @@ def test_waitConnected_isConnected_timeout(capsys):
iface._waitConnected(0.01) iface._waitConnected(0.01)
assert pytest_wrapped_e.type == Exception assert pytest_wrapped_e.type == Exception
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'warn about something', err, re.MULTILINE) assert re.search(r"warn about something", err, re.MULTILINE)
assert out == '' assert out == ""

View File

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,8 @@
import logging import logging
import re import re
from unittest.mock import MagicMock, patch
from unittest.mock import patch, MagicMock
import pytest import pytest
from ..remote_hardware import RemoteHardwareClient, onGPIOreceive from ..remote_hardware import RemoteHardwareClient, onGPIOreceive
@@ -23,25 +23,25 @@ def test_RemoteHardwareClient():
def test_onGPIOreceive(capsys): def test_onGPIOreceive(capsys):
"""Test onGPIOreceive""" """Test onGPIOreceive"""
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
packet = {'decoded': {'remotehw': {'typ': 'foo', 'gpioValue': '4096' }}} packet = {"decoded": {"remotehw": {"type": "foo", "gpioValue": "4096"}}}
onGPIOreceive(packet, iface) onGPIOreceive(packet, iface)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Received RemoteHardware', out) assert re.search(r"Received RemoteHardware", out)
assert err == '' assert err == ""
@pytest.mark.unit @pytest.mark.unit
def test_RemoteHardwareClient_no_gpio_channel(capsys): def test_RemoteHardwareClient_no_gpio_channel(capsys):
"""Test that we can instantiate a RemoteHardwareClient instance but there is no channel named channel 'gpio'""" """Test that we can instantiate a RemoteHardwareClient instance but there is no channel named channel 'gpio'"""
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
mo.localNode.getChannelByName.return_value = None mo.localNode.getChannelByName.return_value = None
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
RemoteHardwareClient(mo) RemoteHardwareClient(mo)
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Warning: No channel named', out) assert re.search(r"Warning: No channel named", out)
assert err == "" assert err == ""
@@ -51,8 +51,8 @@ def test_readGPIOs(caplog):
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface) rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
rhw.readGPIOs('0x10', 123) rhw.readGPIOs("0x10", 123)
assert re.search(r'readGPIOs', caplog.text, re.MULTILINE) assert re.search(r"readGPIOs", caplog.text, re.MULTILINE)
iface.close() iface.close()
@@ -62,8 +62,8 @@ def test_writeGPIOs(caplog):
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface) rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
rhw.writeGPIOs('0x10', 123, 1) rhw.writeGPIOs("0x10", 123, 1)
assert re.search(r'writeGPIOs', caplog.text, re.MULTILINE) assert re.search(r"writeGPIOs", caplog.text, re.MULTILINE)
iface.close() iface.close()
@@ -73,8 +73,8 @@ def test_watchGPIOs(caplog):
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
rhw = RemoteHardwareClient(iface) rhw = RemoteHardwareClient(iface)
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
rhw.watchGPIOs('0x10', 123) rhw.watchGPIOs("0x10", 123)
assert re.search(r'watchGPIOs', caplog.text, re.MULTILINE) assert re.search(r"watchGPIOs", caplog.text, re.MULTILINE)
iface.close() iface.close()
@@ -82,11 +82,11 @@ def test_watchGPIOs(caplog):
def test_sendHardware_no_nodeid(capsys): def test_sendHardware_no_nodeid(capsys):
"""Test sending no nodeid to _sendHardware()""" """Test sending no nodeid to _sendHardware()"""
iface = MagicMock(autospec=SerialInterface) iface = MagicMock(autospec=SerialInterface)
with patch('meshtastic.serial_interface.SerialInterface', return_value=iface) as mo: with patch("meshtastic.serial_interface.SerialInterface", return_value=iface) as mo:
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
rhw = RemoteHardwareClient(mo) rhw = RemoteHardwareClient(mo)
rhw._sendHardware(None, None) rhw._sendHardware(None, None)
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Warning: Must use a destination node ID', out) assert re.search(r"Warning: Must use a destination node ID", out)
assert err == '' assert err == ""

View File

@@ -1,21 +1,23 @@
"""Meshtastic unit tests for serial_interface.py""" """Meshtastic unit tests for serial_interface.py"""
import re import re
from unittest.mock import mock_open, patch
from unittest.mock import patch, mock_open
import pytest import pytest
from ..serial_interface import SerialInterface from ..serial_interface import SerialInterface
@pytest.mark.unit @pytest.mark.unit
@patch("time.sleep") @patch("time.sleep")
@patch("termios.tcsetattr") @patch("termios.tcsetattr")
@patch("termios.tcgetattr") @patch("termios.tcgetattr")
@patch("builtins.open", new_callable=mock_open, read_data="data") @patch("builtins.open", new_callable=mock_open, read_data="data")
@patch('serial.Serial') @patch("serial.Serial")
@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake']) @patch("meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake"])
def test_SerialInterface_single_port(mocked_findPorts, mocked_serial, mocked_open, mock_get, mock_set, mock_sleep, capsys): def test_SerialInterface_single_port(
mocked_findPorts, mocked_serial, mocked_open, mock_get, mock_set, mock_sleep, capsys
):
"""Test that we can instantiate a SerialInterface with a single port""" """Test that we can instantiate a SerialInterface with a single port"""
iface = SerialInterface(noProto=True) iface = SerialInterface(noProto=True)
iface.showInfo() iface.showInfo()
@@ -28,15 +30,15 @@ def test_SerialInterface_single_port(mocked_findPorts, mocked_serial, mocked_ope
mock_set.assert_called() mock_set.assert_called()
mock_sleep.assert_called() mock_sleep.assert_called()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Nodes in mesh', out, re.MULTILINE) assert re.search(r"Nodes in mesh", out, re.MULTILINE)
assert re.search(r'Preferences', out, re.MULTILINE) assert re.search(r"Preferences", out, re.MULTILINE)
assert re.search(r'Channels', out, re.MULTILINE) assert re.search(r"Channels", out, re.MULTILINE)
assert re.search(r'Primary channel', out, re.MULTILINE) assert re.search(r"Primary channel", out, re.MULTILINE)
assert err == '' assert err == ""
@pytest.mark.unit @pytest.mark.unit
@patch('meshtastic.util.findPorts', return_value=[]) @patch("meshtastic.util.findPorts", return_value=[])
def test_SerialInterface_no_ports(mocked_findPorts, capsys): def test_SerialInterface_no_ports(mocked_findPorts, capsys):
"""Test that we can instantiate a SerialInterface with no ports""" """Test that we can instantiate a SerialInterface with no ports"""
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
@@ -45,12 +47,14 @@ def test_SerialInterface_no_ports(mocked_findPorts, capsys):
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Warning: No Meshtastic devices detected', out, re.MULTILINE) assert re.search(r"Warning: No Meshtastic devices detected", out, re.MULTILINE)
assert err == '' assert err == ""
@pytest.mark.unit @pytest.mark.unit
@patch('meshtastic.util.findPorts', return_value=['/dev/ttyUSBfake1', '/dev/ttyUSBfake2']) @patch(
"meshtastic.util.findPorts", return_value=["/dev/ttyUSBfake1", "/dev/ttyUSBfake2"]
)
def test_SerialInterface_multiple_ports(mocked_findPorts, capsys): def test_SerialInterface_multiple_ports(mocked_findPorts, capsys):
"""Test that we can instantiate a SerialInterface with two ports""" """Test that we can instantiate a SerialInterface with two ports"""
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
@@ -59,5 +63,5 @@ def test_SerialInterface_multiple_ports(mocked_findPorts, capsys):
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Warning: Multiple serial ports were detected', out, re.MULTILINE) assert re.search(r"Warning: Multiple serial ports were detected", out, re.MULTILINE)
assert err == '' assert err == ""

View File

@@ -1,12 +1,12 @@
"""Meshtastic smoke tests with a single device via USB""" """Meshtastic smoke tests with a single device via USB"""
import os
import platform
import re import re
import subprocess import subprocess
import time import time
import platform
import os
# Do not like using hard coded sleeps, but it probably makes # Do not like using hard coded sleeps, but it probably makes
# sense to pause for the radio at apprpriate times # sense to pause for the radio at appropriate times
import pytest import pytest
from ..util import findPorts from ..util import findPorts
@@ -19,7 +19,7 @@ PAUSE_AFTER_REBOOT = 7
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_reboot(): def test_smoke1_reboot():
"""Test reboot""" """Test reboot"""
return_value, _ = subprocess.getstatusoutput('meshtastic --reboot') return_value, _ = subprocess.getstatusoutput("meshtastic --reboot")
assert return_value == 0 assert return_value == 0
# pause for the radio to reset (10 seconds for the pause, and a few more seconds to be back up) # pause for the radio to reset (10 seconds for the pause, and a few more seconds to be back up)
time.sleep(18) time.sleep(18)
@@ -28,94 +28,100 @@ def test_smoke1_reboot():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_info(): def test_smoke1_info():
"""Test --info""" """Test --info"""
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'^Owner', out, re.MULTILINE) assert re.search(r"^Owner", out, re.MULTILINE)
assert re.search(r'^My info', out, re.MULTILINE) assert re.search(r"^My info", out, re.MULTILINE)
assert re.search(r'^Nodes in mesh', out, re.MULTILINE) assert re.search(r"^Nodes in mesh", out, re.MULTILINE)
assert re.search(r'^Preferences', out, re.MULTILINE) assert re.search(r"^Preferences", out, re.MULTILINE)
assert re.search(r'^Channels', out, re.MULTILINE) assert re.search(r"^Channels", out, re.MULTILINE)
assert re.search(r'^ PRIMARY', out, re.MULTILINE) assert re.search(r"^ PRIMARY", out, re.MULTILINE)
assert re.search(r'^Primary channel URL', out, re.MULTILINE) assert re.search(r"^Primary channel URL", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_sendping(): def test_smoke1_sendping():
"""Test --sendping""" """Test --sendping"""
return_value, out = subprocess.getstatusoutput('meshtastic --sendping') return_value, out = subprocess.getstatusoutput("meshtastic --sendping")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'^Sending ping message', out, re.MULTILINE) assert re.search(r"^Sending ping message", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_get_with_invalid_setting(): def test_get_with_invalid_setting():
"""Test '--get a_bad_setting'.""" """Test '--get a_bad_setting'."""
return_value, out = subprocess.getstatusoutput('meshtastic --get a_bad_setting') return_value, out = subprocess.getstatusoutput("meshtastic --get a_bad_setting")
assert re.search(r'Choices in sorted order', out) assert re.search(r"Choices in sorted order", out)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_set_with_invalid_setting(): def test_set_with_invalid_setting():
"""Test '--set a_bad_setting'.""" """Test '--set a_bad_setting'."""
return_value, out = subprocess.getstatusoutput('meshtastic --set a_bad_setting foo') return_value, out = subprocess.getstatusoutput("meshtastic --set a_bad_setting foo")
assert re.search(r'Choices in sorted order', out) assert re.search(r"Choices in sorted order", out)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_ch_set_with_invalid_settingpatch_find_ports(): def test_ch_set_with_invalid_settingpatch_find_ports():
"""Test '--ch-set with a_bad_setting'.""" """Test '--ch-set with a_bad_setting'."""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set invalid_setting foo --ch-index 0') return_value, out = subprocess.getstatusoutput(
assert re.search(r'Choices in sorted order', out) "meshtastic --ch-set invalid_setting foo --ch-index 0"
)
assert re.search(r"Choices in sorted order", out)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_pos_fields(): def test_smoke1_pos_fields():
"""Test --pos-fields (with some values POS_ALTITUDE POS_ALT_MSL POS_BATTERY)""" """Test --pos-fields (with some values POS_ALTITUDE POS_ALT_MSL POS_BATTERY)"""
return_value, out = subprocess.getstatusoutput('meshtastic --pos-fields POS_ALTITUDE POS_ALT_MSL POS_BATTERY') return_value, out = subprocess.getstatusoutput(
assert re.match(r'Connected to radio', out) "meshtastic --pos-fields POS_ALTITUDE POS_ALT_MSL POS_BATTERY"
assert re.search(r'^Setting position fields to 35', out, re.MULTILINE) )
assert re.match(r"Connected to radio", out)
assert re.search(r"^Setting position fields to 35", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --pos-fields') return_value, out = subprocess.getstatusoutput("meshtastic --pos-fields")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'POS_ALTITUDE', out, re.MULTILINE) assert re.search(r"POS_ALTITUDE", out, re.MULTILINE)
assert re.search(r'POS_ALT_MSL', out, re.MULTILINE) assert re.search(r"POS_ALT_MSL", out, re.MULTILINE)
assert re.search(r'POS_BATTERY', out, re.MULTILINE) assert re.search(r"POS_BATTERY", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_test_with_arg_but_no_hardware(): def test_smoke1_test_with_arg_but_no_hardware():
"""Test --test """Test --test
Note: Since only one device is connected, it will not do much. Note: Since only one device is connected, it will not do much.
""" """
return_value, out = subprocess.getstatusoutput('meshtastic --test') return_value, out = subprocess.getstatusoutput("meshtastic --test")
assert re.search(r'^Warning: Must have at least two devices', out, re.MULTILINE) assert re.search(r"^Warning: Must have at least two devices", out, re.MULTILINE)
assert return_value == 1 assert return_value == 1
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_debug(): def test_smoke1_debug():
"""Test --debug""" """Test --debug"""
return_value, out = subprocess.getstatusoutput('meshtastic --info --debug') return_value, out = subprocess.getstatusoutput("meshtastic --info --debug")
assert re.search(r'^Owner', out, re.MULTILINE) assert re.search(r"^Owner", out, re.MULTILINE)
assert re.search(r'^DEBUG file', out, re.MULTILINE) assert re.search(r"^DEBUG file", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_seriallog_to_file(): def test_smoke1_seriallog_to_file():
"""Test --seriallog to a file creates a file""" """Test --seriallog to a file creates a file"""
filename = 'tmpoutput.txt' filename = "tmpoutput.txt"
if os.path.exists(f"{filename}"): if os.path.exists(f"{filename}"):
os.remove(f"{filename}") os.remove(f"{filename}")
return_value, _ = subprocess.getstatusoutput(f'meshtastic --info --seriallog {filename}') return_value, _ = subprocess.getstatusoutput(
f"meshtastic --info --seriallog {filename}"
)
assert os.path.exists(f"{filename}") assert os.path.exists(f"{filename}")
assert return_value == 0 assert return_value == 0
os.remove(f"{filename}") os.remove(f"{filename}")
@@ -124,10 +130,10 @@ def test_smoke1_seriallog_to_file():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_qr(): def test_smoke1_qr():
"""Test --qr""" """Test --qr"""
filename = 'tmpqr' filename = "tmpqr"
if os.path.exists(f"{filename}"): if os.path.exists(f"{filename}"):
os.remove(f"{filename}") os.remove(f"{filename}")
return_value, _ = subprocess.getstatusoutput(f'meshtastic --qr > {filename}') return_value, _ = subprocess.getstatusoutput(f"meshtastic --qr > {filename}")
assert os.path.exists(f"{filename}") assert os.path.exists(f"{filename}")
# not really testing that a valid qr code is created, just that the file size # not really testing that a valid qr code is created, just that the file size
# is reasonably big enough for a qr code # is reasonably big enough for a qr code
@@ -139,20 +145,20 @@ def test_smoke1_qr():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_nodes(): def test_smoke1_nodes():
"""Test --nodes""" """Test --nodes"""
return_value, out = subprocess.getstatusoutput('meshtastic --nodes') return_value, out = subprocess.getstatusoutput("meshtastic --nodes")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
if platform.system() != 'Windows': if platform.system() != "Windows":
assert re.search(r' User ', out, re.MULTILINE) assert re.search(r" User ", out, re.MULTILINE)
assert re.search(r' 1 ', out, re.MULTILINE) assert re.search(r" 1 ", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_send_hello(): def test_smoke1_send_hello():
"""Test --sendtext hello""" """Test --sendtext hello"""
return_value, out = subprocess.getstatusoutput('meshtastic --sendtext hello') return_value, out = subprocess.getstatusoutput("meshtastic --sendtext hello")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'^Sending text message hello to \^all', out, re.MULTILINE) assert re.search(r"^Sending text message hello to \^all", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@@ -160,59 +166,33 @@ def test_smoke1_send_hello():
def test_smoke1_port(): def test_smoke1_port():
"""Test --port""" """Test --port"""
# first, get the ports # first, get the ports
ports = findPorts() ports = findPorts(True)
# hopefully there is just one # hopefully there is just one
assert len(ports) == 1 assert len(ports) == 1
port = ports[0] port = ports[0]
return_value, out = subprocess.getstatusoutput(f'meshtastic --port {port} --info') return_value, out = subprocess.getstatusoutput(f"meshtastic --port {port} --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'^Owner', out, re.MULTILINE) assert re.search(r"^Owner", out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_is_router_true():
"""Test --set is_router true"""
return_value, out = subprocess.getstatusoutput('meshtastic --set is_router true')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set is_router to true', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --get is_router')
assert re.search(r'^is_router: True', out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_set_location_info(): def test_smoke1_set_location_info():
"""Test --setlat, --setlon and --setalt """ """Test --setlat, --setlon and --setalt"""
return_value, out = subprocess.getstatusoutput('meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337') return_value, out = subprocess.getstatusoutput(
assert re.match(r'Connected to radio', out) "meshtastic --setlat 32.7767 --setlon -96.7970 --setalt 1337"
assert re.search(r'^Fixing altitude', out, re.MULTILINE) )
assert re.search(r'^Fixing latitude', out, re.MULTILINE) assert re.match(r"Connected to radio", out)
assert re.search(r'^Fixing longitude', out, re.MULTILINE) assert re.search(r"^Fixing altitude", out, re.MULTILINE)
assert re.search(r"^Fixing latitude", out, re.MULTILINE)
assert re.search(r"^Fixing longitude", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out2 = subprocess.getstatusoutput('meshtastic --info') return_value, out2 = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r'1337', out2, re.MULTILINE) assert re.search(r"1337", out2, re.MULTILINE)
assert re.search(r'32.7767', out2, re.MULTILINE) assert re.search(r"32.7767", out2, re.MULTILINE)
assert re.search(r'-96.797', out2, re.MULTILINE) assert re.search(r"-96.797", out2, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1
def test_smoke1_set_is_router_false():
"""Test --set is_router false"""
return_value, out = subprocess.getstatusoutput('meshtastic --set is_router false')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set is_router to false', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --get is_router')
assert re.search(r'^is_router: False', out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@@ -220,72 +200,80 @@ def test_smoke1_set_is_router_false():
def test_smoke1_set_owner(): def test_smoke1_set_owner():
"""Test --set-owner name""" """Test --set-owner name"""
# make sure the owner is not Joe # make sure the owner is not Joe
return_value, out = subprocess.getstatusoutput('meshtastic --set-owner Bob') return_value, out = subprocess.getstatusoutput("meshtastic --set-owner Bob")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'^Setting device owner to Bob', out, re.MULTILINE) assert re.search(r"^Setting device owner to Bob", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert not re.search(r'Owner: Joe', out, re.MULTILINE) assert not re.search(r"Owner: Joe", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --set-owner Joe') return_value, out = subprocess.getstatusoutput("meshtastic --set-owner Joe")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'^Setting device owner to Joe', out, re.MULTILINE) assert re.search(r"^Setting device owner to Joe", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r'Owner: Joe', out, re.MULTILINE) assert re.search(r"Owner: Joe", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_set_team(): def test_smoke1_ch_set_modem_config():
"""Test --set-team """ """Test --ch-set modem_config"""
# unset the team return_value, out = subprocess.getstatusoutput(
return_value, out = subprocess.getstatusoutput('meshtastic --set-team CLEAR') "meshtastic --ch-set modem_config MedFast"
assert re.match(r'Connected to radio', out) )
assert re.search(r'^Setting team to CLEAR', out, re.MULTILINE) assert re.search(r"Warning: Need to specify", out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert not re.search(r"MedFast", out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-set modem_config MedFast --ch-index 0"
)
assert re.match(r"Connected to radio", out)
assert re.search(r"^Set modem_config to MedFast", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_REBOOT) time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput('meshtastic --set-team CYAN') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r'Setting team to CYAN', out, re.MULTILINE) assert re.search(r"MedFast", out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.search(r'CYAN', out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_ch_values(): def test_smoke1_ch_values():
"""Test --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast, """Test --ch-vlongslow --ch-longslow, --ch-longfast, --ch-mediumslow, --ch-mediumsfast,
--ch-shortslow, and --ch-shortfast arguments --ch-shortslow, and --ch-shortfast arguments
""" """
exp = { exp = {
'--ch-longslow': 'Bw125Cr48Sf4096', "--ch-vlongslow": '{ "psk": "AQ==" }',
'--ch-longfast': 'Bw31_25Cr48Sf512', "--ch-longslow": "LongSlow",
'--ch-mediumslow': 'Bw250Cr46Sf2048', "--ch-longfast": "LongFast",
'--ch-mediumfast': 'Bw250Cr47Sf1024', "--ch-medslow": "MedSlow",
# for some reason, this value does not show any modemConfig "--ch-medfast": "MedFast",
'--ch-shortslow': '{ "psk', "--ch-shortslow": "ShortSlow",
'--ch-shortfast': 'Bw500Cr45Sf128' "--ch-shortfast": "ShortFast",
} }
for key, val in exp.items(): for key, val in exp.items():
print(key, val) print(key, val)
return_value, out = subprocess.getstatusoutput(f'meshtastic {key}') return_value, out = subprocess.getstatusoutput(f"meshtastic {key}")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'Writing modified channels to device', out, re.MULTILINE) assert re.search(r"Writing modified channels to device", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio (might reboot) # pause for the radio (might reboot)
time.sleep(PAUSE_AFTER_REBOOT) time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(val, out, re.MULTILINE) assert re.search(val, out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
@@ -295,132 +283,144 @@ def test_smoke1_ch_values():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_ch_set_name(): def test_smoke1_ch_set_name():
"""Test --ch-set name""" """Test --ch-set name"""
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert not re.search(r'MyChannel', out, re.MULTILINE) assert not re.search(r"MyChannel", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel') return_value, out = subprocess.getstatusoutput("meshtastic --ch-set name MyChannel")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'Warning: Need to specify', out, re.MULTILINE) assert re.search(r"Warning: Need to specify", out, re.MULTILINE)
assert return_value == 1 assert return_value == 1
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name MyChannel --ch-index 0') return_value, out = subprocess.getstatusoutput(
assert re.match(r'Connected to radio', out) "meshtastic --ch-set name MyChannel --ch-index 0"
assert re.search(r'^Set name to MyChannel', out, re.MULTILINE) )
assert re.match(r"Connected to radio", out)
assert re.search(r"^Set name to MyChannel", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r'MyChannel', out, re.MULTILINE) assert re.search(r"MyChannel", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_ch_set_downlink_and_uplink(): def test_smoke1_ch_set_downlink_and_uplink():
"""Test -ch-set downlink_enabled X and --ch-set uplink_enabled X""" """Test -ch-set downlink_enabled X and --ch-set uplink_enabled X"""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false') return_value, out = subprocess.getstatusoutput(
assert re.match(r'Connected to radio', out) "meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false"
assert re.search(r'Warning: Need to specify', out, re.MULTILINE) )
assert re.match(r"Connected to radio", out)
assert re.search(r"Warning: Need to specify", out, re.MULTILINE)
assert return_value == 1 assert return_value == 1
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0') return_value, out = subprocess.getstatusoutput(
assert re.match(r'Connected to radio', out) "meshtastic --ch-set downlink_enabled false --ch-set uplink_enabled false --ch-index 0"
)
assert re.match(r"Connected to radio", out)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert not re.search(r'uplinkEnabled', out, re.MULTILINE) assert not re.search(r"uplinkEnabled", out, re.MULTILINE)
assert not re.search(r'downlinkEnabled', out, re.MULTILINE) assert not re.search(r"downlinkEnabled", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0') return_value, out = subprocess.getstatusoutput(
assert re.match(r'Connected to radio', out) "meshtastic --ch-set downlink_enabled true --ch-set uplink_enabled true --ch-index 0"
assert re.search(r'^Set downlink_enabled to true', out, re.MULTILINE) )
assert re.search(r'^Set uplink_enabled to true', out, re.MULTILINE) assert re.match(r"Connected to radio", out)
assert re.search(r"^Set downlink_enabled to true", out, re.MULTILINE)
assert re.search(r"^Set uplink_enabled to true", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r'uplinkEnabled', out, re.MULTILINE) assert re.search(r"uplinkEnabled", out, re.MULTILINE)
assert re.search(r'downlinkEnabled', out, re.MULTILINE) assert re.search(r"downlinkEnabled", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_ch_add_and_ch_del(): def test_smoke1_ch_add_and_ch_del():
"""Test --ch-add""" """Test --ch-add"""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing') return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing")
assert re.search(r'Writing modified channels to device', out, re.MULTILINE) assert re.search(r"Writing modified channels to device", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'SECONDARY', out, re.MULTILINE) assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE) assert re.search(r"testing", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-index 1 --ch-del') return_value, out = subprocess.getstatusoutput("meshtastic --ch-index 1 --ch-del")
assert re.search(r'Deleting channel 1', out, re.MULTILINE) assert re.search(r"Deleting channel 1", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_REBOOT) time.sleep(PAUSE_AFTER_REBOOT)
# make sure the secondar channel is not there # make sure the secondar channel is not there
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert not re.search(r'SECONDARY', out, re.MULTILINE) assert not re.search(r"SECONDARY", out, re.MULTILINE)
assert not re.search(r'testing', out, re.MULTILINE) assert not re.search(r"testing", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_ch_enable_and_disable(): def test_smoke1_ch_enable_and_disable():
"""Test --ch-enable and --ch-disable""" """Test --ch-enable and --ch-disable"""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing') return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing")
assert re.search(r'Writing modified channels to device', out, re.MULTILINE) assert re.search(r"Writing modified channels to device", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'SECONDARY', out, re.MULTILINE) assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE) assert re.search(r"testing", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
# ensure they need to specify a --ch-index # ensure they need to specify a --ch-index
return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable') return_value, out = subprocess.getstatusoutput("meshtastic --ch-disable")
assert return_value == 1 assert return_value == 1
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 1') return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-disable --ch-index 1"
)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'DISABLED', out, re.MULTILINE) assert re.search(r"DISABLED", out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE) assert re.search(r"testing", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 1') return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-enable --ch-index 1"
)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'SECONDARY', out, re.MULTILINE) assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE) assert re.search(r"testing", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1")
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
@@ -429,32 +429,32 @@ def test_smoke1_ch_enable_and_disable():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_ch_del_a_disabled_non_primary_channel(): def test_smoke1_ch_del_a_disabled_non_primary_channel():
"""Test --ch-del will work on a disabled non-primary channel.""" """Test --ch-del will work on a disabled non-primary channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing') return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing")
assert re.search(r'Writing modified channels to device', out, re.MULTILINE) assert re.search(r"Writing modified channels to device", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'SECONDARY', out, re.MULTILINE) assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r'testing', out, re.MULTILINE) assert re.search(r"testing", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
# ensure they need to specify a --ch-index # ensure they need to specify a --ch-index
return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable') return_value, out = subprocess.getstatusoutput("meshtastic --ch-disable")
assert return_value == 1 assert return_value == 1
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1")
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert not re.search(r'DISABLED', out, re.MULTILINE) assert not re.search(r"DISABLED", out, re.MULTILINE)
assert not re.search(r'SECONDARY', out, re.MULTILINE) assert not re.search(r"SECONDARY", out, re.MULTILINE)
assert not re.search(r'testing', out, re.MULTILINE) assert not re.search(r"testing", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
@@ -463,8 +463,8 @@ def test_smoke1_ch_del_a_disabled_non_primary_channel():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_attempt_to_delete_primary_channel(): def test_smoke1_attempt_to_delete_primary_channel():
"""Test that we cannot delete the PRIMARY channel.""" """Test that we cannot delete the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 0') return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 0")
assert re.search(r'Warning: Cannot delete primary channel', out, re.MULTILINE) assert re.search(r"Warning: Cannot delete primary channel", out, re.MULTILINE)
assert return_value == 1 assert return_value == 1
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
@@ -473,8 +473,10 @@ def test_smoke1_attempt_to_delete_primary_channel():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_attempt_to_disable_primary_channel(): def test_smoke1_attempt_to_disable_primary_channel():
"""Test that we cannot disable the PRIMARY channel.""" """Test that we cannot disable the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-disable --ch-index 0') return_value, out = subprocess.getstatusoutput(
assert re.search(r'Warning: Cannot enable', out, re.MULTILINE) "meshtastic --ch-disable --ch-index 0"
)
assert re.search(r"Warning: Cannot enable", out, re.MULTILINE)
assert return_value == 1 assert return_value == 1
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
@@ -483,8 +485,10 @@ def test_smoke1_attempt_to_disable_primary_channel():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_attempt_to_enable_primary_channel(): def test_smoke1_attempt_to_enable_primary_channel():
"""Test that we cannot enable the PRIMARY channel.""" """Test that we cannot enable the PRIMARY channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-enable --ch-index 0') return_value, out = subprocess.getstatusoutput(
assert re.search(r'Warning: Cannot enable', out, re.MULTILINE) "meshtastic --ch-enable --ch-index 0"
)
assert re.search(r"Warning: Cannot enable", out, re.MULTILINE)
assert return_value == 1 assert return_value == 1
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
@@ -493,42 +497,42 @@ def test_smoke1_attempt_to_enable_primary_channel():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_ensure_ch_del_second_of_three_channels(): def test_smoke1_ensure_ch_del_second_of_three_channels():
"""Test that when we delete the 2nd of 3 channels, that it deletes the correct channel.""" """Test that when we delete the 2nd of 3 channels, that it deletes the correct channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1') return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing1")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'SECONDARY', out, re.MULTILINE) assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r'testing1', out, re.MULTILINE) assert re.search(r"testing1", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2') return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing2")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'testing2', out, re.MULTILINE) assert re.search(r"testing2", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'testing2', out, re.MULTILINE) assert re.search(r"testing2", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
@@ -537,91 +541,69 @@ def test_smoke1_ensure_ch_del_second_of_three_channels():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_ensure_ch_del_third_of_three_channels(): def test_smoke1_ensure_ch_del_third_of_three_channels():
"""Test that when we delete the 3rd of 3 channels, that it deletes the correct channel.""" """Test that when we delete the 3rd of 3 channels, that it deletes the correct channel."""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing1') return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing1")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'SECONDARY', out, re.MULTILINE) assert re.search(r"SECONDARY", out, re.MULTILINE)
assert re.search(r'testing1', out, re.MULTILINE) assert re.search(r"testing1", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-add testing2') return_value, out = subprocess.getstatusoutput("meshtastic --ch-add testing2")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'testing2', out, re.MULTILINE) assert re.search(r"testing2", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 2') return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 2")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search(r'testing1', out, re.MULTILINE) assert re.search(r"testing1", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-del --ch-index 1') return_value, out = subprocess.getstatusoutput("meshtastic --ch-del --ch-index 1")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
@pytest.mark.smoke1
def test_smoke1_ch_set_modem_config():
"""Test --ch-set modem_config"""
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config Bw31_25Cr48Sf512')
assert re.search(r'Warning: Need to specify', out, re.MULTILINE)
assert return_value == 1
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert not re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set modem_config Bw31_25Cr48Sf512 --ch-index 0')
assert re.match(r'Connected to radio', out)
assert re.search(r'^Set modem_config to Bw31_25Cr48Sf512', out, re.MULTILINE)
assert return_value == 0
# pause for the radio
time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info')
assert re.search(r'Bw31_25Cr48Sf512', out, re.MULTILINE)
assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_seturl_default(): def test_smoke1_seturl_default():
"""Test --seturl with default value""" """Test --seturl with default value"""
# set some channel value so we no longer have a default channel # set some channel value so we no longer have a default channel
return_value, out = subprocess.getstatusoutput('meshtastic --ch-set name foo --ch-index 0') return_value, out = subprocess.getstatusoutput(
"meshtastic --ch-set name foo --ch-index 0"
)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
# ensure we no longer have a default primary channel # ensure we no longer have a default primary channel
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert not re.search('CgUYAyIBAQ', out, re.MULTILINE) assert not re.search("CgUYAyIBAQ", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
url = "https://www.meshtastic.org/d/#CgUYAyIBAQ" url = "https://www.meshtastic.org/d/#CgUYAyIBAQ"
return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}") return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search('CgUYAyIBAQ', out, re.MULTILINE) assert re.search("CgUYAyIBAQ", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@@ -631,8 +613,8 @@ def test_smoke1_seturl_invalid_url():
# Note: This url is no longer a valid url. # Note: This url is no longer a valid url.
url = "https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ=" url = "https://www.meshtastic.org/c/#GAMiENTxuzogKQdZ8Lz_q89Oab8qB0RlZmF1bHQ="
return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}") return_value, out = subprocess.getstatusoutput(f"meshtastic --seturl {url}")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search('Warning: There were no settings', out, re.MULTILINE) assert re.search("Warning: There were no settings", out, re.MULTILINE)
assert return_value == 1 assert return_value == 1
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
@@ -641,19 +623,18 @@ def test_smoke1_seturl_invalid_url():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_configure(): def test_smoke1_configure():
"""Test --configure""" """Test --configure"""
_ , out = subprocess.getstatusoutput(f"meshtastic --configure example_config.yaml") _, out = subprocess.getstatusoutput(f"meshtastic --configure example_config.yaml")
assert re.match(r'Connected to radio', out) assert re.match(r"Connected to radio", out)
assert re.search('^Setting device owner to Bob TBeam', out, re.MULTILINE) assert re.search("^Setting device owner to Bob TBeam", out, re.MULTILINE)
assert re.search('^Fixing altitude at 304 meters', out, re.MULTILINE) assert re.search("^Fixing altitude at 304 meters", out, re.MULTILINE)
assert re.search('^Fixing latitude at 35.8', out, re.MULTILINE) assert re.search("^Fixing latitude at 35.8", out, re.MULTILINE)
assert re.search('^Fixing longitude at -93.8', out, re.MULTILINE) assert re.search("^Fixing longitude at -93.8", out, re.MULTILINE)
assert re.search('^Setting device position', out, re.MULTILINE) assert re.search("^Setting device position", out, re.MULTILINE)
assert re.search('^Set region to 1', out, re.MULTILINE) assert re.search("^Set region to 1", out, re.MULTILINE)
assert re.search('^Set is_always_powered to true', out, re.MULTILINE) assert re.search("^Set is_always_powered to true", out, re.MULTILINE)
assert re.search('^Set send_owner_interval to 2', out, re.MULTILINE) assert re.search("^Set screen_on_secs to 31536000", out, re.MULTILINE)
assert re.search('^Set screen_on_secs to 31536000', out, re.MULTILINE) assert re.search("^Set wait_bluetooth_secs to 31536000", out, re.MULTILINE)
assert re.search('^Set wait_bluetooth_secs to 31536000', out, re.MULTILINE) assert re.search("^Writing modified preferences to device", out, re.MULTILINE)
assert re.search('^Writing modified preferences to device', out, re.MULTILINE)
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_REBOOT) time.sleep(PAUSE_AFTER_REBOOT)
@@ -661,41 +642,47 @@ def test_smoke1_configure():
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_set_ham(): def test_smoke1_set_ham():
"""Test --set-ham """Test --set-ham
Note: Do a factory reset after this setting so it is very short-lived. Note: Do a factory reset after this setting so it is very short-lived.
""" """
return_value, out = subprocess.getstatusoutput('meshtastic --set-ham KI1234') return_value, out = subprocess.getstatusoutput("meshtastic --set-ham KI1234")
assert re.search(r'Setting Ham ID', out, re.MULTILINE) assert re.search(r"Setting Ham ID", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_REBOOT) time.sleep(PAUSE_AFTER_REBOOT)
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r'Owner: KI1234', out, re.MULTILINE) assert re.search(r"Owner: KI1234", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_set_wifi_settings(): def test_smoke1_set_wifi_settings():
"""Test --set wifi_ssid and --set wifi_password""" """Test --set wifi_ssid and --set wifi_password"""
return_value, out = subprocess.getstatusoutput('meshtastic --set wifi_ssid "some_ssid" --set wifi_password "temp1234"') return_value, out = subprocess.getstatusoutput(
assert re.match(r'Connected to radio', out) 'meshtastic --set wifi_ssid "some_ssid" --set wifi_password "temp1234"'
assert re.search(r'^Set wifi_ssid to some_ssid', out, re.MULTILINE) )
assert re.search(r'^Set wifi_password to temp1234', out, re.MULTILINE) assert re.match(r"Connected to radio", out)
assert re.search(r"^Set wifi_ssid to some_ssid", out, re.MULTILINE)
assert re.search(r"^Set wifi_password to temp1234", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# pause for the radio # pause for the radio
time.sleep(PAUSE_AFTER_COMMAND) time.sleep(PAUSE_AFTER_COMMAND)
return_value, out = subprocess.getstatusoutput('meshtastic --get wifi_ssid --get wifi_password') return_value, out = subprocess.getstatusoutput(
assert re.search(r'^wifi_ssid: some_ssid', out, re.MULTILINE) "meshtastic --get wifi_ssid --get wifi_password"
assert re.search(r'^wifi_password: sekrit', out, re.MULTILINE) )
assert re.search(r"^wifi_ssid: some_ssid", out, re.MULTILINE)
assert re.search(r"^wifi_password: sekrit", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
@pytest.mark.smoke1 @pytest.mark.smoke1
def test_smoke1_factory_reset(): def test_smoke1_factory_reset():
"""Test factory reset""" """Test factory reset"""
return_value, out = subprocess.getstatusoutput('meshtastic --set factory_reset true') return_value, out = subprocess.getstatusoutput(
assert re.match(r'Connected to radio', out) "meshtastic --set factory_reset true"
assert re.search(r'^Set factory_reset to true', out, re.MULTILINE) )
assert re.search(r'^Writing modified preferences to device', out, re.MULTILINE) assert re.match(r"Connected to radio", out)
assert re.search(r"^Set factory_reset to true", out, re.MULTILINE)
assert re.search(r"^Writing modified preferences to device", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0
# NOTE: The radio may not be responsive after this, may need to do a manual reboot # NOTE: The radio may not be responsive after this, may need to do a manual reboot
# by pressing the button # by pressing the button

View File

@@ -8,16 +8,16 @@ import pytest
@pytest.mark.smoke2 @pytest.mark.smoke2
def test_smoke2_info(): def test_smoke2_info():
"""Test --info with 2 devices connected serially""" """Test --info with 2 devices connected serially"""
return_value, out = subprocess.getstatusoutput('meshtastic --info') return_value, out = subprocess.getstatusoutput("meshtastic --info")
assert re.search(r'Warning: Multiple', out, re.MULTILINE) assert re.search(r"Warning: Multiple", out, re.MULTILINE)
assert return_value == 1 assert return_value == 1
@pytest.mark.smoke2 @pytest.mark.smoke2
def test_smoke2_test(): def test_smoke2_test():
"""Test --test""" """Test --test"""
return_value, out = subprocess.getstatusoutput('meshtastic --test') return_value, out = subprocess.getstatusoutput("meshtastic --test")
assert re.search(r'Writing serial debugging', out, re.MULTILINE) assert re.search(r"Writing serial debugging", out, re.MULTILINE)
assert re.search(r'Ports opened', out, re.MULTILINE) assert re.search(r"Ports opened", out, re.MULTILINE)
assert re.search(r'Running 5 tests', out, re.MULTILINE) assert re.search(r"Running 5 tests", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0

View File

@@ -12,12 +12,14 @@ import pytest
@pytest.mark.smokewifi @pytest.mark.smokewifi
def test_smokewifi_info(): def test_smokewifi_info():
"""Test --info""" """Test --info"""
return_value, out = subprocess.getstatusoutput('meshtastic --info --host meshtastic.local') return_value, out = subprocess.getstatusoutput(
assert re.search(r'^Owner', out, re.MULTILINE) "meshtastic --info --host meshtastic.local"
assert re.search(r'^My info', out, re.MULTILINE) )
assert re.search(r'^Nodes in mesh', out, re.MULTILINE) assert re.search(r"^Owner", out, re.MULTILINE)
assert re.search(r'^Preferences', out, re.MULTILINE) assert re.search(r"^My info", out, re.MULTILINE)
assert re.search(r'^Channels', out, re.MULTILINE) assert re.search(r"^Nodes in mesh", out, re.MULTILINE)
assert re.search(r'^ PRIMARY', out, re.MULTILINE) assert re.search(r"^Preferences", out, re.MULTILINE)
assert re.search(r'^Primary channel URL', out, re.MULTILINE) assert re.search(r"^Channels", out, re.MULTILINE)
assert re.search(r"^ PRIMARY", out, re.MULTILINE)
assert re.search(r"^Primary channel URL", out, re.MULTILINE)
assert return_value == 0 assert return_value == 0

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,14 @@
"""Meshtastic unit tests for stream_interface.py""" """Meshtastic unit tests for stream_interface.py"""
import logging import logging
import re
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from ..stream_interface import StreamInterface from ..stream_interface import StreamInterface
# import re
@pytest.mark.unit @pytest.mark.unit
def test_StreamInterface(): def test_StreamInterface():
@@ -22,10 +23,10 @@ def test_StreamInterface():
@pytest.mark.usefixtures("reset_globals") @pytest.mark.usefixtures("reset_globals")
def test_StreamInterface_with_noProto(caplog): def test_StreamInterface_with_noProto(caplog):
"""Test that we can instantiate a StreamInterface based on nonProto """Test that we can instantiate a StreamInterface based on nonProto
and we can read/write bytes from a mocked stream and we can read/write bytes from a mocked stream
""" """
stream = MagicMock() stream = MagicMock()
test_data = b'hello' test_data = b"hello"
stream.read.return_value = test_data stream.read.return_value = test_data
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
iface = StreamInterface(noProto=True, connectNow=False) iface = StreamInterface(noProto=True, connectNow=False)
@@ -35,48 +36,49 @@ def test_StreamInterface_with_noProto(caplog):
assert data == test_data assert data == test_data
## Note: This takes a bit, so moving from unit to slow # TODO
## Tip: If you want to see the print output, run with '-s' flag: ### Note: This takes a bit, so moving from unit to slow
## pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl ### Tip: If you want to see the print output, run with '-s' flag:
@pytest.mark.unitslow ### pytest -s meshtastic/tests/test_stream_interface.py::test_sendToRadioImpl
@pytest.mark.usefixtures("reset_globals") # @pytest.mark.unitslow
def test_sendToRadioImpl(caplog): # @pytest.mark.usefixtures("reset_globals")
"""Test _sendToRadioImpl()""" # def test_sendToRadioImpl(caplog):
# """Test _sendToRadioImpl()"""
# def add_header(b): #
# """Add header stuffs for radio""" ## def add_header(b):
# bufLen = len(b) ## """Add header stuffs for radio"""
# header = bytes([START1, START2, (bufLen >> 8) & 0xff, bufLen & 0xff]) ## bufLen = len(b)
# return header + b ## header = bytes([START1, START2, (bufLen >> 8) & 0xff, bufLen & 0xff])
## return header + b
# captured raw bytes of a Heltec2.1 radio with 2 channels (primary and a secondary channel named "gpio") #
raw_1_my_info = b'\x1a,\x08\xdc\x8c\xd5\xc5\x02\x18\r2\x0e1.2.49.5354c49P\x15]\xe1%\x17Eh\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01' # # captured raw bytes of a Heltec2.1 radio with 2 channels (primary and a secondary channel named "gpio")
raw_2_node_info = b'"9\x08\xdc\x8c\xd5\xc5\x02\x12(\n\t!28b5465c\x12\x0cUnknown 465c\x1a\x03?5C"\x06$o(\xb5F\\0\n\x1a\x02 1%M<\xc6a' # raw_1_my_info = b'\x1a,\x08\xdc\x8c\xd5\xc5\x02\x18\r2\x0e1.2.49.5354c49P\x15]\xe1%\x17Eh\xe0\xa7\x12p\xe8\x9d\x01x\x08\x90\x01\x01'
# pylint: disable=C0301 # raw_2_node_info = b'"9\x08\xdc\x8c\xd5\xc5\x02\x12(\n\t!28b5465c\x12\x0cUnknown 465c\x1a\x03?5C"\x06$o(\xb5F\\0\n\x1a\x02 1%M<\xc6a'
raw_3_node_info = b'"C\x08\xa4\x8c\xd5\xc5\x02\x12(\n\t!28b54624\x12\x0cUnknown 4624\x1a\x03?24"\x06$o(\xb5F$0\n\x1a\x07 5MH<\xc6a%G<\xc6a=\x00\x00\xc0@' # # pylint: disable=C0301
raw_4_complete = b'@\xcf\xe5\xd1\x8c\x0e' # raw_3_node_info = b'"C\x08\xa4\x8c\xd5\xc5\x02\x12(\n\t!28b54624\x12\x0cUnknown 4624\x1a\x03?24"\x06$o(\xb5F$0\n\x1a\x07 5MH<\xc6a%G<\xc6a=\x00\x00\xc0@'
# pylint: disable=C0301 # raw_4_complete = b'@\xcf\xe5\xd1\x8c\x0e'
raw_5_prefs = b'Z6\r\\F\xb5(\x15\\F\xb5("\x1c\x08\x06\x12\x13*\x11\n\x0f0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#\xb8\t\x015]$\xddk5\xd5\x7f!b=M<\xc6aP\x03`F' # # pylint: disable=C0301
# pylint: disable=C0301 # raw_5_prefs = b'Z6\r\\F\xb5(\x15\\F\xb5("\x1c\x08\x06\x12\x13*\x11\n\x0f0\x84\x07P\xac\x02\x88\x01\x01\xb0\t#\xb8\t\x015]$\xddk5\xd5\x7f!b=M<\xc6aP\x03`F'
raw_6_channel0 = b'Z.\r\\F\xb5(\x15\\F\xb5("\x14\x08\x06\x12\x0b:\t\x12\x05\x18\x01"\x01\x01\x18\x015^$\xddk5\xd6\x7f!b=M<\xc6aP\x03`F' # # pylint: disable=C0301
# pylint: disable=C0301 # raw_6_channel0 = b'Z.\r\\F\xb5(\x15\\F\xb5("\x14\x08\x06\x12\x0b:\t\x12\x05\x18\x01"\x01\x01\x18\x015^$\xddk5\xd6\x7f!b=M<\xc6aP\x03`F'
raw_7_channel1 = b'ZS\r\\F\xb5(\x15\\F\xb5("9\x08\x06\x120:.\x08\x01\x12(" \xb4&\xb3\xc7\x06\xd8\xe39%\xba\xa5\xee\x8eH\x06\xf6\xf4H\xe8\xd5\xc1[ao\xb5Y\\\xb4"\xafmi*\x04gpio\x18\x025_$\xddk5\xd7\x7f!b=M<\xc6aP\x03`F' # # pylint: disable=C0301
raw_8_channel2 = b'Z)\r\\F\xb5(\x15\\F\xb5("\x0f\x08\x06\x12\x06:\x04\x08\x02\x12\x005`$\xddk5\xd8\x7f!b=M<\xc6aP\x03`F' # raw_7_channel1 = b'ZS\r\\F\xb5(\x15\\F\xb5("9\x08\x06\x120:.\x08\x01\x12(" \xb4&\xb3\xc7\x06\xd8\xe39%\xba\xa5\xee\x8eH\x06\xf6\xf4H\xe8\xd5\xc1[ao\xb5Y\\\xb4"\xafmi*\x04gpio\x18\x025_$\xddk5\xd7\x7f!b=M<\xc6aP\x03`F'
raw_blank = b'' # raw_8_channel2 = b'Z)\r\\F\xb5(\x15\\F\xb5("\x0f\x08\x06\x12\x06:\x04\x08\x02\x12\x005`$\xddk5\xd8\x7f!b=M<\xc6aP\x03`F'
# raw_blank = b''
test_data = b'hello' #
stream = MagicMock() # test_data = b'hello'
#stream.read.return_value = add_header(test_data) # stream = MagicMock()
stream.read.side_effect = [ raw_1_my_info, raw_2_node_info, raw_3_node_info, raw_4_complete, # #stream.read.return_value = add_header(test_data)
raw_5_prefs, raw_6_channel0, raw_7_channel1, raw_8_channel2, # stream.read.side_effect = [ raw_1_my_info, raw_2_node_info, raw_3_node_info, raw_4_complete,
raw_blank, raw_blank] # raw_5_prefs, raw_6_channel0, raw_7_channel1, raw_8_channel2,
toRadio = MagicMock() # raw_blank, raw_blank]
toRadio.SerializeToString.return_value = test_data # toRadio = MagicMock()
with caplog.at_level(logging.DEBUG): # toRadio.SerializeToString.return_value = test_data
iface = StreamInterface(noProto=True, connectNow=False) # with caplog.at_level(logging.DEBUG):
iface.stream = stream # iface = StreamInterface(noProto=True, connectNow=False)
iface.connect() # iface.stream = stream
iface._sendToRadioImpl(toRadio) # iface.connect()
assert re.search(r'Sending: ', caplog.text, re.MULTILINE) # iface._sendToRadioImpl(toRadio)
assert re.search(r'reading character', caplog.text, re.MULTILINE) # assert re.search(r'Sending: ', caplog.text, re.MULTILINE)
assert re.search(r'In reader loop', caplog.text, re.MULTILINE) # assert re.search(r'reading character', caplog.text, re.MULTILINE)
# assert re.search(r'In reader loop', caplog.text, re.MULTILINE)

View File

@@ -1,8 +1,8 @@
"""Meshtastic unit tests for tcp_interface.py""" """Meshtastic unit tests for tcp_interface.py"""
import re import re
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
from ..tcp_interface import TCPInterface from ..tcp_interface import TCPInterface
@@ -11,18 +11,18 @@ from ..tcp_interface import TCPInterface
@pytest.mark.unit @pytest.mark.unit
def test_TCPInterface(capsys): def test_TCPInterface(capsys):
"""Test that we can instantiate a TCPInterface""" """Test that we can instantiate a TCPInterface"""
with patch('socket.socket') as mock_socket: with patch("socket.socket") as mock_socket:
iface = TCPInterface(hostname='localhost', noProto=True) iface = TCPInterface(hostname="localhost", noProto=True)
iface.myConnect() iface.myConnect()
iface.showInfo() iface.showInfo()
iface.localNode.showInfo() iface.localNode.showInfo()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Owner: None \(None\)', out, re.MULTILINE) assert re.search(r"Owner: None \(None\)", out, re.MULTILINE)
assert re.search(r'Nodes', out, re.MULTILINE) assert re.search(r"Nodes", out, re.MULTILINE)
assert re.search(r'Preferences', out, re.MULTILINE) assert re.search(r"Preferences", out, re.MULTILINE)
assert re.search(r'Channels', out, re.MULTILINE) assert re.search(r"Channels", out, re.MULTILINE)
assert re.search(r'Primary channel URL', out, re.MULTILINE) assert re.search(r"Primary channel URL", out, re.MULTILINE)
assert err == '' assert err == ""
assert mock_socket.called assert mock_socket.called
iface.close() iface.close()
@@ -34,10 +34,12 @@ def test_TCPInterface_exception():
def throw_an_exception(): def throw_an_exception():
raise ValueError("Fake exception.") raise ValueError("Fake exception.")
with patch('meshtastic.tcp_interface.TCPInterface._socket_shutdown') as mock_shutdown: with patch(
"meshtastic.tcp_interface.TCPInterface._socket_shutdown"
) as mock_shutdown:
mock_shutdown.side_effect = throw_an_exception mock_shutdown.side_effect = throw_an_exception
with patch('socket.socket') as mock_socket: with patch("socket.socket") as mock_socket:
iface = TCPInterface(hostname='localhost', noProto=True) iface = TCPInterface(hostname="localhost", noProto=True)
iface.myConnect() iface.myConnect()
iface.close() iface.close()
assert mock_socket.called assert mock_socket.called
@@ -47,6 +49,6 @@ def test_TCPInterface_exception():
@pytest.mark.unit @pytest.mark.unit
def test_TCPInterface_without_connecting(): def test_TCPInterface_without_connecting():
"""Test that we can instantiate a TCPInterface with connectNow as false""" """Test that we can instantiate a TCPInterface with connectNow as false"""
with patch('socket.socket'): with patch("socket.socket"):
iface = TCPInterface(hostname='localhost', noProto=True, connectNow=False) iface = TCPInterface(hostname="localhost", noProto=True, connectNow=False)
assert iface.socket is None assert iface.socket is None

View File

@@ -1,38 +1,38 @@
"""Meshtastic unit tests for tunnel.py""" """Meshtastic unit tests for tunnel.py"""
import logging
import re import re
import sys import sys
import logging from unittest.mock import MagicMock, patch
from unittest.mock import patch, MagicMock
import pytest import pytest
from ..globals import Globals
from ..tcp_interface import TCPInterface from ..tcp_interface import TCPInterface
from ..tunnel import Tunnel, onTunnelReceive from ..tunnel import Tunnel, onTunnelReceive
from ..globals import Globals
@pytest.mark.unit @pytest.mark.unit
@patch('platform.system') @patch("platform.system")
def test_Tunnel_on_non_linux_system(mock_platform_system): def test_Tunnel_on_non_linux_system(mock_platform_system):
"""Test that we cannot instantiate a Tunnel on a non Linux system""" """Test that we cannot instantiate a Tunnel on a non Linux system"""
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'notLinux' a_mock.return_value = "notLinux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with patch('socket.socket') as mock_socket: with patch("socket.socket") as mock_socket:
with pytest.raises(Exception) as pytest_wrapped_e: with pytest.raises(Exception) as pytest_wrapped_e:
iface = TCPInterface(hostname='localhost', noProto=True) iface = TCPInterface(hostname="localhost", noProto=True)
Tunnel(iface) Tunnel(iface)
assert pytest_wrapped_e.type == Exception assert pytest_wrapped_e.type == Exception
assert mock_socket.called assert mock_socket.called
@pytest.mark.unit @pytest.mark.unit
@patch('platform.system') @patch("platform.system")
def test_Tunnel_without_interface(mock_platform_system): def test_Tunnel_without_interface(mock_platform_system):
"""Test that we can not instantiate a Tunnel without a valid interface""" """Test that we can not instantiate a Tunnel without a valid interface"""
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with pytest.raises(Exception) as pytest_wrapped_e: with pytest.raises(Exception) as pytest_wrapped_e:
Tunnel(None) Tunnel(None)
@@ -40,227 +40,235 @@ def test_Tunnel_without_interface(mock_platform_system):
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('platform.system') @patch("platform.system")
def test_Tunnel_with_interface(mock_platform_system, caplog, iface_with_nodes): def test_Tunnel_with_interface(mock_platform_system, caplog, iface_with_nodes):
"""Test that we can not instantiate a Tunnel without a valid interface""" """Test that we can not instantiate a Tunnel without a valid interface"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.WARNING): with caplog.at_level(logging.WARNING):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
assert tun == Globals.getInstance().get_tunnelInstance() assert tun == Globals.getInstance().get_tunnelInstance()
iface.close() iface.close()
assert re.search(r'Not creating a TapDevice()', caplog.text, re.MULTILINE) assert re.search(r"Not creating a TapDevice()", caplog.text, re.MULTILINE)
assert re.search(r'Not starting TUN reader', caplog.text, re.MULTILINE) assert re.search(r"Not starting TUN reader", caplog.text, re.MULTILINE)
assert re.search(r'Not sending packet', caplog.text, re.MULTILINE) assert re.search(r"Not sending packet", caplog.text, re.MULTILINE)
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('platform.system') @patch("platform.system")
def test_onTunnelReceive_from_ourselves(mock_platform_system, caplog, iface_with_nodes): def test_onTunnelReceive_from_ourselves(mock_platform_system, caplog, iface_with_nodes):
"""Test onTunnelReceive""" """Test onTunnelReceive"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
sys.argv = [''] sys.argv = [""]
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
packet = {'decoded': { 'payload': 'foo'}, 'from': 2475227164} packet = {"decoded": {"payload": "foo"}, "from": 2475227164}
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
Globals.getInstance().set_tunnelInstance(tun) Globals.getInstance().set_tunnelInstance(tun)
onTunnelReceive(packet, iface) onTunnelReceive(packet, iface)
assert re.search(r'in onTunnelReceive', caplog.text, re.MULTILINE) assert re.search(r"in onTunnelReceive", caplog.text, re.MULTILINE)
assert re.search(r'Ignoring message we sent', caplog.text, re.MULTILINE) assert re.search(r"Ignoring message we sent", caplog.text, re.MULTILINE)
@pytest.mark.unit @pytest.mark.unit
@patch('platform.system') @patch("platform.system")
def test_onTunnelReceive_from_someone_else(mock_platform_system, caplog, iface_with_nodes): def test_onTunnelReceive_from_someone_else(
mock_platform_system, caplog, iface_with_nodes
):
"""Test onTunnelReceive""" """Test onTunnelReceive"""
iface = iface_with_nodes iface = iface_with_nodes
iface.myInfo.my_node_num = 2475227164 iface.myInfo.my_node_num = 2475227164
sys.argv = [''] sys.argv = [""]
Globals.getInstance().set_args(sys.argv) Globals.getInstance().set_args(sys.argv)
packet = {'decoded': { 'payload': 'foo'}, 'from': 123} packet = {"decoded": {"payload": "foo"}, "from": 123}
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
Globals.getInstance().set_tunnelInstance(tun) Globals.getInstance().set_tunnelInstance(tun)
onTunnelReceive(packet, iface) onTunnelReceive(packet, iface)
assert re.search(r'in onTunnelReceive', caplog.text, re.MULTILINE) assert re.search(r"in onTunnelReceive", caplog.text, re.MULTILINE)
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('platform.system') @patch("platform.system")
def test_shouldFilterPacket_random(mock_platform_system, caplog, iface_with_nodes): def test_shouldFilterPacket_random(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
# random packet # random packet
packet = b'1234567890123456789012345678901234567890' packet = b"1234567890123456789012345678901234567890"
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet) ignore = tun._shouldFilterPacket(packet)
assert not ignore assert not ignore
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('platform.system') @patch("platform.system")
def test_shouldFilterPacket_in_blacklist(mock_platform_system, caplog, iface_with_nodes): def test_shouldFilterPacket_in_blacklist(
mock_platform_system, caplog, iface_with_nodes
):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
# faked IGMP # faked IGMP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet) ignore = tun._shouldFilterPacket(packet)
assert ignore assert ignore
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('platform.system') @patch("platform.system")
def test_shouldFilterPacket_icmp(mock_platform_system, caplog, iface_with_nodes): def test_shouldFilterPacket_icmp(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
# faked ICMP # faked ICMP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet) ignore = tun._shouldFilterPacket(packet)
assert re.search(r'forwarding ICMP message', caplog.text, re.MULTILINE) assert re.search(r"forwarding ICMP message", caplog.text, re.MULTILINE)
assert not ignore assert not ignore
@pytest.mark.unit @pytest.mark.unit
@patch('platform.system') @patch("platform.system")
def test_shouldFilterPacket_udp(mock_platform_system, caplog, iface_with_nodes): def test_shouldFilterPacket_udp(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
# faked UDP # faked UDP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet) ignore = tun._shouldFilterPacket(packet)
assert re.search(r'forwarding udp', caplog.text, re.MULTILINE) assert re.search(r"forwarding udp", caplog.text, re.MULTILINE)
assert not ignore assert not ignore
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('platform.system') @patch("platform.system")
def test_shouldFilterPacket_udp_blacklisted(mock_platform_system, caplog, iface_with_nodes): def test_shouldFilterPacket_udp_blacklisted(
mock_platform_system, caplog, iface_with_nodes
):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
# faked UDP # faked UDP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x6c\x07\x6c\x00\x00\x00' packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x6c\x07\x6c\x00\x00\x00"
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
# Note: custom logging level # Note: custom logging level
LOG_TRACE = 5 LOG_TRACE = 5
with caplog.at_level(LOG_TRACE): with caplog.at_level(LOG_TRACE):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet) ignore = tun._shouldFilterPacket(packet)
assert re.search(r'ignoring blacklisted UDP', caplog.text, re.MULTILINE) assert re.search(r"ignoring blacklisted UDP", caplog.text, re.MULTILINE)
assert ignore assert ignore
@pytest.mark.unit @pytest.mark.unit
@patch('platform.system') @patch("platform.system")
def test_shouldFilterPacket_tcp(mock_platform_system, caplog, iface_with_nodes): def test_shouldFilterPacket_tcp(mock_platform_system, caplog, iface_with_nodes):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
# faked TCP # faked TCP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet) ignore = tun._shouldFilterPacket(packet)
assert re.search(r'forwarding tcp', caplog.text, re.MULTILINE) assert re.search(r"forwarding tcp", caplog.text, re.MULTILINE)
assert not ignore assert not ignore
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('platform.system') @patch("platform.system")
def test_shouldFilterPacket_tcp_blacklisted(mock_platform_system, caplog, iface_with_nodes): def test_shouldFilterPacket_tcp_blacklisted(
mock_platform_system, caplog, iface_with_nodes
):
"""Test _shouldFilterPacket()""" """Test _shouldFilterPacket()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
# faked TCP # faked TCP
packet = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x0c\x17\x0c\x00\x00\x00' packet = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x0c\x17\x0c\x00\x00\x00"
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
# Note: custom logging level # Note: custom logging level
LOG_TRACE = 5 LOG_TRACE = 5
with caplog.at_level(LOG_TRACE): with caplog.at_level(LOG_TRACE):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
ignore = tun._shouldFilterPacket(packet) ignore = tun._shouldFilterPacket(packet)
assert re.search(r'ignoring blacklisted TCP', caplog.text, re.MULTILINE) assert re.search(r"ignoring blacklisted TCP", caplog.text, re.MULTILINE)
assert ignore assert ignore
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('platform.system') @patch("platform.system")
def test_ipToNodeId_none(mock_platform_system, caplog, iface_with_nodes): def test_ipToNodeId_none(mock_platform_system, caplog, iface_with_nodes):
"""Test _ipToNodeId()""" """Test _ipToNodeId()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
nodeid = tun._ipToNodeId('something not useful') nodeid = tun._ipToNodeId("something not useful")
assert nodeid is None assert nodeid is None
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('platform.system') @patch("platform.system")
def test_ipToNodeId_all(mock_platform_system, caplog, iface_with_nodes): def test_ipToNodeId_all(mock_platform_system, caplog, iface_with_nodes):
"""Test _ipToNodeId()""" """Test _ipToNodeId()"""
iface = iface_with_nodes iface = iface_with_nodes
iface.noProto = True iface.noProto = True
a_mock = MagicMock() a_mock = MagicMock()
a_mock.return_value = 'Linux' a_mock.return_value = "Linux"
mock_platform_system.side_effect = a_mock mock_platform_system.side_effect = a_mock
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
with patch('socket.socket'): with patch("socket.socket"):
tun = Tunnel(iface) tun = Tunnel(iface)
nodeid = tun._ipToNodeId(b'\x00\x00\xff\xff') nodeid = tun._ipToNodeId(b"\x00\x00\xff\xff")
assert nodeid == '^all' assert nodeid == "^all"

View File

@@ -1,111 +1,131 @@
"""Meshtastic unit tests for util.py""" """Meshtastic unit tests for util.py"""
import re
import logging import logging
import re
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
from meshtastic.util import (fixme, stripnl, pskToString, our_exit, from meshtastic.supported_device import SupportedDevice
support_info, genPSK256, fromStr, fromPSK, from meshtastic.util import (
quoteBooleans, catchAndIgnore, Timeout,
remove_keys_from_dict, Timeout, hexstr, active_ports_on_supported_devices,
ipstr, readnet_u16, findPorts, convert_mac_addr, camel_to_snake,
snake_to_camel, camel_to_snake) catchAndIgnore,
convert_mac_addr,
eliminate_duplicate_port,
findPorts,
fixme,
fromPSK,
fromStr,
genPSK256,
hexstr,
ipstr,
is_windows11,
our_exit,
pskToString,
quoteBooleans,
readnet_u16,
remove_keys_from_dict,
snake_to_camel,
stripnl,
support_info,
)
@pytest.mark.unit @pytest.mark.unit
def test_genPSK256(): def test_genPSK256():
"""Test genPSK256""" """Test genPSK256"""
assert genPSK256() != '' assert genPSK256() != ""
@pytest.mark.unit @pytest.mark.unit
def test_fromStr(): def test_fromStr():
"""Test fromStr""" """Test fromStr"""
assert fromStr('') == b'' assert fromStr("") == b""
assert fromStr('0x12') == b'\x12' assert fromStr("0x12") == b"\x12"
assert fromStr('t') assert fromStr("t")
assert fromStr('T') assert fromStr("T")
assert fromStr('true') assert fromStr("true")
assert fromStr('True') assert fromStr("True")
assert fromStr('yes') assert fromStr("yes")
assert fromStr('Yes') assert fromStr("Yes")
assert fromStr('f') is False assert fromStr("f") is False
assert fromStr('F') is False assert fromStr("F") is False
assert fromStr('false') is False assert fromStr("false") is False
assert fromStr('False') is False assert fromStr("False") is False
assert fromStr('no') is False assert fromStr("no") is False
assert fromStr('No') is False assert fromStr("No") is False
assert fromStr('100.01') == 100.01 assert fromStr("100.01") == 100.01
assert fromStr('123') == 123 assert fromStr("123") == 123
assert fromStr('abc') == 'abc' assert fromStr("abc") == "abc"
assert fromStr('123456789') == 123456789 assert fromStr("123456789") == 123456789
@pytest.mark.unitslow @pytest.mark.unitslow
def test_quoteBooleans(): def test_quoteBooleans():
"""Test quoteBooleans""" """Test quoteBooleans"""
assert quoteBooleans('') == '' assert quoteBooleans("") == ""
assert quoteBooleans('foo') == 'foo' assert quoteBooleans("foo") == "foo"
assert quoteBooleans('true') == 'true' assert quoteBooleans("true") == "true"
assert quoteBooleans('false') == 'false' assert quoteBooleans("false") == "false"
assert quoteBooleans(': true') == ": 'true'" assert quoteBooleans(": true") == ": 'true'"
assert quoteBooleans(': false') == ": 'false'" assert quoteBooleans(": false") == ": 'false'"
@pytest.mark.unit @pytest.mark.unit
def test_fromPSK(): def test_fromPSK():
"""Test fromPSK""" """Test fromPSK"""
assert fromPSK('random') != '' assert fromPSK("random") != ""
assert fromPSK('none') == b'\x00' assert fromPSK("none") == b"\x00"
assert fromPSK('default') == b'\x01' assert fromPSK("default") == b"\x01"
assert fromPSK('simple22') == b'\x17' assert fromPSK("simple22") == b"\x17"
assert fromPSK('trash') == 'trash' assert fromPSK("trash") == "trash"
@pytest.mark.unit @pytest.mark.unit
def test_stripnl(): def test_stripnl():
"""Test stripnl""" """Test stripnl"""
assert stripnl('') == '' assert stripnl("") == ""
assert stripnl('a\n') == 'a' assert stripnl("a\n") == "a"
assert stripnl(' a \n ') == 'a' assert stripnl(" a \n ") == "a"
assert stripnl('a\nb') == 'a b' assert stripnl("a\nb") == "a b"
@pytest.mark.unit @pytest.mark.unit
def test_pskToString_empty_string(): def test_pskToString_empty_string():
"""Test pskToString empty string""" """Test pskToString empty string"""
assert pskToString('') == 'unencrypted' assert pskToString("") == "unencrypted"
@pytest.mark.unit @pytest.mark.unit
def test_pskToString_string(): def test_pskToString_string():
"""Test pskToString string""" """Test pskToString string"""
assert pskToString('hunter123') == 'secret' assert pskToString("hunter123") == "secret"
@pytest.mark.unit @pytest.mark.unit
def test_pskToString_one_byte_zero_value(): def test_pskToString_one_byte_zero_value():
"""Test pskToString one byte that is value of 0""" """Test pskToString one byte that is value of 0"""
assert pskToString(bytes([0x00])) == 'unencrypted' assert pskToString(bytes([0x00])) == "unencrypted"
@pytest.mark.unitslow @pytest.mark.unitslow
def test_pskToString_one_byte_non_zero_value(): def test_pskToString_one_byte_non_zero_value():
"""Test pskToString one byte that is non-zero""" """Test pskToString one byte that is non-zero"""
assert pskToString(bytes([0x01])) == 'default' assert pskToString(bytes([0x01])) == "default"
@pytest.mark.unitslow @pytest.mark.unitslow
def test_pskToString_many_bytes(): def test_pskToString_many_bytes():
"""Test pskToString many bytes""" """Test pskToString many bytes"""
assert pskToString(bytes([0x02, 0x01])) == 'secret' assert pskToString(bytes([0x02, 0x01])) == "secret"
@pytest.mark.unit @pytest.mark.unit
def test_pskToString_simple(): def test_pskToString_simple():
"""Test pskToString simple""" """Test pskToString simple"""
assert pskToString(bytes([0x03])) == 'simple2' assert pskToString(bytes([0x03])) == "simple2"
@pytest.mark.unitslow @pytest.mark.unitslow
@@ -114,8 +134,8 @@ def test_our_exit_zero_return_value(capsys):
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit("Warning: Some message", 0) our_exit("Warning: Some message", 0)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Warning: Some message', out, re.MULTILINE) assert re.search(r"Warning: Some message", out, re.MULTILINE)
assert err == '' assert err == ""
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 0 assert pytest_wrapped_e.value.code == 0
@@ -126,8 +146,8 @@ def test_our_exit_non_zero_return_value(capsys):
with pytest.raises(SystemExit) as pytest_wrapped_e: with pytest.raises(SystemExit) as pytest_wrapped_e:
our_exit("Error: Some message", 1) our_exit("Error: Some message", 1)
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'Error: Some message', out, re.MULTILINE) assert re.search(r"Error: Some message", out, re.MULTILINE)
assert err == '' assert err == ""
assert pytest_wrapped_e.type == SystemExit assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == 1 assert pytest_wrapped_e.value.code == 1
@@ -145,21 +165,23 @@ def test_support_info(capsys):
"""Test support_info""" """Test support_info"""
support_info() support_info()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert re.search(r'System', out, re.MULTILINE) assert re.search(r"System", out, re.MULTILINE)
assert re.search(r'Platform', out, re.MULTILINE) assert re.search(r"Platform", out, re.MULTILINE)
assert re.search(r'Machine', out, re.MULTILINE) assert re.search(r"Machine", out, re.MULTILINE)
assert re.search(r'Executable', out, re.MULTILINE) assert re.search(r"Executable", out, re.MULTILINE)
assert err == '' assert err == ""
@pytest.mark.unit @pytest.mark.unit
def test_catchAndIgnore(caplog): def test_catchAndIgnore(caplog):
"""Test catchAndIgnore() does not actually throw an exception, but just logs""" """Test catchAndIgnore() does not actually throw an exception, but just logs"""
def some_closure(): def some_closure():
raise Exception('foo') raise Exception("foo")
with caplog.at_level(logging.DEBUG): with caplog.at_level(logging.DEBUG):
catchAndIgnore("something", some_closure) catchAndIgnore("something", some_closure)
assert re.search(r'Exception thrown in something', caplog.text, re.MULTILINE) assert re.search(r"Exception thrown in something", caplog.text, re.MULTILINE)
@pytest.mark.unitslow @pytest.mark.unitslow
@@ -171,35 +193,35 @@ def test_remove_keys_from_dict_empty_keys_empty_dict():
@pytest.mark.unitslow @pytest.mark.unitslow
def test_remove_keys_from_dict_empty_dict(): def test_remove_keys_from_dict_empty_dict():
"""Test when dict is empty""" """Test when dict is empty"""
assert not remove_keys_from_dict(('a'), {}) assert not remove_keys_from_dict(("a"), {})
@pytest.mark.unit @pytest.mark.unit
def test_remove_keys_from_dict_empty_keys(): def test_remove_keys_from_dict_empty_keys():
"""Test when keys is empty""" """Test when keys is empty"""
assert remove_keys_from_dict((), {'a':1}) == {'a':1} assert remove_keys_from_dict((), {"a": 1}) == {"a": 1}
@pytest.mark.unitslow @pytest.mark.unitslow
def test_remove_keys_from_dict(): def test_remove_keys_from_dict():
"""Test remove_keys_from_dict()""" """Test remove_keys_from_dict()"""
assert remove_keys_from_dict(('b'), {'a':1, 'b':2}) == {'a':1} assert remove_keys_from_dict(("b"), {"a": 1, "b": 2}) == {"a": 1}
@pytest.mark.unitslow @pytest.mark.unitslow
def test_remove_keys_from_dict_multiple_keys(): def test_remove_keys_from_dict_multiple_keys():
"""Test remove_keys_from_dict()""" """Test remove_keys_from_dict()"""
keys = ('a', 'b') keys = ("a", "b")
adict = {'a': 1, 'b': 2, 'c': 3} adict = {"a": 1, "b": 2, "c": 3}
assert remove_keys_from_dict(keys, adict) == {'c':3} assert remove_keys_from_dict(keys, adict) == {"c": 3}
@pytest.mark.unit @pytest.mark.unit
def test_remove_keys_from_dict_nested(): def test_remove_keys_from_dict_nested():
"""Test remove_keys_from_dict()""" """Test remove_keys_from_dict()"""
keys = ('b') keys = "b"
adict = {'a': {'b': 1}, 'b': 2, 'c': 3} adict = {"a": {"b": 1}, "b": 2, "c": 3}
exp = {'a': {}, 'c': 3} exp = {"a": {}, "c": 3}
assert remove_keys_from_dict(keys, adict) == exp assert remove_keys_from_dict(keys, adict) == exp
@@ -207,8 +229,8 @@ def test_remove_keys_from_dict_nested():
def test_Timeout_not_found(): def test_Timeout_not_found():
"""Test Timeout()""" """Test Timeout()"""
to = Timeout(0.2) to = Timeout(0.2)
attrs = ('foo') attrs = "foo"
to.waitForSet('bar', attrs) to.waitForSet("bar", attrs)
@pytest.mark.unitslow @pytest.mark.unitslow
@@ -216,59 +238,310 @@ def test_Timeout_found():
"""Test Timeout()""" """Test Timeout()"""
to = Timeout(0.2) to = Timeout(0.2)
attrs = () attrs = ()
to.waitForSet('bar', attrs) to.waitForSet("bar", attrs)
@pytest.mark.unitslow @pytest.mark.unitslow
def test_hexstr(): def test_hexstr():
"""Test hexstr()""" """Test hexstr()"""
assert hexstr(b'123') == '31:32:33' assert hexstr(b"123") == "31:32:33"
assert hexstr(b'') == '' assert hexstr(b"") == ""
@pytest.mark.unitslow @pytest.mark.unitslow
def test_ipstr(): def test_ipstr():
"""Test ipstr()""" """Test ipstr()"""
assert ipstr(b'1234') == '49.50.51.52' assert ipstr(b"1234") == "49.50.51.52"
assert ipstr(b'') == '' assert ipstr(b"") == ""
@pytest.mark.unitslow @pytest.mark.unitslow
def test_readnet_u16(): def test_readnet_u16():
"""Test readnet_u16()""" """Test readnet_u16()"""
assert readnet_u16(b'123456', 2) == 13108 assert readnet_u16(b"123456", 2) == 13108
@pytest.mark.unitslow @pytest.mark.unitslow
@patch('serial.tools.list_ports.comports', return_value=[]) @patch("serial.tools.list_ports.comports", return_value=[])
def test_findPorts_when_none_found(patch_comports): def test_findPorts_when_none_found(patch_comports):
"""Test findPorts()""" """Test findPorts()"""
assert not findPorts() assert not findPorts()
patch_comports.assert_called() patch_comports.assert_called()
@pytest.mark.unitslow
@patch("serial.tools.list_ports.comports")
def test_findPorts_when_duplicate_found_and_duplicate_option_used(patch_comports):
"""Test findPorts()"""
class TempPort:
"""temp class for port"""
def __init__(self, device=None, vid=None):
self.device = device
self.vid = vid
fake1 = TempPort("/dev/cu.usbserial-1430", vid="fake1")
fake2 = TempPort("/dev/cu.wchusbserial1430", vid="fake2")
patch_comports.return_value = [fake1, fake2]
assert findPorts(eliminate_duplicates=True) == ["/dev/cu.wchusbserial1430"]
patch_comports.assert_called()
@pytest.mark.unitslow
@patch("serial.tools.list_ports.comports")
def test_findPorts_when_duplicate_found_and_duplicate_option_used_ports_reversed(
patch_comports,
):
"""Test findPorts()"""
class TempPort:
"""temp class for port"""
def __init__(self, device=None, vid=None):
self.device = device
self.vid = vid
fake1 = TempPort("/dev/cu.usbserial-1430", vid="fake1")
fake2 = TempPort("/dev/cu.wchusbserial1430", vid="fake2")
patch_comports.return_value = [fake2, fake1]
assert findPorts(eliminate_duplicates=True) == ["/dev/cu.wchusbserial1430"]
patch_comports.assert_called()
@pytest.mark.unitslow
@patch("serial.tools.list_ports.comports")
def test_findPorts_when_duplicate_found_and_duplicate_option_not_used(patch_comports):
"""Test findPorts()"""
class TempPort:
"""temp class for port"""
def __init__(self, device=None, vid=None):
self.device = device
self.vid = vid
fake1 = TempPort("/dev/cu.usbserial-1430", vid="fake1")
fake2 = TempPort("/dev/cu.wchusbserial1430", vid="fake2")
patch_comports.return_value = [fake1, fake2]
assert findPorts() == ["/dev/cu.usbserial-1430", "/dev/cu.wchusbserial1430"]
patch_comports.assert_called()
@pytest.mark.unitslow @pytest.mark.unitslow
def test_convert_mac_addr(): def test_convert_mac_addr():
"""Test convert_mac_addr()""" """Test convert_mac_addr()"""
assert convert_mac_addr('/c0gFyhb') == 'fd:cd:20:17:28:5b' assert convert_mac_addr("/c0gFyhb") == "fd:cd:20:17:28:5b"
assert convert_mac_addr('fd:cd:20:17:28:5b') == 'fd:cd:20:17:28:5b' assert convert_mac_addr("fd:cd:20:17:28:5b") == "fd:cd:20:17:28:5b"
assert convert_mac_addr('') == '' assert convert_mac_addr("") == ""
@pytest.mark.unit @pytest.mark.unit
def test_snake_to_camel(): def test_snake_to_camel():
"""Test snake_to_camel""" """Test snake_to_camel"""
assert snake_to_camel('') == '' assert snake_to_camel("") == ""
assert snake_to_camel('foo') == 'foo' assert snake_to_camel("foo") == "foo"
assert snake_to_camel('foo_bar') == 'fooBar' assert snake_to_camel("foo_bar") == "fooBar"
assert snake_to_camel('fooBar') == 'fooBar' assert snake_to_camel("fooBar") == "fooBar"
@pytest.mark.unit @pytest.mark.unit
def test_camel_to_snake(): def test_camel_to_snake():
"""Test camel_to_snake""" """Test camel_to_snake"""
assert camel_to_snake('') == '' assert camel_to_snake("") == ""
assert camel_to_snake('foo') == 'foo' assert camel_to_snake("foo") == "foo"
assert camel_to_snake('Foo') == 'foo' assert camel_to_snake("Foo") == "foo"
assert camel_to_snake('fooBar') == 'foo_bar' assert camel_to_snake("fooBar") == "foo_bar"
assert camel_to_snake('fooBarBaz') == 'foo_bar_baz' assert camel_to_snake("fooBarBaz") == "foo_bar_baz"
@pytest.mark.unit
def test_eliminate_duplicate_port():
"""Test eliminate_duplicate_port()"""
assert not eliminate_duplicate_port([])
assert eliminate_duplicate_port(["/dev/fake"]) == ["/dev/fake"]
assert eliminate_duplicate_port(["/dev/fake", "/dev/fake1"]) == [
"/dev/fake",
"/dev/fake1",
]
assert eliminate_duplicate_port(["/dev/fake", "/dev/fake1", "/dev/fake2"]) == [
"/dev/fake",
"/dev/fake1",
"/dev/fake2",
]
assert eliminate_duplicate_port(
["/dev/cu.usbserial-1430", "/dev/cu.wchusbserial1430"]
) == ["/dev/cu.wchusbserial1430"]
assert eliminate_duplicate_port(
["/dev/cu.wchusbserial1430", "/dev/cu.usbserial-1430"]
) == ["/dev/cu.wchusbserial1430"]
assert eliminate_duplicate_port(
["/dev/cu.SLAB_USBtoUART", "/dev/cu.usbserial-0001"]
) == ["/dev/cu.usbserial-0001"]
assert eliminate_duplicate_port(
["/dev/cu.usbserial-0001", "/dev/cu.SLAB_USBtoUART"]
) == ["/dev/cu.usbserial-0001"]
assert eliminate_duplicate_port(
["/dev/cu.usbmodem11301", "/dev/cu.wchusbserial11301"]
) == ["/dev/cu.wchusbserial11301"]
assert eliminate_duplicate_port(
["/dev/cu.wchusbserial11301", "/dev/cu.usbmodem11301"]
) == ["/dev/cu.wchusbserial11301"]
assert eliminate_duplicate_port(
["/dev/cu.usbmodem53230051441", "/dev/cu.wchusbserial53230051441"]
) == ["/dev/cu.wchusbserial53230051441"]
assert eliminate_duplicate_port(
["/dev/cu.wchusbserial53230051441", "/dev/cu.usbmodem53230051441"]
) == ["/dev/cu.wchusbserial53230051441"]
@patch("platform.version", return_value="10.0.22000.194")
@patch("platform.release", return_value="10")
@patch("platform.system", return_value="Windows")
def test_is_windows11_true(patched_platform, patched_release, patched_version):
"""Test is_windows11()"""
assert is_windows11() is True
patched_platform.assert_called()
patched_release.assert_called()
patched_version.assert_called()
@patch("platform.version", return_value="10.0.a2200.foo") # made up
@patch("platform.release", return_value="10")
@patch("platform.system", return_value="Windows")
def test_is_windows11_true2(patched_platform, patched_release, patched_version):
"""Test is_windows11()"""
assert is_windows11() is False
patched_platform.assert_called()
patched_release.assert_called()
patched_version.assert_called()
@patch("platform.version", return_value="10.0.17763") # windows 10 home
@patch("platform.release", return_value="10")
@patch("platform.system", return_value="Windows")
def test_is_windows11_false(patched_platform, patched_release, patched_version):
"""Test is_windows11()"""
assert is_windows11() is False
patched_platform.assert_called()
patched_release.assert_called()
patched_version.assert_called()
@patch("platform.release", return_value="8.1")
@patch("platform.system", return_value="Windows")
def test_is_windows11_false_win8_1(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")
def test_active_ports_on_supported_devices_empty(mock_platform):
"""Test active_ports_on_supported_devices()"""
sds = set()
assert active_ports_on_supported_devices(sds) == set()
mock_platform.assert_called()
@pytest.mark.unit
@patch("subprocess.getstatusoutput")
@patch("platform.system", return_value="Linux")
def test_active_ports_on_supported_devices_linux(mock_platform, mock_sp):
"""Test active_ports_on_supported_devices()"""
mock_sp.return_value = (
None,
"crw-rw-rw- 1 root wheel 0x9000000 Feb 8 22:22 /dev/ttyUSBfake",
)
fake_device = SupportedDevice(
name="a", for_firmware="heltec-v2.1", baseport_on_linux="ttyUSB"
)
fake_supported_devices = [fake_device]
assert active_ports_on_supported_devices(fake_supported_devices) == {
"/dev/ttyUSBfake"
}
mock_platform.assert_called()
mock_sp.assert_called()
@pytest.mark.unit
@patch("subprocess.getstatusoutput")
@patch("platform.system", return_value="Darwin")
def test_active_ports_on_supported_devices_mac(mock_platform, mock_sp):
"""Test active_ports_on_supported_devices()"""
mock_sp.return_value = (
None,
"crw-rw-rw- 1 root wheel 0x9000000 Feb 8 22:22 /dev/cu.usbserial-foo",
)
fake_device = SupportedDevice(
name="a", for_firmware="heltec-v2.1", baseport_on_linux="cu.usbserial-"
)
fake_supported_devices = [fake_device]
assert active_ports_on_supported_devices(fake_supported_devices) == {
"/dev/cu.usbserial-foo"
}
mock_platform.assert_called()
mock_sp.assert_called()
@pytest.mark.unit
@patch("meshtastic.util.detect_windows_port", return_value={"COM2"})
@patch("platform.system", return_value="Windows")
def test_active_ports_on_supported_devices_win(mock_platform, mock_dwp):
"""Test active_ports_on_supported_devices()"""
fake_device = SupportedDevice(name="a", for_firmware="heltec-v2.1")
fake_supported_devices = [fake_device]
assert active_ports_on_supported_devices(fake_supported_devices) == {"COM2"}
mock_platform.assert_called()
mock_dwp.assert_called()
@pytest.mark.unit
@patch("subprocess.getstatusoutput")
@patch("platform.system", return_value="Darwin")
def test_active_ports_on_supported_devices_mac_no_duplicates_check(
mock_platform, mock_sp
):
"""Test active_ports_on_supported_devices()"""
mock_sp.return_value = (
None,
(
"crw-rw-rw- 1 root wheel 0x9000005 Mar 8 10:05 /dev/cu.usbmodem53230051441\n"
"crw-rw-rw- 1 root wheel 0x9000003 Mar 8 10:06 /dev/cu.wchusbserial53230051441"
),
)
fake_device = SupportedDevice(
name="a", for_firmware="tbeam", baseport_on_mac="cu.usbmodem"
)
fake_supported_devices = [fake_device]
assert active_ports_on_supported_devices(fake_supported_devices, False) == {
"/dev/cu.usbmodem53230051441",
"/dev/cu.wchusbserial53230051441",
}
mock_platform.assert_called()
mock_sp.assert_called()
@pytest.mark.unit
@patch("subprocess.getstatusoutput")
@patch("platform.system", return_value="Darwin")
def test_active_ports_on_supported_devices_mac_duplicates_check(mock_platform, mock_sp):
"""Test active_ports_on_supported_devices()"""
mock_sp.return_value = (
None,
(
"crw-rw-rw- 1 root wheel 0x9000005 Mar 8 10:05 /dev/cu.usbmodem53230051441\n"
"crw-rw-rw- 1 root wheel 0x9000003 Mar 8 10:06 /dev/cu.wchusbserial53230051441"
),
)
fake_device = SupportedDevice(
name="a", for_firmware="tbeam", baseport_on_mac="cu.usbmodem"
)
fake_supported_devices = [fake_device]
assert active_ports_on_supported_devices(fake_supported_devices, True) == {
"/dev/cu.wchusbserial53230051441"
}
mock_platform.assert_called()
mock_sp.assert_called()

View File

@@ -16,20 +16,20 @@
""" """
import logging import logging
import threading
import platform import platform
from pubsub import pub import threading
from pubsub import pub
from pytap2 import TapDevice from pytap2 import TapDevice
from meshtastic import portnums_pb2 from meshtastic import portnums_pb2
from meshtastic.util import ipstr, readnet_u16
from meshtastic.globals import Globals from meshtastic.globals import Globals
from meshtastic.util import ipstr, readnet_u16
def onTunnelReceive(packet, interface): # pylint: disable=W0613 def onTunnelReceive(packet, interface): # pylint: disable=W0613
"""Callback for received tunneled messages from mesh.""" """Callback for received tunneled messages from mesh."""
logging.debug(f'in onTunnelReceive()') logging.debug(f"in onTunnelReceive()")
our_globals = Globals.getInstance() our_globals = Globals.getInstance()
tunnelInstance = our_globals.get_tunnelInstance() tunnelInstance = our_globals.get_tunnelInstance()
tunnelInstance.onReceive(packet) tunnelInstance.onReceive(packet)
@@ -38,7 +38,7 @@ def onTunnelReceive(packet, interface): # pylint: disable=W0613
class Tunnel: class Tunnel:
"""A TUN based IP tunnel over meshtastic""" """A TUN based IP tunnel over meshtastic"""
def __init__(self, iface, subnet='10.115', netmask="255.255.0.0"): def __init__(self, iface, subnet="10.115", netmask="255.255.0.0"):
""" """
Constructor Constructor
@@ -52,7 +52,7 @@ class Tunnel:
self.iface = iface self.iface = iface
self.subnetPrefix = subnet self.subnetPrefix = subnet
if platform.system() != 'Linux': if platform.system() != "Linux":
raise Exception("Tunnel() can only be run instantiated on a Linux system") raise Exception("Tunnel() can only be run instantiated on a Linux system")
our_globals = Globals.getInstance() our_globals = Globals.getInstance()
@@ -80,8 +80,10 @@ class Tunnel:
self.LOG_TRACE = 5 self.LOG_TRACE = 5
# TODO: check if root? # TODO: check if root?
logging.info("Starting IP to mesh tunnel (you must be root for this *pre-alpha* "\ logging.info(
"feature to work). Mesh members:") "Starting IP to mesh tunnel (you must be root for this *pre-alpha* "
"feature to work). Mesh members:"
)
pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP") pub.subscribe(onTunnelReceive, "meshtastic.receive.data.IP_TUNNEL_APP")
myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num) myAddr = self._nodeNumToIp(self.iface.myInfo.my_node_num)
@@ -96,7 +98,9 @@ class Tunnel:
# FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data # FIXME - figure out real max MTU, it should be 240 - the overhead bytes for SubPacket and Data
self.tun = None self.tun = None
if self.iface.noProto: if self.iface.noProto:
logging.warning(f"Not creating a TapDevice() because it is disabled by noProto") logging.warning(
f"Not creating a TapDevice() because it is disabled by noProto"
)
else: else:
self.tun = TapDevice(name="mesh") self.tun = TapDevice(name="mesh")
self.tun.up() self.tun.up()
@@ -104,10 +108,14 @@ class Tunnel:
self._rxThread = None self._rxThread = None
if self.iface.noProto: if self.iface.noProto:
logging.warning(f"Not starting TUN reader because it is disabled by noProto") logging.warning(
f"Not starting TUN reader because it is disabled by noProto"
)
else: else:
logging.debug(f"starting TUN reader, our IP address is {myAddr}") logging.debug(f"starting TUN reader, our IP address is {myAddr}")
self._rxThread = threading.Thread(target=self.__tunReader, args=(), daemon=True) self._rxThread = threading.Thread(
target=self.__tunReader, args=(), daemon=True
)
self._rxThread.start() self._rxThread.start()
def onReceive(self, packet): def onReceive(self, packet):
@@ -132,15 +140,19 @@ class Tunnel:
ignore = False # Assume we will be forwarding the packet ignore = False # Assume we will be forwarding the packet
if protocol in self.protocolBlacklist: if protocol in self.protocolBlacklist:
ignore = True ignore = True
logging.log(self.LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}") logging.log(
self.LOG_TRACE, f"Ignoring blacklisted protocol 0x{protocol:02x}"
)
elif protocol == 0x01: # ICMP elif protocol == 0x01: # ICMP
icmpType = p[20] icmpType = p[20]
icmpCode = p[21] icmpCode = p[21]
checksum = p[22:24] checksum = p[22:24]
# pylint: disable=line-too-long # pylint: disable=line-too-long
logging.debug(f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}") logging.debug(
f"forwarding ICMP message src={ipstr(srcaddr)}, dest={ipstr(destAddr)}, type={icmpType}, code={icmpCode}, checksum={checksum}"
)
# reply to pings (swap src and dest but keep rest of packet unchanged) # reply to pings (swap src and dest but keep rest of packet unchanged)
#pingback = p[:12]+p[16:20]+p[12:16]+p[20:] # pingback = p[:12]+p[16:20]+p[12:16]+p[20:]
# tap.write(pingback) # tap.write(pingback)
elif protocol == 0x11: # UDP elif protocol == 0x11: # UDP
srcport = readnet_u16(p, subheader) srcport = readnet_u16(p, subheader)
@@ -159,8 +171,10 @@ class Tunnel:
else: else:
logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}") logging.debug(f"forwarding tcp srcport={srcport}, destport={destport}")
else: else:
logging.warning(f"forwarding unexpected protocol 0x{protocol:02x}, "\ logging.warning(
"src={ipstr(srcaddr)}, dest={ipstr(destAddr)}") f"forwarding unexpected protocol 0x{protocol:02x}, "
"src={ipstr(srcaddr)}, dest={ipstr(destAddr)}"
)
return ignore return ignore
@@ -169,7 +183,7 @@ class Tunnel:
logging.debug("TUN reader running") logging.debug("TUN reader running")
while True: while True:
p = tap.read() p = tap.read()
#logging.debug(f"IP packet received on TUN interface, type={type(p)}") # logging.debug(f"IP packet received on TUN interface, type={type(p)}")
destAddr = p[16:20] destAddr = p[16:20]
if not self._shouldFilterPacket(p): if not self._shouldFilterPacket(p):
@@ -179,11 +193,11 @@ class Tunnel:
# We only consider the last 16 bits of the nodenum for IP address matching # We only consider the last 16 bits of the nodenum for IP address matching
ipBits = ipAddr[2] * 256 + ipAddr[3] ipBits = ipAddr[2] * 256 + ipAddr[3]
if ipBits == 0xffff: if ipBits == 0xFFFF:
return "^all" return "^all"
for node in self.iface.nodes.values(): for node in self.iface.nodes.values():
nodeNum = node["num"] & 0xffff nodeNum = node["num"] & 0xFFFF
# logging.debug(f"Considering nodenum 0x{nodeNum:x} for ipBits 0x{ipBits:x}") # logging.debug(f"Considering nodenum 0x{nodeNum:x} for ipBits 0x{ipBits:x}")
if (nodeNum) == ipBits: if (nodeNum) == ipBits:
return node["user"]["id"] return node["user"]["id"]
@@ -196,11 +210,14 @@ class Tunnel:
"""Forward the provided IP packet into the mesh""" """Forward the provided IP packet into the mesh"""
nodeId = self._ipToNodeId(destAddr) nodeId = self._ipToNodeId(destAddr)
if nodeId is not None: if nodeId is not None:
logging.debug(f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}") logging.debug(
self.iface.sendData( f"Forwarding packet bytelen={len(p)} dest={ipstr(destAddr)}, destNode={nodeId}"
p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False) )
self.iface.sendData(p, nodeId, portnums_pb2.IP_TUNNEL_APP, wantAck=False)
else: else:
logging.warning(f"Dropping packet because no node found for destIP={ipstr(destAddr)}") logging.warning(
f"Dropping packet because no node found for destIP={ipstr(destAddr)}"
)
def close(self): def close(self):
"""Close""" """Close"""

View File

@@ -1,20 +1,23 @@
"""Utility functions. """Utility functions.
""" """
import base64
import logging
import os
import platform
import re
import subprocess
import sys
import threading
import time
import traceback import traceback
from queue import Queue from queue import Queue
import os
import re import pkg_resources
import sys import requests
import base64
import time
import platform
import logging
import threading
import subprocess
import serial import serial
import serial.tools.list_ports import serial.tools.list_ports
import pkg_resources
from meshtastic.supported_device import get_unique_vendor_ids, get_devices_with_vendor_id from meshtastic.supported_device import supported_devices
"""Some devices such as a seger jlink we never want to accidentally open""" """Some devices such as a seger jlink we never want to accidentally open"""
blacklistVids = dict.fromkeys([0x1366]) blacklistVids = dict.fromkeys([0x1366])
@@ -22,12 +25,13 @@ blacklistVids = dict.fromkeys([0x1366])
def quoteBooleans(a_string): def quoteBooleans(a_string):
"""Quote booleans """Quote booleans
given a string that contains ": true", replace with ": 'true'" (or false) given a string that contains ": true", replace with ": 'true'" (or false)
""" """
tmp = a_string.replace(": true", ": 'true'") tmp = a_string.replace(": true", ": 'true'")
tmp = tmp.replace(": false", ": 'false'") tmp = tmp.replace(": false", ": 'false'")
return tmp return tmp
def genPSK256(): def genPSK256():
"""Generate a random preshared key""" """Generate a random preshared key"""
return os.urandom(32) return os.urandom(32)
@@ -60,9 +64,11 @@ def fromStr(valstr):
""" """
if len(valstr) == 0: # Treat an emptystring as an empty bytes if len(valstr) == 0: # Treat an emptystring as an empty bytes
val = bytes() val = bytes()
elif valstr.startswith('0x'): elif valstr.startswith("0x"):
# if needed convert to string with asBytes.decode('utf-8') # if needed convert to string with asBytes.decode('utf-8')
val = bytes.fromhex(valstr[2:]) val = bytes.fromhex(valstr[2:])
elif valstr.startswith("base64:"):
val = base64.b64decode(valstr[7:])
elif valstr.lower() in {"t", "true", "yes"}: elif valstr.lower() in {"t", "true", "yes"}:
val = True val = True
elif valstr.lower() in {"f", "false", "no"}: elif valstr.lower() in {"f", "false", "no"}:
@@ -97,7 +103,7 @@ def pskToString(psk: bytes):
def stripnl(s): def stripnl(s):
"""Remove newlines from a string (and remove extra whitespace)""" """Remove newlines from a string (and remove extra whitespace)"""
s = str(s).replace("\n", " ") s = str(s).replace("\n", " ")
return ' '.join(s.split()) return " ".join(s.split())
def fixme(message): def fixme(message):
@@ -113,21 +119,31 @@ def catchAndIgnore(reason, closure):
logging.error(f"Exception thrown in {reason}: {ex}") logging.error(f"Exception thrown in {reason}: {ex}")
def findPorts(): def findPorts(eliminate_duplicates=False):
"""Find all ports that might have meshtastic devices """Find all ports that might have meshtastic devices
eliminate_duplicates will run the eliminate_duplicate_port() on the collection
Returns: Returns:
list -- a list of device paths list -- a list of device paths
""" """
l = list(map(lambda port: port.device, l = list(
filter(lambda port: port.vid is not None and port.vid not in blacklistVids, map(
serial.tools.list_ports.comports()))) lambda port: port.device,
filter(
lambda port: port.vid is not None and port.vid not in blacklistVids,
serial.tools.list_ports.comports(),
),
)
)
l.sort() l.sort()
if eliminate_duplicates:
l = eliminate_duplicate_port(l)
return l return l
class dotdict(dict): class dotdict(dict):
"""dot.notation access to dictionary attributes""" """dot.notation access to dictionary attributes"""
__getattr__ = dict.get __getattr__ = dict.get
__setattr__ = dict.__setitem__ __setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__ __delattr__ = dict.__delitem__
@@ -135,6 +151,7 @@ class dotdict(dict):
class Timeout: class Timeout:
"""Timeout class""" """Timeout class"""
def __init__(self, maxSecs=20): def __init__(self, maxSecs=20):
self.expireTime = 0 self.expireTime = 0
self.sleepInterval = 0.1 self.sleepInterval = 0.1
@@ -153,8 +170,60 @@ class Timeout:
time.sleep(self.sleepInterval) time.sleep(self.sleepInterval)
return False return False
def waitForAckNak(
self, acknowledgment, attrs=("receivedAck", "receivedNak", "receivedImplAck")
):
"""Block until an ACK or NAK has been received. Returns True if ACK or NAK has been received."""
self.reset()
while time.time() < self.expireTime:
if any(map(lambda a: getattr(acknowledgment, a, None), attrs)):
acknowledgment.reset()
return True
time.sleep(self.sleepInterval)
return False
class DeferredExecution(): def waitForTraceRoute(self, waitFactor, acknowledgment, attr="receivedTraceRoute"):
"""Block until traceroute response is received. Returns True if traceroute response has been received."""
self.expireTimeout *= waitFactor
self.reset()
while time.time() < self.expireTime:
if getattr(acknowledgment, attr, None):
acknowledgment.reset()
return True
time.sleep(self.sleepInterval)
return False
def waitForTelemetry(self, acknowledgment):
"""Block until telemetry response is received. Returns True if telemetry response has been received."""
self.reset()
while time.time() < self.expireTime:
if getattr(acknowledgment, "receivedTelemetry", 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):
"""initialize"""
self.receivedAck = False
self.receivedNak = False
self.receivedImplAck = False
self.receivedTraceRoute = False
self.receivedTelemetry = False
def reset(self):
"""reset"""
self.receivedAck = False
self.receivedNak = False
self.receivedImplAck = False
self.receivedTraceRoute = False
self.receivedTelemetry = False
class DeferredExecution:
"""A thread that accepts closures to run, and runs them as they are received""" """A thread that accepts closures to run, and runs them as they are received"""
def __init__(self, name=None): def __init__(self, name=None):
@@ -164,7 +233,7 @@ class DeferredExecution():
self.thread.start() self.thread.start()
def queueWork(self, runnable): def queueWork(self, runnable):
""" Queue up the work""" """Queue up the work"""
self.queue.put(runnable) self.queue.put(runnable)
def _run(self): def _run(self):
@@ -173,13 +242,15 @@ class DeferredExecution():
o = self.queue.get() o = self.queue.get()
o() o()
except: except:
logging.error(f"Unexpected error in deferred execution {sys.exc_info()[0]}") logging.error(
f"Unexpected error in deferred execution {sys.exc_info()[0]}"
)
print(traceback.format_exc()) print(traceback.format_exc())
def our_exit(message, return_value = 1): def our_exit(message, return_value=1):
"""Print the message and return a value. """Print the message and return a value.
return_value defaults to 1 (non-successful) return_value defaults to 1 (non-successful)
""" """
print(message) print(message)
sys.exit(return_value) sys.exit(return_value)
@@ -187,28 +258,36 @@ def our_exit(message, return_value = 1):
def support_info(): def support_info():
"""Print out info that helps troubleshooting of the cli.""" """Print out info that helps troubleshooting of the cli."""
print('') print("")
print('If having issues with meshtastic cli or python library') print("If having issues with meshtastic cli or python library")
print('or wish to make feature requests, visit:') print("or wish to make feature requests, visit:")
print('https://github.com/meshtastic/Meshtastic-python/issues') print("https://github.com/meshtastic/python/issues")
print('When adding an issue, be sure to include the following info:') print("When adding an issue, be sure to include the following info:")
print(f' System: {platform.system()}') print(f" System: {platform.system()}")
print(f' Platform: {platform.platform()}') print(f" Platform: {platform.platform()}")
print(f' Release: {platform.uname().release}') print(f" Release: {platform.uname().release}")
print(f' Machine: {platform.uname().machine}') print(f" Machine: {platform.uname().machine}")
print(f' Encoding (stdin): {sys.stdin.encoding}') print(f" Encoding (stdin): {sys.stdin.encoding}")
print(f' Encoding (stdout): {sys.stdout.encoding}') print(f" Encoding (stdout): {sys.stdout.encoding}")
the_version = pkg_resources.get_distribution("meshtastic").version the_version = pkg_resources.get_distribution("meshtastic").version
print(f' meshtastic: v{the_version}') pypi_version = check_if_newer_version()
print(f' Executable: {sys.argv[0]}') if pypi_version:
print(f' Python: {platform.python_version()} {platform.python_implementation()} {platform.python_compiler()}') print(
print('') f" meshtastic: v{the_version} (*** newer version v{pypi_version} available ***)"
print('Please add the output from the command: meshtastic --info') )
else:
print(f" meshtastic: v{the_version}")
print(f" Executable: {sys.argv[0]}")
print(
f" Python: {platform.python_version()} {platform.python_implementation()} {platform.python_compiler()}"
)
print("")
print("Please add the output from the command: meshtastic --info")
def remove_keys_from_dict(keys, adict): def remove_keys_from_dict(keys, adict):
"""Return a dictionary without some keys in it. """Return a dictionary without some keys in it.
Will removed nested keys. Will removed nested keys.
""" """
for key in keys: for key in keys:
try: try:
@@ -223,12 +302,12 @@ def remove_keys_from_dict(keys, adict):
def hexstr(barray): def hexstr(barray):
"""Print a string of hex digits""" """Print a string of hex digits"""
return ":".join(f'{x:02x}' for x in barray) return ":".join(f"{x:02x}" for x in barray)
def ipstr(barray): def ipstr(barray):
"""Print a string of ip digits""" """Print a string of ip digits"""
return ".".join(f'{x}' for x in barray) return ".".join(f"{x}" for x in barray)
def readnet_u16(p, offset): def readnet_u16(p, offset):
@@ -238,8 +317,8 @@ def readnet_u16(p, offset):
def convert_mac_addr(val): def convert_mac_addr(val):
"""Convert the base 64 encoded value to a mac address """Convert the base 64 encoded value to a mac address
val - base64 encoded value (ex: '/c0gFyhb')) val - base64 encoded value (ex: '/c0gFyhb'))
returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b') returns: a string formatted like a mac address (ex: 'fd:cd:20:17:28:5b')
""" """
if not re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", val): if not re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", val):
val_as_bytes = base64.b64decode(val) val_as_bytes = base64.b64decode(val)
@@ -250,21 +329,23 @@ def convert_mac_addr(val):
def snake_to_camel(a_string): def snake_to_camel(a_string):
"""convert snake_case to camelCase""" """convert snake_case to camelCase"""
# split underscore using split # split underscore using split
temp = a_string.split('_') temp = a_string.split("_")
# joining result # joining result
result = temp[0] + ''.join(ele.title() for ele in temp[1:]) result = temp[0] + "".join(ele.title() for ele in temp[1:])
return result return result
def camel_to_snake(a_string): def camel_to_snake(a_string):
"""convert camelCase to snake_case""" """convert camelCase to snake_case"""
return ''.join(['_'+i.lower() if i.isupper() else i for i in a_string]).lstrip('_') return "".join(["_" + i.lower() if i.isupper() else i for i in a_string]).lstrip(
"_"
)
def detect_supported_devices(): def detect_supported_devices():
"""detect supported devices""" """detect supported devices based on vendor id"""
system = platform.system() system = platform.system()
#print(f'system:{system}') # print(f'system:{system}')
possible_devices = set() possible_devices = set()
if system == "Linux": if system == "Linux":
@@ -272,86 +353,52 @@ def detect_supported_devices():
# linux: use lsusb # linux: use lsusb
# Bus 001 Device 091: ID 10c4:ea60 Silicon Labs CP210x UART Bridge # Bus 001 Device 091: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
_, lsusb_output = subprocess.getstatusoutput('lsusb') _, lsusb_output = subprocess.getstatusoutput("lsusb")
vids = get_unique_vendor_ids() vids = get_unique_vendor_ids()
for vid in vids: for vid in vids:
#print(f'looking for {vid}...') # print(f'looking for {vid}...')
search = f' {vid}:' search = f" {vid}:"
#print(f'search:"{search}"') # print(f'search:"{search}"')
if re.search(search, lsusb_output, re.MULTILINE): if re.search(search, lsusb_output, re.MULTILINE):
#print(f'Found vendor id that matches') # print(f'Found vendor id that matches')
devices = get_devices_with_vendor_id(vid) devices = get_devices_with_vendor_id(vid)
# check device id
for device in devices: for device in devices:
#print(f'device:{device} device.usb_product_id_in_hex:{device.usb_product_id_in_hex}') possible_devices.add(device)
if device.usb_product_id_in_hex:
search = f' {vid}:{device.usb_product_id_in_hex} '
#print(f'search:"{search}"')
if re.search(search, lsusb_output, re.MULTILINE):
# concatenate the devices with vendor id to possibles
possible_devices.add(device)
else:
# if there is a supported device witout a product id, then it
# might be a match... so, concatenate
possible_devices.add(device)
elif system == "Windows": elif system == "Windows":
# if windows, run Get-PnpDevice # if windows, run Get-PnpDevice
_, sp_output = subprocess.getstatusoutput('powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;' _, sp_output = subprocess.getstatusoutput(
'Get-PnpDevice -PresentOnly | Format-List"') 'powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;'
#print(f'sp_output:{sp_output}') 'Get-PnpDevice -PresentOnly | Format-List"'
)
# print(f'sp_output:{sp_output}')
vids = get_unique_vendor_ids() vids = get_unique_vendor_ids()
for vid in vids: for vid in vids:
#print(f'looking for {vid.upper()}...') # print(f'looking for {vid.upper()}...')
search = f'DeviceID.*{vid.upper()}&' search = f"DeviceID.*{vid.upper()}&"
#search = f'{vid.upper()}' # search = f'{vid.upper()}'
#print(f'search:"{search}"') # print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE): if re.search(search, sp_output, re.MULTILINE):
#print(f'Found vendor id that matches') # print(f'Found vendor id that matches')
devices = get_devices_with_vendor_id(vid) devices = get_devices_with_vendor_id(vid)
# check device id
for device in devices: for device in devices:
#print(f'device:{device} device.usb_product_id_in_hex:{device.usb_product_id_in_hex}') possible_devices.add(device)
if device.usb_product_id_in_hex:
search = f'DeviceID.*{vid.upper()}&PID_{device.usb_product_id_in_hex.upper()}'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
# concatenate the devices with vendor id to possibles
possible_devices.add(device)
# do a check to see if there is a Windows driver issue
if detect_windows_needs_driver(device, False):
print("WARNING: Need to install driver.")
else:
# if there is a supported device witout a product id, then it
# might be a match... so, concatenate
possible_devices.add(device)
elif system == "Darwin": elif system == "Darwin":
# run: system_profiler SPUSBDataType # run: system_profiler SPUSBDataType
# Note: If in boot mode, the 19003 reports same product ID as 5005. # Note: If in boot mode, the 19003 reports same product ID as 5005.
_, sp_output = subprocess.getstatusoutput('system_profiler SPUSBDataType') _, sp_output = subprocess.getstatusoutput("system_profiler SPUSBDataType")
vids = get_unique_vendor_ids() vids = get_unique_vendor_ids()
for vid in vids: for vid in vids:
#print(f'looking for {vid}...') # print(f'looking for {vid}...')
search = f'Vendor ID: 0x{vid}' search = f"Vendor ID: 0x{vid}"
#print(f'search:"{search}"') # print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE): if re.search(search, sp_output, re.MULTILINE):
#print(f'Found vendor id that matches') # print(f'Found vendor id that matches')
devices = get_devices_with_vendor_id(vid) devices = get_devices_with_vendor_id(vid)
# check device id
for device in devices: for device in devices:
#print(f'device:{device} device.usb_product_id_in_hex:{device.usb_product_id_in_hex}') possible_devices.add(device)
if device.usb_product_id_in_hex:
search = f'Product ID: 0x{device.usb_product_id_in_hex}'
#print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE):
# concatenate the devices with vendor id to possibles
possible_devices.add(device)
else:
# if there is a supported device witout a product id, then it
# might be a match... so, concatenate
possible_devices.add(device)
return possible_devices return possible_devices
@@ -361,7 +408,7 @@ def detect_windows_needs_driver(sd, print_reason=False):
if sd: if sd:
system = platform.system() system = platform.system()
#print(f'in detect_windows_needs_driver system:{system}') # print(f'in detect_windows_needs_driver system:{system}')
if system == "Windows": if system == "Windows":
# if windows, see if we can find a DeviceId with the vendor id # if windows, see if we can find a DeviceId with the vendor id
@@ -370,14 +417,191 @@ def detect_windows_needs_driver(sd, print_reason=False):
command += f"'*{sd.usb_vendor_id_in_hex.upper()}*'" command += f"'*{sd.usb_vendor_id_in_hex.upper()}*'"
command += ')} | Format-List"' command += ')} | Format-List"'
#print(f'command:{command}') # print(f'command:{command}')
_, sp_output = subprocess.getstatusoutput(command) _, sp_output = subprocess.getstatusoutput(command)
#print(f'sp_output:{sp_output}') # print(f'sp_output:{sp_output}')
search = f'CM_PROB_FAILED_INSTALL' search = f"CM_PROB_FAILED_INSTALL"
#print(f'search:"{search}"') # print(f'search:"{search}"')
if re.search(search, sp_output, re.MULTILINE): if re.search(search, sp_output, re.MULTILINE):
need_to_install_driver = True need_to_install_driver = True
# if the want to see the reason # if the want to see the reason
if print_reason: if print_reason:
print(sp_output) print(sp_output)
return need_to_install_driver return need_to_install_driver
def eliminate_duplicate_port(ports):
"""Sometimes we detect 2 serial ports, but we really only need to use one of the ports.
ports is a list of ports
return a list with a single port to use, if it meets the duplicate port conditions
examples:
Ports: ['/dev/cu.usbserial-1430', '/dev/cu.wchusbserial1430'] => ['/dev/cu.wchusbserial1430']
Ports: ['/dev/cu.usbmodem11301', '/dev/cu.wchusbserial11301'] => ['/dev/cu.wchusbserial11301']
Ports: ['/dev/cu.SLAB_USBtoUART', '/dev/cu.usbserial-0001'] => ['/dev/cu.usbserial-0001']
"""
new_ports = []
if len(ports) != 2:
new_ports = ports
else:
ports.sort()
if "usbserial" in ports[0] and "wchusbserial" in ports[1]:
first = ports[0].replace("usbserial-", "")
second = ports[1].replace("wchusbserial", "")
if first == second:
new_ports.append(ports[1])
elif "usbmodem" in ports[0] and "wchusbserial" in ports[1]:
first = ports[0].replace("usbmodem", "")
second = ports[1].replace("wchusbserial", "")
if first == second:
new_ports.append(ports[1])
elif "SLAB_USBtoUART" in ports[0] and "usbserial" in ports[1]:
new_ports.append(ports[1])
else:
new_ports = ports
return new_ports
def is_windows11():
"""Detect if Windows 11"""
is_win11 = 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:
if int(patch) >= 22000:
is_win11 = True
except Exception as e:
print(f"problem detecting win11 e:{e}")
return is_win11
def get_unique_vendor_ids():
"""Return a set of unique vendor ids"""
vids = set()
for d in supported_devices:
if d.usb_vendor_id_in_hex:
vids.add(d.usb_vendor_id_in_hex)
return vids
def get_devices_with_vendor_id(vid):
"""Return a set of unique devices with the vendor id"""
sd = set()
for d in supported_devices:
if d.usb_vendor_id_in_hex == vid:
sd.add(d)
return sd
def active_ports_on_supported_devices(sds, eliminate_duplicates=False):
"""Return a set of active ports based on the supplied supported devices"""
ports = set()
baseports = set()
system = platform.system()
# figure out what possible base ports there are
for d in sds:
if system == "Linux":
baseports.add(d.baseport_on_linux)
elif system == "Darwin":
baseports.add(d.baseport_on_mac)
elif system == "Windows":
baseports.add(d.baseport_on_windows)
for bp in baseports:
if system == "Linux":
# see if we have any devices (ignoring any stderr output)
command = f"ls -al /dev/{bp}* 2> /dev/null"
# print(f'command:{command}')
_, ls_output = subprocess.getstatusoutput(command)
# print(f'ls_output:{ls_output}')
# if we got output, there are ports
if len(ls_output) > 0:
# print('got output')
# for each line of output
lines = ls_output.split("\n")
# print(f'lines:{lines}')
for line in lines:
parts = line.split(" ")
# print(f'parts:{parts}')
port = parts[-1]
# print(f'port:{port}')
ports.add(port)
elif system == "Darwin":
# see if we have any devices (ignoring any stderr output)
command = f"ls -al /dev/{bp}* 2> /dev/null"
# print(f'command:{command}')
_, ls_output = subprocess.getstatusoutput(command)
# print(f'ls_output:{ls_output}')
# if we got output, there are ports
if len(ls_output) > 0:
# print('got output')
# for each line of output
lines = ls_output.split("\n")
# print(f'lines:{lines}')
for line in lines:
parts = line.split(" ")
# print(f'parts:{parts}')
port = parts[-1]
# print(f'port:{port}')
ports.add(port)
elif system == "Windows":
# for each device in supported devices found
for d in sds:
# find the port(s)
com_ports = detect_windows_port(d)
# print(f'com_ports:{com_ports}')
# add all ports
for com_port in com_ports:
ports.add(com_port)
if eliminate_duplicates:
ports = eliminate_duplicate_port(list(ports))
ports.sort()
ports = set(ports)
return ports
def detect_windows_port(sd):
"""detect if Windows port"""
ports = set()
if sd:
system = platform.system()
if system == "Windows":
command = (
'powershell.exe "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8;'
"Get-PnpDevice -PresentOnly | Where-Object{ ($_.DeviceId -like "
)
command += f"'*{sd.usb_vendor_id_in_hex.upper()}*'"
command += ')} | Format-List"'
# print(f'command:{command}')
_, sp_output = subprocess.getstatusoutput(command)
# print(f'sp_output:{sp_output}')
p = re.compile(r"\(COM(.*)\)")
for x in p.findall(sp_output):
# print(f'x:{x}')
ports.add(f"COM{x}")
return ports
def check_if_newer_version():
"""Check pip to see if we are running the latest version."""
pypi_version = None
try:
url = "https://pypi.org/pypi/meshtastic/json"
data = requests.get(url, timeout=5).json()
pypi_version = data["info"]["version"]
except Exception:
pass
act_version = pkg_resources.get_distribution("meshtastic").version
if pypi_version and pkg_resources.parse_version(
pypi_version
) <= pkg_resources.parse_version(act_version):
return None
return pypi_version

38
meshtastic/xmodem_pb2.py Normal file
View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: meshtastic/xmodem.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 message as _message
from google.protobuf import reflection as _reflection
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17meshtastic/xmodem.proto\"\xab\x01\n\x06XModem\x12 \n\x07\x63ontrol\x18\x01 \x01(\x0e\x32\x0f.XModem.Control\x12\x0b\n\x03seq\x18\x02 \x01(\r\x12\r\n\x05\x63rc16\x18\x03 \x01(\r\x12\x0e\n\x06\x62uffer\x18\x04 \x01(\x0c\"S\n\x07\x43ontrol\x12\x07\n\x03NUL\x10\x00\x12\x07\n\x03SOH\x10\x01\x12\x07\n\x03STX\x10\x02\x12\x07\n\x03\x45OT\x10\x04\x12\x07\n\x03\x41\x43K\x10\x06\x12\x07\n\x03NAK\x10\x15\x12\x07\n\x03\x43\x41N\x10\x18\x12\t\n\x05\x43TRLZ\x10\x1a\x42\x61\n\x13\x63om.geeksville.meshB\x0cXmodemProtosZ\"github.com/meshtastic/go/generated\xaa\x02\x14Meshtastic.Protobufs\xba\x02\x00\x62\x06proto3')
_XMODEM = DESCRIPTOR.message_types_by_name['XModem']
_XMODEM_CONTROL = _XMODEM.enum_types_by_name['Control']
XModem = _reflection.GeneratedProtocolMessageType('XModem', (_message.Message,), {
'DESCRIPTOR' : _XMODEM,
'__module__' : 'meshtastic.xmodem_pb2'
# @@protoc_insertion_point(class_scope:XModem)
})
_sym_db.RegisterMessage(XModem)
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\023com.geeksville.meshB\014XmodemProtosZ\"github.com/meshtastic/go/generated\252\002\024Meshtastic.Protobufs\272\002\000'
_XMODEM._serialized_start=28
_XMODEM._serialized_end=199
_XMODEM_CONTROL._serialized_start=116
_XMODEM_CONTROL._serialized_end=199
# @@protoc_insertion_point(module_scope)

1
proto

Submodule proto deleted from 792027be93

1
protobufs Submodule

Submodule protobufs added at 468ff2e245

View File

@@ -7,6 +7,7 @@ pyqrcode
tabulate tabulate
timeago timeago
webencodings webencodings
requests
pyparsing pyparsing
twine twine
autopep8 autopep8

View File

@@ -1,6 +1,7 @@
# Note: you shouldn't need to run this script manually. It is run implicitly by the pip3 install command. # Note: you shouldn't need to run this script manually. It is run implicitly by the pip3 install command.
import pathlib import pathlib
from setuptools import setup from setuptools import setup
# The directory containing this file # The directory containing this file
@@ -12,37 +13,44 @@ with open("README.md", "r") as fh:
# This call to setup() does all the work # This call to setup() does all the work
setup( setup(
name="meshtastic", name="meshtastic",
version="1.2.80", version="2.2.1",
description="Python API & client shell for talking to Meshtastic devices", description="Python API & client shell for talking to Meshtastic devices",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
url="https://github.com/meshtastic/Meshtastic-python", url="https://github.com/meshtastic/python",
author="Kevin Hester", author="Meshtastic Developers",
author_email="kevinh@geeksville.com", author_email="contact@meshtastic.org",
license="MIT", license="GPL-3.0-only",
classifiers=[ classifiers=[
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Programming Language :: Python :: 3", "Development Status :: 4 - Beta",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
], ],
packages=["meshtastic"], packages=["meshtastic"],
include_package_data=True, include_package_data=True,
install_requires=["pyserial>=3.4", "protobuf>=3.13.0", install_requires=[
"pypubsub>=4.0.3", "dotmap>=1.3.14", "pexpect>=4.6.0", "pyqrcode>=1.2.1", "pyserial>=3.4",
"tabulate>=0.8.9", "timeago>=1.0.15", "pyyaml", "protobuf>=3.13.0",
"pygatt>=4.0.5 ; platform_system=='Linux'"], "requests>=2.25.0",
extras_require={ "pypubsub>=4.0.3",
'tunnel': ["pytap2>=2.0.0"] "dotmap>=1.3.14",
}, "pexpect>=4.6.0",
python_requires='>=3.6', "pyqrcode>=1.2.1",
"tabulate>=0.8.9",
"timeago>=1.0.15",
"pyyaml",
"pygatt>=4.0.5 ; platform_system=='Linux'",
],
extras_require={"tunnel": ["pytap2>=2.0.0"]},
python_requires=">=3.7",
entry_points={ entry_points={
"console_scripts": [ "console_scripts": [
"meshtastic=meshtastic.__main__:main", "meshtastic=meshtastic.__main__:main",
"mesh-tunnel=meshtastic.__main__:tunnelMain [tunnel]" "mesh-tunnel=meshtastic.__main__:tunnelMain [tunnel]",
] ]
}, },
) )

View File

@@ -1,7 +1,7 @@
readme.txt for single standalone executable zip files that can be readme.txt for single standalone executable zip files that can be
downloaded from https://github.com/meshtastic/Meshtastic-python/releases downloaded from https://github.com/meshtastic/python/releases
If you do not want to install python and/or the python libraries, you can download one of these If you do not want to install python and/or the python libraries, you can download one of these
zip files to run the Meshtastic command line interface (CLI) as a standalone executable. files to run the Meshtastic command line interface (CLI) as a standalone executable.
See https://meshtastic.org/docs/software/python/python-standalone for more info. See https://meshtastic.org/docs/software/python/python-standalone for more info.

View File

@@ -1,11 +1,14 @@
import datetime
import logging
import sys import sys
import meshtastic
import datetime, logging
from pubsub import pub from pubsub import pub
#logging.basicConfig(level=logging.DEBUG) import meshtastic
# logging.basicConfig(level=logging.DEBUG)
print(str(datetime.datetime.now()) + ": start") print(str(datetime.datetime.now()) + ": start")
interface = meshtastic.TCPInterface(sys.argv[1]) interface = meshtastic.TCPInterface(sys.argv[1])
print(str(datetime.datetime.now()) + ": middle") print(str(datetime.datetime.now()) + ": middle")
interface.close() interface.close()
print(str(datetime.datetime.now()) + ": after close") print(str(datetime.datetime.now()) + ": after close")

Some files were not shown because too many files have changed in this diff Show More