mtjk 2.7.8.post1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- meshtastic/.gitignore +1 -0
- meshtastic/__init__.py +638 -0
- meshtastic/__main__.py +3337 -0
- meshtastic/analysis/__init__.py +1 -0
- meshtastic/analysis/__main__.py +577 -0
- meshtastic/ble_interface.py +75 -0
- meshtastic/host_port.py +183 -0
- meshtastic/interfaces/ble/__init__.py +67 -0
- meshtastic/interfaces/ble/client.py +1260 -0
- meshtastic/interfaces/ble/compatibility_service.py +691 -0
- meshtastic/interfaces/ble/connection.py +1874 -0
- meshtastic/interfaces/ble/constants.py +266 -0
- meshtastic/interfaces/ble/coordination.py +468 -0
- meshtastic/interfaces/ble/discovery.py +811 -0
- meshtastic/interfaces/ble/errors.py +207 -0
- meshtastic/interfaces/ble/gating.py +802 -0
- meshtastic/interfaces/ble/interface.py +3313 -0
- meshtastic/interfaces/ble/lifecycle_compat_service.py +1603 -0
- meshtastic/interfaces/ble/lifecycle_controller_runtime.py +358 -0
- meshtastic/interfaces/ble/lifecycle_disconnect_runtime.py +558 -0
- meshtastic/interfaces/ble/lifecycle_ownership_runtime.py +943 -0
- meshtastic/interfaces/ble/lifecycle_primitives.py +686 -0
- meshtastic/interfaces/ble/lifecycle_receive_runtime.py +552 -0
- meshtastic/interfaces/ble/lifecycle_service.py +21 -0
- meshtastic/interfaces/ble/lifecycle_shutdown_runtime.py +608 -0
- meshtastic/interfaces/ble/management_compat_service.py +739 -0
- meshtastic/interfaces/ble/management_runtime.py +1096 -0
- meshtastic/interfaces/ble/management_service.py +22 -0
- meshtastic/interfaces/ble/notifications.py +1023 -0
- meshtastic/interfaces/ble/policies.py +328 -0
- meshtastic/interfaces/ble/receive_compat_service.py +661 -0
- meshtastic/interfaces/ble/receive_service.py +1164 -0
- meshtastic/interfaces/ble/reconnection.py +641 -0
- meshtastic/interfaces/ble/runner.py +727 -0
- meshtastic/interfaces/ble/state.py +418 -0
- meshtastic/interfaces/ble/utils.py +394 -0
- meshtastic/mesh_interface.py +2568 -0
- meshtastic/mesh_interface_runtime/__init__.py +26 -0
- meshtastic/mesh_interface_runtime/flows.py +581 -0
- meshtastic/mesh_interface_runtime/node_data.py +108 -0
- meshtastic/mesh_interface_runtime/node_presentation.py +214 -0
- meshtastic/mesh_interface_runtime/node_view.py +647 -0
- meshtastic/mesh_interface_runtime/receive_pipeline.py +786 -0
- meshtastic/mesh_interface_runtime/request_wait.py +544 -0
- meshtastic/mesh_interface_runtime/send_pipeline.py +880 -0
- meshtastic/mt_config.py +189 -0
- meshtastic/node.py +1641 -0
- meshtastic/node_runtime/__init__.py +36 -0
- meshtastic/node_runtime/channel_export_runtime.py +236 -0
- meshtastic/node_runtime/channel_lookup_runtime.py +113 -0
- meshtastic/node_runtime/channel_normalization_runtime.py +64 -0
- meshtastic/node_runtime/channel_presentation_runtime.py +145 -0
- meshtastic/node_runtime/channel_request_runtime.py +251 -0
- meshtastic/node_runtime/content_runtime.py +566 -0
- meshtastic/node_runtime/response_runtime.py +420 -0
- meshtastic/node_runtime/settings_runtime/__init__.py +20 -0
- meshtastic/node_runtime/settings_runtime/admin.py +322 -0
- meshtastic/node_runtime/settings_runtime/config_runtime.py +111 -0
- meshtastic/node_runtime/settings_runtime/message.py +124 -0
- meshtastic/node_runtime/settings_runtime/owner.py +104 -0
- meshtastic/node_runtime/settings_runtime/response.py +140 -0
- meshtastic/node_runtime/seturl/__init__.py +47 -0
- meshtastic/node_runtime/seturl/cache.py +180 -0
- meshtastic/node_runtime/seturl/context.py +31 -0
- meshtastic/node_runtime/seturl/coordinator.py +141 -0
- meshtastic/node_runtime/seturl/execution.py +251 -0
- meshtastic/node_runtime/seturl/helpers.py +23 -0
- meshtastic/node_runtime/seturl/parser.py +84 -0
- meshtastic/node_runtime/seturl/planner.py +302 -0
- meshtastic/node_runtime/seturl/rollback.py +364 -0
- meshtastic/node_runtime/seturl_runtime.py +36 -0
- meshtastic/node_runtime/shared.py +48 -0
- meshtastic/node_runtime/transport_runtime/__init__.py +25 -0
- meshtastic/node_runtime/transport_runtime/ack.py +155 -0
- meshtastic/node_runtime/transport_runtime/admin.py +85 -0
- meshtastic/node_runtime/transport_runtime/channel.py +307 -0
- meshtastic/node_runtime/transport_runtime/position_time.py +140 -0
- meshtastic/node_runtime/transport_runtime/session.py +79 -0
- meshtastic/ota.py +270 -0
- meshtastic/powermon/__init__.py +81 -0
- meshtastic/powermon/constants.py +13 -0
- meshtastic/powermon/power_supply.py +233 -0
- meshtastic/powermon/ppk2.py +342 -0
- meshtastic/powermon/riden.py +117 -0
- meshtastic/powermon/sim.py +35 -0
- meshtastic/powermon/stress.py +231 -0
- meshtastic/protobuf/__init__.py +0 -0
- meshtastic/protobuf/admin_pb2.py +62 -0
- meshtastic/protobuf/admin_pb2.pyi +1122 -0
- meshtastic/protobuf/apponly_pb2.py +28 -0
- meshtastic/protobuf/apponly_pb2.pyi +52 -0
- meshtastic/protobuf/atak_pb2.py +40 -0
- meshtastic/protobuf/atak_pb2.pyi +477 -0
- meshtastic/protobuf/cannedmessages_pb2.py +26 -0
- meshtastic/protobuf/cannedmessages_pb2.pyi +33 -0
- meshtastic/protobuf/channel_pb2.py +34 -0
- meshtastic/protobuf/channel_pb2.pyi +234 -0
- meshtastic/protobuf/clientonly_pb2.py +28 -0
- meshtastic/protobuf/clientonly_pb2.pyi +101 -0
- meshtastic/protobuf/config_pb2.py +97 -0
- meshtastic/protobuf/config_pb2.pyi +1913 -0
- meshtastic/protobuf/connection_status_pb2.py +36 -0
- meshtastic/protobuf/connection_status_pb2.pyi +228 -0
- meshtastic/protobuf/device_ui_pb2.py +42 -0
- meshtastic/protobuf/device_ui_pb2.pyi +649 -0
- meshtastic/protobuf/deviceonly_pb2.py +52 -0
- meshtastic/protobuf/deviceonly_pb2.pyi +459 -0
- meshtastic/protobuf/interdevice_pb2.py +30 -0
- meshtastic/protobuf/interdevice_pb2.pyi +105 -0
- meshtastic/protobuf/localonly_pb2.py +30 -0
- meshtastic/protobuf/localonly_pb2.pyi +244 -0
- meshtastic/protobuf/mesh_pb2.py +127 -0
- meshtastic/protobuf/mesh_pb2.pyi +4021 -0
- meshtastic/protobuf/module_config_pb2.py +76 -0
- meshtastic/protobuf/module_config_pb2.pyi +1466 -0
- meshtastic/protobuf/mqtt_pb2.py +30 -0
- meshtastic/protobuf/mqtt_pb2.pyi +155 -0
- meshtastic/protobuf/nanopb_pb2.py +35 -0
- meshtastic/protobuf/nanopb_pb2.pyi +324 -0
- meshtastic/protobuf/paxcount_pb2.py +26 -0
- meshtastic/protobuf/paxcount_pb2.pyi +45 -0
- meshtastic/protobuf/portnums_pb2.py +26 -0
- meshtastic/protobuf/portnums_pb2.pyi +444 -0
- meshtastic/protobuf/powermon_pb2.py +32 -0
- meshtastic/protobuf/powermon_pb2.pyi +221 -0
- meshtastic/protobuf/remote_hardware_pb2.py +28 -0
- meshtastic/protobuf/remote_hardware_pb2.pyi +126 -0
- meshtastic/protobuf/rtttl_pb2.py +26 -0
- meshtastic/protobuf/rtttl_pb2.pyi +33 -0
- meshtastic/protobuf/storeforward_pb2.py +34 -0
- meshtastic/protobuf/storeforward_pb2.pyi +345 -0
- meshtastic/protobuf/telemetry_pb2.py +48 -0
- meshtastic/protobuf/telemetry_pb2.pyi +1498 -0
- meshtastic/protobuf/xmodem_pb2.py +28 -0
- meshtastic/protobuf/xmodem_pb2.pyi +67 -0
- meshtastic/py.typed +0 -0
- meshtastic/remote_hardware.py +500 -0
- meshtastic/serial_interface.py +279 -0
- meshtastic/slog/__init__.py +5 -0
- meshtastic/slog/arrow.py +445 -0
- meshtastic/slog/slog.py +840 -0
- meshtastic/stream_interface.py +558 -0
- meshtastic/supported_device.py +353 -0
- meshtastic/tcp_interface.py +670 -0
- meshtastic/test.py +575 -0
- meshtastic/tests/__init__.py +0 -0
- meshtastic/tests/api_baselines/api_baseline.json +168 -0
- meshtastic/tests/api_baselines/api_baseline_master.json +152 -0
- meshtastic/tests/cli_test_utils.py +227 -0
- meshtastic/tests/conftest.py +609 -0
- meshtastic/tests/seturl/__init__.py +1 -0
- meshtastic/tests/seturl/conftest.py +228 -0
- meshtastic/tests/seturl/test_cache_manager.py +276 -0
- meshtastic/tests/seturl/test_coordinator.py +83 -0
- meshtastic/tests/seturl/test_execution_engine.py +349 -0
- meshtastic/tests/seturl/test_execution_state.py +74 -0
- meshtastic/tests/seturl/test_parser.py +151 -0
- meshtastic/tests/seturl/test_planners.py +246 -0
- meshtastic/tests/seturl/test_rollback_engine.py +448 -0
- meshtastic/tests/slog-test-input/power.feather +0 -0
- meshtastic/tests/slog-test-input/raw.txt +349 -0
- meshtastic/tests/slog-test-input/slog.feather +0 -0
- meshtastic/tests/test_analysis.py +729 -0
- meshtastic/tests/test_api_baseline_comparison.py +616 -0
- meshtastic/tests/test_api_compat.py +141 -0
- meshtastic/tests/test_api_contract_snapshot.py +809 -0
- meshtastic/tests/test_ble_compat_services_coverage.py +1309 -0
- meshtastic/tests/test_ble_connection_runtime.py +82 -0
- meshtastic/tests/test_ble_coordination_coverage.py +1235 -0
- meshtastic/tests/test_ble_errors_coverage.py +530 -0
- meshtastic/tests/test_ble_init.py +204 -0
- meshtastic/tests/test_ble_interface_compat_coverage.py +275 -0
- meshtastic/tests/test_ble_lifecycle.py +2713 -0
- meshtastic/tests/test_ble_reconnection_coverage.py +1824 -0
- meshtastic/tests/test_ble_runner_coverage.py +1307 -0
- meshtastic/tests/test_content_runtime_additional.py +507 -0
- meshtastic/tests/test_examples.py +183 -0
- meshtastic/tests/test_fallback_dotmap.py +85 -0
- meshtastic/tests/test_import_compatibility.py +412 -0
- meshtastic/tests/test_init.py +513 -0
- meshtastic/tests/test_int.py +54 -0
- meshtastic/tests/test_main.py +4984 -0
- meshtastic/tests/test_main_coverage.py +1316 -0
- meshtastic/tests/test_main_specific_lines.py +667 -0
- meshtastic/tests/test_mesh_interface.py +4576 -0
- meshtastic/tests/test_mesh_interface_behavioral_compat.py +1827 -0
- meshtastic/tests/test_mesh_interface_traffic_management.py +26 -0
- meshtastic/tests/test_meshtasticd_ci.py +144 -0
- meshtastic/tests/test_meshtasticd_multinode_ci.py +599 -0
- meshtastic/tests/test_meshtasticd_tcp_interface_ci.py +295 -0
- meshtastic/tests/test_mt_config.py +301 -0
- meshtastic/tests/test_node.py +3234 -0
- meshtastic/tests/test_node_behavioral_compat.py +756 -0
- meshtastic/tests/test_node_runtime_channel_export.py +576 -0
- meshtastic/tests/test_node_runtime_channel_lookup.py +453 -0
- meshtastic/tests/test_node_runtime_channel_normalization.py +311 -0
- meshtastic/tests/test_node_runtime_channel_presentation.py +234 -0
- meshtastic/tests/test_node_runtime_channel_request.py +339 -0
- meshtastic/tests/test_node_runtime_content.py +1124 -0
- meshtastic/tests/test_node_runtime_response.py +765 -0
- meshtastic/tests/test_node_runtime_settings.py +1329 -0
- meshtastic/tests/test_node_runtime_transport.py +1287 -0
- meshtastic/tests/test_node_view.py +744 -0
- meshtastic/tests/test_ota.py +722 -0
- meshtastic/tests/test_powermon_power_supply.py +271 -0
- meshtastic/tests/test_powermon_ppk2.py +287 -0
- meshtastic/tests/test_powermon_ppk2_coverage.py +1447 -0
- meshtastic/tests/test_powermon_riden.py +172 -0
- meshtastic/tests/test_powermon_stress.py +273 -0
- meshtastic/tests/test_receive_pipeline.py +1223 -0
- meshtastic/tests/test_remote_hardware.py +304 -0
- meshtastic/tests/test_response_handler_compat.py +71 -0
- meshtastic/tests/test_runtime_coverage.py +1283 -0
- meshtastic/tests/test_send_pipeline.py +1339 -0
- meshtastic/tests/test_serial_interface.py +156 -0
- meshtastic/tests/test_showNodes_favorite.py +239 -0
- meshtastic/tests/test_slog_arrow.py +359 -0
- meshtastic/tests/test_slog_coverage.py +1096 -0
- meshtastic/tests/test_slog_power_logger.py +481 -0
- meshtastic/tests/test_smoke1.py +1230 -0
- meshtastic/tests/test_smoke2.py +53 -0
- meshtastic/tests/test_smoke_wifi.py +26 -0
- meshtastic/tests/test_smokevirt.py +792 -0
- meshtastic/tests/test_stream_interface.py +506 -0
- meshtastic/tests/test_stream_interface_coverage.py +912 -0
- meshtastic/tests/test_supported_device.py +584 -0
- meshtastic/tests/test_tcp_interface.py +893 -0
- meshtastic/tests/test_tcp_interface_coverage.py +1348 -0
- meshtastic/tests/test_tunnel.py +558 -0
- meshtastic/tests/test_util.py +1583 -0
- meshtastic/tests/test_util_windows_port.py +41 -0
- meshtastic/tests/test_version.py +155 -0
- meshtastic/tests/test_write_bytes_investigation.py +370 -0
- meshtastic/tunnel.py +508 -0
- meshtastic/util.py +1467 -0
- meshtastic/version.py +39 -0
- mtjk-2.7.8.post1.dist-info/METADATA +202 -0
- mtjk-2.7.8.post1.dist-info/RECORD +241 -0
- mtjk-2.7.8.post1.dist-info/WHEEL +4 -0
- mtjk-2.7.8.post1.dist-info/entry_points.txt +5 -0
- mtjk-2.7.8.post1.dist-info/licenses/LICENSE.md +232 -0
meshtastic/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__pycache__
|
meshtastic/__init__.py
ADDED
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
"""# A library for the Meshtastic Client API.
|
|
2
|
+
|
|
3
|
+
Primary interfaces: SerialInterface, TCPInterface, BLEInterface
|
|
4
|
+
|
|
5
|
+
Install with pip: "[pip3 install mtjk](https://pypi.org/project/mtjk/)"
|
|
6
|
+
|
|
7
|
+
Source code on [github](https://github.com/jeremiah-k/meshtastic-python)
|
|
8
|
+
|
|
9
|
+
notable properties of interface classes:
|
|
10
|
+
|
|
11
|
+
- `nodes` - The database of received nodes. Includes always up-to-date location and username information for each
|
|
12
|
+
node in the mesh. This is a read-only datastructure.
|
|
13
|
+
- `nodesByNum` - like "nodes" but keyed by nodeNum instead of nodeId. As such, includes "unknown" nodes which haven't seen a User packet yet
|
|
14
|
+
- `myInfo` & `metadata` - Contain read-only information about the local radio device (software version, hardware version, etc)
|
|
15
|
+
- `localNode` - Pointer to a node object for the local node
|
|
16
|
+
|
|
17
|
+
notable properties of nodes:
|
|
18
|
+
|
|
19
|
+
- `localConfig` - Current radio settings, can be written to the radio with the `writeConfig` method.
|
|
20
|
+
- `moduleConfig` - Current module settings, can be written to the radio with the `writeConfig` method.
|
|
21
|
+
- `channels` - The node's channels, keyed by index.
|
|
22
|
+
|
|
23
|
+
# Published PubSub topics
|
|
24
|
+
|
|
25
|
+
We use a [publish-subscribe](https://pypubsub.readthedocs.io/en/v4.0.3/) model to communicate asynchronous events. Available
|
|
26
|
+
topics:
|
|
27
|
+
|
|
28
|
+
- `meshtastic.connection.established` - published once we've successfully connected to the radio and downloaded the node DB
|
|
29
|
+
- `meshtastic.connection.lost` - published once we've lost our link to the radio
|
|
30
|
+
- `meshtastic.receive.text(packet)` - delivers a received packet as a dictionary, if you only care about a particular
|
|
31
|
+
type of packet, you should subscribe to the full topic name. If you want to see all packets, simply subscribe to "meshtastic.receive".
|
|
32
|
+
- `meshtastic.receive.position(packet)`
|
|
33
|
+
- `meshtastic.receive.user(packet)`
|
|
34
|
+
- `meshtastic.receive.data.portnum(packet)` (where portnum is an integer or well known PortNum enum)
|
|
35
|
+
- `meshtastic.node.updated(node = NodeInfo)` - published when a node in the DB changes (appears, location changed, username changed, etc...)
|
|
36
|
+
- `meshtastic.log.line(line)` - a raw unparsed log line from the radio
|
|
37
|
+
- `meshtastic.clientNotification(notification, interface) - a ClientNotification sent from the radio
|
|
38
|
+
|
|
39
|
+
We receive position, user, or data packets from the mesh. You probably only care about `meshtastic.receive.data`. The first argument for
|
|
40
|
+
that publish will be the packet. Text or binary data packets (from `sendData` or `sendText`) will both arrive this way. If you print packet
|
|
41
|
+
you'll see the fields in the dictionary. `decoded.data.payload` will contain the raw bytes that were sent. If the packet was sent with
|
|
42
|
+
`sendText`, `decoded.data.text` will **also** be populated with the decoded string. For ASCII these two strings will be the same, but for
|
|
43
|
+
unicode scripts they can be different.
|
|
44
|
+
|
|
45
|
+
# Example Usage
|
|
46
|
+
```
|
|
47
|
+
import meshtastic
|
|
48
|
+
import meshtastic.serial_interface
|
|
49
|
+
from pubsub import pub
|
|
50
|
+
|
|
51
|
+
def onReceive(packet, interface): # called when a packet arrives
|
|
52
|
+
print(f"Received: {packet}")
|
|
53
|
+
|
|
54
|
+
def onConnection(interface, topic=pub.AUTO_TOPIC): # called when we (re)connect to the radio
|
|
55
|
+
# defaults to broadcast, specify a destination ID if you wish
|
|
56
|
+
interface.sendText("hello mesh")
|
|
57
|
+
|
|
58
|
+
pub.subscribe(onReceive, "meshtastic.receive")
|
|
59
|
+
pub.subscribe(onConnection, "meshtastic.connection.established")
|
|
60
|
+
# By default will try to find a meshtastic device, otherwise provide a device path like /dev/ttyUSB0
|
|
61
|
+
interface = meshtastic.serial_interface.SerialInterface()
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
# ruff: noqa: F401
|
|
67
|
+
|
|
68
|
+
import copy as _copy
|
|
69
|
+
import logging
|
|
70
|
+
from importlib import import_module as _import_module
|
|
71
|
+
from typing import Any as _Any
|
|
72
|
+
from typing import Callable as _Callable
|
|
73
|
+
from typing import NamedTuple as _NamedTuple
|
|
74
|
+
from typing import TypeGuard as _TypeGuard
|
|
75
|
+
from typing import cast as _cast
|
|
76
|
+
|
|
77
|
+
from google.protobuf.json_format import MessageToJson
|
|
78
|
+
|
|
79
|
+
from meshtastic.node import Node
|
|
80
|
+
from meshtastic.util import (
|
|
81
|
+
DeferredExecution,
|
|
82
|
+
Timeout,
|
|
83
|
+
catchAndIgnore,
|
|
84
|
+
stripnl,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
from . import util
|
|
88
|
+
from .protobuf import (
|
|
89
|
+
admin_pb2,
|
|
90
|
+
apponly_pb2,
|
|
91
|
+
channel_pb2,
|
|
92
|
+
config_pb2,
|
|
93
|
+
mesh_pb2,
|
|
94
|
+
mqtt_pb2,
|
|
95
|
+
paxcount_pb2,
|
|
96
|
+
portnums_pb2,
|
|
97
|
+
powermon_pb2,
|
|
98
|
+
remote_hardware_pb2,
|
|
99
|
+
storeforward_pb2,
|
|
100
|
+
telemetry_pb2,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
pub = _cast(_Any, _import_module("pubsub.pub"))
|
|
104
|
+
|
|
105
|
+
# Keep this module aligned with historical master behavior by intentionally not
|
|
106
|
+
# defining __all__. Public names remain available as module attributes.
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def __getattr__(name: str) -> _Any:
|
|
110
|
+
"""Provide lazy access to legacy module attributes.
|
|
111
|
+
|
|
112
|
+
When the attribute "serial" is requested, import the third-party pyserial
|
|
113
|
+
module, cache it on the module globals as "serial", and return it. For any
|
|
114
|
+
other attribute, raise AttributeError.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
name : str
|
|
119
|
+
Attribute name being accessed.
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
_Any
|
|
124
|
+
The resolved module object for the requested legacy attribute
|
|
125
|
+
(e.g., the third-party pyserial module for "serial").
|
|
126
|
+
|
|
127
|
+
Raises
|
|
128
|
+
------
|
|
129
|
+
AttributeError
|
|
130
|
+
If the requested attribute is not provided by this lazy loader.
|
|
131
|
+
"""
|
|
132
|
+
# COMPAT_STABLE_SHIM: preserve historical `meshtastic.serial` module access.
|
|
133
|
+
if name == "serial":
|
|
134
|
+
# Keep historical `meshtastic.serial` access to the third-party
|
|
135
|
+
# pyserial module as exposed on master.
|
|
136
|
+
serial_module = _import_module("serial")
|
|
137
|
+
# Cache in module namespace so subsequent accesses bypass __getattr__
|
|
138
|
+
globals()["serial"] = serial_module
|
|
139
|
+
return serial_module
|
|
140
|
+
raise AttributeError( # noqa: TRY003
|
|
141
|
+
f"module {__name__!r} has no attribute {name!r}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# Note: To follow PEP224, comments should be after the module variable.
|
|
146
|
+
|
|
147
|
+
LOCAL_ADDR = "^local"
|
|
148
|
+
"""A special ID that means the local node"""
|
|
149
|
+
|
|
150
|
+
BROADCAST_NUM: int = 0xFFFFFFFF
|
|
151
|
+
"""if using 8 bit nodenums this will be shortened on the target"""
|
|
152
|
+
|
|
153
|
+
BROADCAST_ADDR = "^all"
|
|
154
|
+
"""A special ID that means broadcast"""
|
|
155
|
+
|
|
156
|
+
OUR_APP_VERSION: int = 20300
|
|
157
|
+
"""The numeric buildnumber (shared with android apps) specifying the
|
|
158
|
+
level of device code we are guaranteed to understand
|
|
159
|
+
|
|
160
|
+
format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
NODELESS_WANT_CONFIG_ID = 69420
|
|
164
|
+
"""A special thing to pass for want_config_id that instructs nodes to skip sending nodeinfos other than its own."""
|
|
165
|
+
|
|
166
|
+
publishingThread = DeferredExecution("publishing")
|
|
167
|
+
"""Process-wide deferred publisher worker.
|
|
168
|
+
|
|
169
|
+
`DeferredExecution.queueWork()` is thread-safe (backed by Queue) and all
|
|
170
|
+
callbacks are serialized on the worker thread.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
logger = logging.getLogger(__name__)
|
|
174
|
+
|
|
175
|
+
REDACTED_TEXT = "<redacted>"
|
|
176
|
+
REDACTED_BYTES = b"<redacted>"
|
|
177
|
+
DECODE_ERROR_KEY = "error"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
ResponseCallback = _Callable[[dict[str, _Any]], _Any]
|
|
181
|
+
ProtobufFactory = _Callable[[], _Any]
|
|
182
|
+
OnReceive = _Callable[[_Any, dict[str, _Any]], None]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class ResponseHandler(_NamedTuple):
|
|
186
|
+
"""A pending response callback, waiting for a response to one of our messages."""
|
|
187
|
+
|
|
188
|
+
# requestId: int - used only as a key
|
|
189
|
+
#: a callable to call when a response is received
|
|
190
|
+
callback: ResponseCallback
|
|
191
|
+
#: Whether ACKs and NAKs should be passed to this handler
|
|
192
|
+
ackPermitted: bool = False
|
|
193
|
+
# FIXME, add timestamp and age out old requests
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class KnownProtocol(_NamedTuple):
|
|
197
|
+
"""Used to automatically decode known protocol payloads."""
|
|
198
|
+
|
|
199
|
+
#: A descriptive name (e.g. "text", "user", "admin")
|
|
200
|
+
name: str
|
|
201
|
+
#: If set, will be called to parse as a protocol buffer
|
|
202
|
+
protobufFactory: ProtobufFactory | None = None
|
|
203
|
+
#: If set, invoked as onReceive(interface, packet)
|
|
204
|
+
onReceive: OnReceive | None = None
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _packet_debug_summary(as_dict: dict[str, _Any]) -> dict[str, _Any]:
|
|
208
|
+
"""Return a sanitized packet summary for debug logging.
|
|
209
|
+
|
|
210
|
+
The summary intentionally omits sensitive payload/body fields while retaining
|
|
211
|
+
enough context for troubleshooting receive-handler flows.
|
|
212
|
+
"""
|
|
213
|
+
decoded = as_dict.get("decoded")
|
|
214
|
+
decoded_dict = decoded if isinstance(decoded, dict) else {}
|
|
215
|
+
payload = decoded_dict.get("payload")
|
|
216
|
+
payload_len = (
|
|
217
|
+
len(payload)
|
|
218
|
+
if isinstance(payload, (bytes, bytearray, memoryview, str))
|
|
219
|
+
else None
|
|
220
|
+
)
|
|
221
|
+
decoded_keys = sorted(str(key) for key in decoded_dict.keys())
|
|
222
|
+
return {
|
|
223
|
+
"from": as_dict.get("from"),
|
|
224
|
+
"to": as_dict.get("to"),
|
|
225
|
+
"portnum": decoded_dict.get("portnum"),
|
|
226
|
+
"decoded_keys": decoded_keys,
|
|
227
|
+
"payload_len": payload_len,
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _sanitize_last_received(as_dict: dict[str, _Any]) -> dict[str, _Any]:
|
|
232
|
+
"""Return a node-cache-safe packet copy for ``node['lastReceived']``.
|
|
233
|
+
|
|
234
|
+
Keeps historical packet structure for compatibility while redacting only
|
|
235
|
+
``decoded.admin.raw.session_passkey`` when present.
|
|
236
|
+
"""
|
|
237
|
+
sanitized = dict(as_dict)
|
|
238
|
+
decoded = sanitized.get("decoded")
|
|
239
|
+
if not isinstance(decoded, dict):
|
|
240
|
+
return sanitized
|
|
241
|
+
if "admin" not in decoded:
|
|
242
|
+
return sanitized
|
|
243
|
+
decoded_sanitized = dict(decoded)
|
|
244
|
+
admin_payload = decoded.get("admin")
|
|
245
|
+
if isinstance(admin_payload, dict):
|
|
246
|
+
admin_sanitized = dict(admin_payload)
|
|
247
|
+
raw_admin = admin_payload.get("raw")
|
|
248
|
+
if isinstance(raw_admin, dict):
|
|
249
|
+
raw_sanitized_dict = dict(raw_admin)
|
|
250
|
+
if "session_passkey" in raw_sanitized_dict:
|
|
251
|
+
raw_sanitized_dict["session_passkey"] = (
|
|
252
|
+
REDACTED_BYTES
|
|
253
|
+
if isinstance(
|
|
254
|
+
raw_sanitized_dict["session_passkey"],
|
|
255
|
+
(bytes, bytearray, memoryview),
|
|
256
|
+
)
|
|
257
|
+
else REDACTED_TEXT
|
|
258
|
+
)
|
|
259
|
+
admin_sanitized["raw"] = raw_sanitized_dict
|
|
260
|
+
elif hasattr(raw_admin, "session_passkey"):
|
|
261
|
+
raw_sanitized_obj: _Any | None
|
|
262
|
+
try:
|
|
263
|
+
raw_sanitized_obj = _copy.deepcopy(raw_admin)
|
|
264
|
+
except Exception: # noqa: BLE001 - preserve packet flow on odd payloads
|
|
265
|
+
logger.debug(
|
|
266
|
+
"deepcopy failed during admin payload redaction; using sentinel payload",
|
|
267
|
+
exc_info=True,
|
|
268
|
+
)
|
|
269
|
+
raw_sanitized_obj = None
|
|
270
|
+
if raw_sanitized_obj is None:
|
|
271
|
+
admin_sanitized["raw"] = {"session_passkey": REDACTED_BYTES}
|
|
272
|
+
else:
|
|
273
|
+
try:
|
|
274
|
+
raw_sanitized_obj.session_passkey = REDACTED_BYTES
|
|
275
|
+
except Exception: # noqa: BLE001 - best effort redaction only
|
|
276
|
+
logger.debug(
|
|
277
|
+
"session_passkey assignment redaction failed; forcing fallback redaction",
|
|
278
|
+
exc_info=True,
|
|
279
|
+
)
|
|
280
|
+
redaction_applied = False
|
|
281
|
+
try:
|
|
282
|
+
delattr(raw_sanitized_obj, "session_passkey")
|
|
283
|
+
redaction_applied = True
|
|
284
|
+
except Exception: # noqa: BLE001 - final fallback below
|
|
285
|
+
redaction_applied = False
|
|
286
|
+
admin_sanitized["raw"] = (
|
|
287
|
+
raw_sanitized_obj
|
|
288
|
+
if redaction_applied
|
|
289
|
+
else {"session_passkey": REDACTED_BYTES}
|
|
290
|
+
)
|
|
291
|
+
else:
|
|
292
|
+
admin_sanitized["raw"] = raw_sanitized_obj
|
|
293
|
+
decoded_sanitized["admin"] = admin_sanitized
|
|
294
|
+
else:
|
|
295
|
+
decoded_sanitized["admin"] = REDACTED_TEXT
|
|
296
|
+
sanitized["decoded"] = decoded_sanitized
|
|
297
|
+
return sanitized
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _is_valid_node_num(value: object) -> _TypeGuard[int]:
|
|
301
|
+
"""Return True when value is an integer node number (excluding bool)."""
|
|
302
|
+
return isinstance(value, int) and not isinstance(value, bool)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _extract_sender_and_decoded(
|
|
306
|
+
as_dict: dict[str, _Any],
|
|
307
|
+
) -> tuple[int, dict[str, _Any]] | None:
|
|
308
|
+
"""Return validated packet sender and decoded payload dictionary.
|
|
309
|
+
|
|
310
|
+
Parameters
|
|
311
|
+
----------
|
|
312
|
+
as_dict : dict[str, _Any]
|
|
313
|
+
Packet dictionary expected to contain an integer ``from`` and dict
|
|
314
|
+
``decoded`` payload.
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
tuple[int, dict[str, Any]] | None
|
|
319
|
+
``(sender, decoded)`` when both fields are present with expected types;
|
|
320
|
+
otherwise ``None``.
|
|
321
|
+
"""
|
|
322
|
+
sender = as_dict.get("from")
|
|
323
|
+
decoded = as_dict.get("decoded")
|
|
324
|
+
if not _is_valid_node_num(sender) or not isinstance(decoded, dict):
|
|
325
|
+
return None
|
|
326
|
+
return sender, decoded
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _on_text_receive(iface: _Any, as_dict: dict[str, _Any]) -> None:
|
|
330
|
+
"""Decode text payloads from a received packet and update per-node metadata.
|
|
331
|
+
|
|
332
|
+
If the packet's decoded.payload contains valid UTF-8, store the decoded string in
|
|
333
|
+
as_dict["decoded"]["text"]. If decoding fails, leave that field unset and log an error.
|
|
334
|
+
Always invokes the interface's info-update path to refresh node metadata based on the packet.
|
|
335
|
+
|
|
336
|
+
Parameters
|
|
337
|
+
----------
|
|
338
|
+
iface : _Any
|
|
339
|
+
The interface instance that received the packet.
|
|
340
|
+
as_dict : dict[str, _Any]
|
|
341
|
+
Packet dictionary expected to contain
|
|
342
|
+
decoded.payload (bytes) where the text is stored.
|
|
343
|
+
"""
|
|
344
|
+
# We don't throw if the utf8 is invalid in the text message. Instead we just don't populate
|
|
345
|
+
# the decoded.data.text and we log an error message. This at least allows some delivery to
|
|
346
|
+
# the app and the app can deal with the missing decoded representation.
|
|
347
|
+
#
|
|
348
|
+
# Usually btw this problem is caused by apps sending binary data but setting the payload type to
|
|
349
|
+
# text.
|
|
350
|
+
logger.debug("in _on_text_receive() %s", _packet_debug_summary(as_dict))
|
|
351
|
+
try:
|
|
352
|
+
as_bytes = as_dict["decoded"]["payload"]
|
|
353
|
+
as_dict["decoded"]["text"] = as_bytes.decode("utf-8")
|
|
354
|
+
except (UnicodeDecodeError, KeyError, AttributeError, TypeError):
|
|
355
|
+
logger.exception("Malformatted utf8 in text message")
|
|
356
|
+
_receive_info_update(iface, as_dict)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _on_position_receive(iface: _Any, as_dict: dict[str, _Any]) -> None:
|
|
360
|
+
"""Update the sender node's stored position when a received packet contains position data.
|
|
361
|
+
|
|
362
|
+
If as_dict contains a "from" field and a decoded "position", the position is normalized
|
|
363
|
+
using the interface's fixup routine and written to that node's "position" entry.
|
|
364
|
+
|
|
365
|
+
Parameters
|
|
366
|
+
----------
|
|
367
|
+
iface : _Any
|
|
368
|
+
Interface instance that provides position normalization and node lookup helpers.
|
|
369
|
+
as_dict : dict[str, _Any]
|
|
370
|
+
Packet dictionary expected to contain
|
|
371
|
+
"from" and "decoded"->"position".
|
|
372
|
+
"""
|
|
373
|
+
logger.debug("in _on_position_receive() %s", _packet_debug_summary(as_dict))
|
|
374
|
+
packet_guard = _extract_sender_and_decoded(as_dict)
|
|
375
|
+
if packet_guard is None:
|
|
376
|
+
return
|
|
377
|
+
sender, decoded = packet_guard
|
|
378
|
+
if "position" in decoded:
|
|
379
|
+
_receive_info_update(iface, as_dict)
|
|
380
|
+
p = decoded["position"]
|
|
381
|
+
if not isinstance(p, dict):
|
|
382
|
+
logger.debug(
|
|
383
|
+
"Skipping position update from=%s: unexpected payload type %s",
|
|
384
|
+
sender,
|
|
385
|
+
type(p).__name__,
|
|
386
|
+
)
|
|
387
|
+
return
|
|
388
|
+
if DECODE_ERROR_KEY in p:
|
|
389
|
+
logger.debug(
|
|
390
|
+
"Skipping position state update from=%s due to decode error payload",
|
|
391
|
+
sender,
|
|
392
|
+
)
|
|
393
|
+
return
|
|
394
|
+
logger.debug("position payload received from=%s", sender)
|
|
395
|
+
p = iface._fixup_position(p)
|
|
396
|
+
logger.debug("position payload normalized from=%s", sender)
|
|
397
|
+
node = iface._get_or_create_by_num(sender)
|
|
398
|
+
with iface._node_db_lock:
|
|
399
|
+
node["position"] = p
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _on_node_info_receive(iface: _Any, as_dict: dict[str, _Any]) -> None:
|
|
403
|
+
"""Update the local node record from a received NodeInfo ("user") payload.
|
|
404
|
+
|
|
405
|
+
When `as_dict` contains a decoded `"user"` entry and a `"from"` sender, stores
|
|
406
|
+
the decoded user protobuf on the sender's node under `node["user"]`, ensures
|
|
407
|
+
`iface.nodes` maps the user's `id` to that node, and refreshes per-node metadata
|
|
408
|
+
via _receive_info_update(iface, as_dict).
|
|
409
|
+
|
|
410
|
+
Parameters
|
|
411
|
+
----------
|
|
412
|
+
iface : _Any
|
|
413
|
+
Interface instance managing the node database.
|
|
414
|
+
as_dict : dict[str, _Any]
|
|
415
|
+
Received packet dictionary; expected to contain
|
|
416
|
+
`"decoded" -> "user"` and `"from"`.
|
|
417
|
+
"""
|
|
418
|
+
logger.debug("in _on_node_info_receive() %s", _packet_debug_summary(as_dict))
|
|
419
|
+
packet_guard = _extract_sender_and_decoded(as_dict)
|
|
420
|
+
if packet_guard is None:
|
|
421
|
+
return
|
|
422
|
+
sender, decoded = packet_guard
|
|
423
|
+
if "user" in decoded:
|
|
424
|
+
_receive_info_update(iface, as_dict)
|
|
425
|
+
p = decoded["user"]
|
|
426
|
+
if not isinstance(p, dict):
|
|
427
|
+
logger.debug(
|
|
428
|
+
"Skipping user update from=%s: unexpected payload type %s",
|
|
429
|
+
sender,
|
|
430
|
+
type(p).__name__,
|
|
431
|
+
)
|
|
432
|
+
return
|
|
433
|
+
if DECODE_ERROR_KEY in p:
|
|
434
|
+
logger.debug(
|
|
435
|
+
"Skipping user state update from=%s due to decode error payload",
|
|
436
|
+
sender,
|
|
437
|
+
)
|
|
438
|
+
return
|
|
439
|
+
# decode user protobufs and update nodedb, provide decoded version as "position" in the published msg
|
|
440
|
+
# update node DB as needed
|
|
441
|
+
n = iface._get_or_create_by_num(sender)
|
|
442
|
+
with iface._node_db_lock:
|
|
443
|
+
n["user"] = p
|
|
444
|
+
# We now have a node ID, make sure it is up-to-date in that table
|
|
445
|
+
node_id = p.get("id") if isinstance(p, dict) else None
|
|
446
|
+
nodes_by_id = iface.nodes
|
|
447
|
+
if isinstance(node_id, str) and node_id and isinstance(nodes_by_id, dict):
|
|
448
|
+
nodes_by_id[node_id] = n
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _on_telemetry_receive(iface: _Any, as_dict: dict[str, _Any]) -> None:
|
|
452
|
+
"""Update the appropriate telemetry section on the sender node when a telemetry packet is received.
|
|
453
|
+
|
|
454
|
+
Merges metrics from the packet's `decoded.telemetry` into one of the node's telemetry
|
|
455
|
+
sections: `deviceMetrics`, `environmentMetrics`, `airQualityMetrics`, `powerMetrics`,
|
|
456
|
+
or `localStats`. If the packet lacks a `from` field or none of these sections are
|
|
457
|
+
present, no change is made.
|
|
458
|
+
|
|
459
|
+
Parameters
|
|
460
|
+
----------
|
|
461
|
+
iface : _Any
|
|
462
|
+
Interface instance used to look up or create the target node.
|
|
463
|
+
as_dict : dict[str, _Any]
|
|
464
|
+
Received packet dictionary; expected to include
|
|
465
|
+
a `from` key and may include `decoded.telemetry`.
|
|
466
|
+
"""
|
|
467
|
+
logger.debug("in _on_telemetry_receive() %s", _packet_debug_summary(as_dict))
|
|
468
|
+
packet_guard = _extract_sender_and_decoded(as_dict)
|
|
469
|
+
if packet_guard is None:
|
|
470
|
+
return
|
|
471
|
+
sender, decoded = packet_guard
|
|
472
|
+
_receive_info_update(iface, as_dict)
|
|
473
|
+
|
|
474
|
+
to_update = None
|
|
475
|
+
telemetry = decoded.get("telemetry") or {}
|
|
476
|
+
if not isinstance(telemetry, dict):
|
|
477
|
+
logger.debug(
|
|
478
|
+
"Skipping telemetry update from=%s: unexpected telemetry payload type %s",
|
|
479
|
+
as_dict.get("from"),
|
|
480
|
+
type(telemetry).__name__,
|
|
481
|
+
)
|
|
482
|
+
return
|
|
483
|
+
if "deviceMetrics" in telemetry:
|
|
484
|
+
to_update = "deviceMetrics"
|
|
485
|
+
elif "environmentMetrics" in telemetry:
|
|
486
|
+
to_update = "environmentMetrics"
|
|
487
|
+
elif "airQualityMetrics" in telemetry:
|
|
488
|
+
to_update = "airQualityMetrics"
|
|
489
|
+
elif "powerMetrics" in telemetry:
|
|
490
|
+
to_update = "powerMetrics"
|
|
491
|
+
elif "localStats" in telemetry:
|
|
492
|
+
to_update = "localStats"
|
|
493
|
+
else:
|
|
494
|
+
return
|
|
495
|
+
|
|
496
|
+
update_obj = telemetry.get(to_update)
|
|
497
|
+
if not isinstance(update_obj, dict):
|
|
498
|
+
return
|
|
499
|
+
node = iface._get_or_create_by_num(sender)
|
|
500
|
+
with iface._node_db_lock:
|
|
501
|
+
new_metrics = node.get(to_update, {})
|
|
502
|
+
if not isinstance(new_metrics, dict):
|
|
503
|
+
new_metrics = {}
|
|
504
|
+
new_metrics.update(update_obj)
|
|
505
|
+
logger.debug("updating %s metrics for %s to %s", to_update, sender, new_metrics)
|
|
506
|
+
node[to_update] = new_metrics
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _receive_info_update(iface: _Any, as_dict: dict[str, _Any]) -> None:
|
|
510
|
+
"""Update per-node metadata fields based on information present in a received packet dictionary.
|
|
511
|
+
|
|
512
|
+
Parameters
|
|
513
|
+
----------
|
|
514
|
+
iface : _Any
|
|
515
|
+
The interface instance whose node store will be updated.
|
|
516
|
+
as_dict : dict[str, _Any]
|
|
517
|
+
Parsed packet dictionary; if it contains an integer "from" key, the
|
|
518
|
+
node identified by that value will have these fields set:
|
|
519
|
+
- lastReceived: packet dictionary copy with admin session passkey redacted
|
|
520
|
+
- lastHeard: value of `rxTime` from the packet (or None)
|
|
521
|
+
- snr: value of `rxSnr` from the packet (or None)
|
|
522
|
+
- hopLimit: value of `hopLimit` from the packet (or None)
|
|
523
|
+
"""
|
|
524
|
+
sender = as_dict.get("from")
|
|
525
|
+
if not _is_valid_node_num(sender):
|
|
526
|
+
if sender is not None:
|
|
527
|
+
logger.debug(
|
|
528
|
+
"Skipping receive info update due to non-integer sender type: %s",
|
|
529
|
+
type(sender).__name__,
|
|
530
|
+
)
|
|
531
|
+
return
|
|
532
|
+
node = iface._get_or_create_by_num(sender)
|
|
533
|
+
sanitized_last_received = _sanitize_last_received(as_dict)
|
|
534
|
+
with iface._node_db_lock:
|
|
535
|
+
node["lastReceived"] = sanitized_last_received
|
|
536
|
+
node["lastHeard"] = as_dict.get("rxTime")
|
|
537
|
+
node["snr"] = as_dict.get("rxSnr")
|
|
538
|
+
node["hopLimit"] = as_dict.get("hopLimit")
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _on_admin_receive(iface: _Any, as_dict: dict[str, _Any]) -> None:
|
|
542
|
+
"""Store the admin session passkey from an admin packet on the sending node.
|
|
543
|
+
|
|
544
|
+
If the expected fields are present in `as_dict`, sets the sender node's
|
|
545
|
+
"adminSessionPassKey" to the extracted `session_passkey`.
|
|
546
|
+
|
|
547
|
+
Parameters
|
|
548
|
+
----------
|
|
549
|
+
iface : _Any
|
|
550
|
+
The interface instance managing the node database.
|
|
551
|
+
as_dict : dict[str, _Any]
|
|
552
|
+
Received packet dictionary; expected to contain
|
|
553
|
+
`decoded.admin.raw.session_passkey` and a `from` sender field.
|
|
554
|
+
"""
|
|
555
|
+
logger.debug("in _on_admin_receive() from=%s", as_dict.get("from"))
|
|
556
|
+
packet_guard = _extract_sender_and_decoded(as_dict)
|
|
557
|
+
if packet_guard is None:
|
|
558
|
+
sender = as_dict.get("from")
|
|
559
|
+
decoded = as_dict.get("decoded")
|
|
560
|
+
if sender is None:
|
|
561
|
+
logger.debug("Dropping admin packet because 'from' field is missing")
|
|
562
|
+
elif not _is_valid_node_num(sender):
|
|
563
|
+
logger.debug("Admin packet has invalid 'from' field type: %r", type(sender))
|
|
564
|
+
elif not isinstance(decoded, dict):
|
|
565
|
+
logger.debug("Admin packet missing decoded dict from=%s", sender)
|
|
566
|
+
return
|
|
567
|
+
|
|
568
|
+
sender, decoded = packet_guard
|
|
569
|
+
_receive_info_update(iface, as_dict)
|
|
570
|
+
|
|
571
|
+
admin_payload = decoded.get("admin")
|
|
572
|
+
if not isinstance(admin_payload, dict):
|
|
573
|
+
logger.debug("Admin packet missing admin payload dict from=%s", sender)
|
|
574
|
+
return
|
|
575
|
+
|
|
576
|
+
raw_admin = admin_payload.get("raw")
|
|
577
|
+
session_passkey = getattr(raw_admin, "session_passkey", None)
|
|
578
|
+
if session_passkey is None and isinstance(raw_admin, dict):
|
|
579
|
+
session_passkey = raw_admin.get("session_passkey")
|
|
580
|
+
|
|
581
|
+
if session_passkey is None:
|
|
582
|
+
logger.debug(
|
|
583
|
+
"Admin session passkey not extracted from admin packet from=%s",
|
|
584
|
+
sender,
|
|
585
|
+
)
|
|
586
|
+
return
|
|
587
|
+
|
|
588
|
+
node = iface._get_or_create_by_num(sender)
|
|
589
|
+
with iface._node_db_lock:
|
|
590
|
+
node["adminSessionPassKey"] = session_passkey
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
# Well known message payloads can register decoders for automatic protobuf parsing.
|
|
594
|
+
protocols = {
|
|
595
|
+
portnums_pb2.PortNum.TEXT_MESSAGE_APP: KnownProtocol(
|
|
596
|
+
"text", onReceive=_on_text_receive
|
|
597
|
+
),
|
|
598
|
+
portnums_pb2.PortNum.RANGE_TEST_APP: KnownProtocol(
|
|
599
|
+
"rangetest", onReceive=_on_text_receive
|
|
600
|
+
),
|
|
601
|
+
portnums_pb2.PortNum.DETECTION_SENSOR_APP: KnownProtocol(
|
|
602
|
+
"detectionsensor", onReceive=_on_text_receive
|
|
603
|
+
),
|
|
604
|
+
portnums_pb2.PortNum.POSITION_APP: KnownProtocol(
|
|
605
|
+
"position", mesh_pb2.Position, _on_position_receive
|
|
606
|
+
),
|
|
607
|
+
portnums_pb2.PortNum.NODEINFO_APP: KnownProtocol(
|
|
608
|
+
"user", mesh_pb2.User, _on_node_info_receive
|
|
609
|
+
),
|
|
610
|
+
portnums_pb2.PortNum.ADMIN_APP: KnownProtocol(
|
|
611
|
+
"admin", admin_pb2.AdminMessage, _on_admin_receive
|
|
612
|
+
),
|
|
613
|
+
portnums_pb2.PortNum.ROUTING_APP: KnownProtocol("routing", mesh_pb2.Routing),
|
|
614
|
+
portnums_pb2.PortNum.TELEMETRY_APP: KnownProtocol(
|
|
615
|
+
"telemetry", telemetry_pb2.Telemetry, _on_telemetry_receive
|
|
616
|
+
),
|
|
617
|
+
portnums_pb2.PortNum.REMOTE_HARDWARE_APP: KnownProtocol(
|
|
618
|
+
"remotehw", remote_hardware_pb2.HardwareMessage
|
|
619
|
+
),
|
|
620
|
+
portnums_pb2.PortNum.SIMULATOR_APP: KnownProtocol("simulator", mesh_pb2.Compressed),
|
|
621
|
+
portnums_pb2.PortNum.TRACEROUTE_APP: KnownProtocol(
|
|
622
|
+
"traceroute", mesh_pb2.RouteDiscovery
|
|
623
|
+
),
|
|
624
|
+
portnums_pb2.PortNum.POWERSTRESS_APP: KnownProtocol(
|
|
625
|
+
"powerstress", powermon_pb2.PowerStressMessage
|
|
626
|
+
),
|
|
627
|
+
portnums_pb2.PortNum.WAYPOINT_APP: KnownProtocol("waypoint", mesh_pb2.Waypoint),
|
|
628
|
+
portnums_pb2.PortNum.PAXCOUNTER_APP: KnownProtocol(
|
|
629
|
+
"paxcounter", paxcount_pb2.Paxcount
|
|
630
|
+
),
|
|
631
|
+
portnums_pb2.PortNum.STORE_FORWARD_APP: KnownProtocol(
|
|
632
|
+
"storeforward", storeforward_pb2.StoreAndForward
|
|
633
|
+
),
|
|
634
|
+
portnums_pb2.PortNum.NEIGHBORINFO_APP: KnownProtocol(
|
|
635
|
+
"neighborinfo", mesh_pb2.NeighborInfo
|
|
636
|
+
),
|
|
637
|
+
portnums_pb2.PortNum.MAP_REPORT_APP: KnownProtocol("mapreport", mqtt_pb2.MapReport),
|
|
638
|
+
}
|