meshcore 2.1.17__py3-none-any.whl → 2.1.24__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.
Potentially problematic release.
This version of meshcore might be problematic. Click here for more details.
- meshcore/commands/.binary.py.swp +0 -0
- meshcore/commands/__init__.py +6 -1
- meshcore/commands/base.py +2 -2
- meshcore/commands/binary.py +111 -0
- meshcore/commands/control_data.py +50 -0
- meshcore/commands/device.py +41 -42
- meshcore/commands/messaging.py +19 -0
- meshcore/events.py +3 -0
- meshcore/packets.py +9 -0
- meshcore/reader.py +212 -123
- {meshcore-2.1.17.dist-info → meshcore-2.1.24.dist-info}/METADATA +1 -1
- meshcore-2.1.24.dist-info/RECORD +23 -0
- meshcore-2.1.17.dist-info/RECORD +0 -21
- {meshcore-2.1.17.dist-info → meshcore-2.1.24.dist-info}/WHEEL +0 -0
- {meshcore-2.1.17.dist-info → meshcore-2.1.24.dist-info}/licenses/LICENSE +0 -0
|
Binary file
|
meshcore/commands/__init__.py
CHANGED
|
@@ -7,10 +7,15 @@ from .binary import BinaryCommandHandler
|
|
|
7
7
|
from .contact import ContactCommands
|
|
8
8
|
from .device import DeviceCommands
|
|
9
9
|
from .messaging import MessagingCommands
|
|
10
|
+
from .control_data import ControlDataCommandHandler
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class CommandHandler(
|
|
13
|
-
DeviceCommands,
|
|
14
|
+
DeviceCommands,
|
|
15
|
+
ContactCommands,
|
|
16
|
+
MessagingCommands,
|
|
17
|
+
BinaryCommandHandler,
|
|
18
|
+
ControlDataCommandHandler
|
|
14
19
|
):
|
|
15
20
|
pass
|
|
16
21
|
|
meshcore/commands/base.py
CHANGED
|
@@ -163,7 +163,7 @@ class CommandHandlerBase:
|
|
|
163
163
|
return Event(EventType.OK, {})
|
|
164
164
|
|
|
165
165
|
# attached at base because its a common method
|
|
166
|
-
async def send_binary_req(self, dst: DestinationType, request_type: BinaryReqType, data: Optional[bytes] = None, timeout=None, min_timeout=0) -> Event:
|
|
166
|
+
async def send_binary_req(self, dst: DestinationType, request_type: BinaryReqType, data: Optional[bytes] = None, context={}, timeout=None, min_timeout=0) -> Event:
|
|
167
167
|
dst_bytes = _validate_destination(dst, prefix_length=32)
|
|
168
168
|
pubkey_prefix = _validate_destination(dst, prefix_length=6)
|
|
169
169
|
logger.debug(f"Binary request to {dst_bytes.hex()}")
|
|
@@ -180,6 +180,6 @@ class CommandHandlerBase:
|
|
|
180
180
|
# Use provided timeout or fallback to suggested timeout (with 5s default)
|
|
181
181
|
actual_timeout = timeout if timeout is not None and timeout > 0 else result.payload.get("suggested_timeout", 4000) / 800.0
|
|
182
182
|
actual_timeout = min_timeout if actual_timeout < min_timeout else actual_timeout
|
|
183
|
-
self._reader.register_binary_request(pubkey_prefix.hex(), exp_tag, request_type, actual_timeout)
|
|
183
|
+
self._reader.register_binary_request(pubkey_prefix.hex(), exp_tag, request_type, actual_timeout, context=context)
|
|
184
184
|
|
|
185
185
|
return result
|
meshcore/commands/binary.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import logging
|
|
3
|
+
import random
|
|
2
4
|
|
|
3
5
|
from .base import CommandHandlerBase
|
|
4
6
|
from ..events import EventType
|
|
@@ -131,3 +133,112 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|
|
131
133
|
)
|
|
132
134
|
|
|
133
135
|
return acl_event.payload["acl_data"] if acl_event else None
|
|
136
|
+
|
|
137
|
+
async def req_neighbours_async(self,
|
|
138
|
+
contact,
|
|
139
|
+
count=255,
|
|
140
|
+
offset=0,
|
|
141
|
+
order_by=0,
|
|
142
|
+
pubkey_prefix_length=4,
|
|
143
|
+
timeout=0,
|
|
144
|
+
min_timeout=0
|
|
145
|
+
):
|
|
146
|
+
req = (b"\x00" # version : 0
|
|
147
|
+
+ count.to_bytes(1, "little", signed=False)
|
|
148
|
+
+ offset.to_bytes(2, "little", signed=False)
|
|
149
|
+
+ order_by.to_bytes(1, "little", signed=False)
|
|
150
|
+
+ pubkey_prefix_length.to_bytes(1, "little", signed=False)
|
|
151
|
+
+ random.randint(1, 0xFFFFFFFF).to_bytes(4, "little", signed=False)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
logger.debug(f"Sending binary neighbours req, count: {count}, offset: {offset} {req.hex()}")
|
|
155
|
+
|
|
156
|
+
return await self.send_binary_req (
|
|
157
|
+
contact,
|
|
158
|
+
BinaryReqType.NEIGHBOURS,
|
|
159
|
+
data=req,
|
|
160
|
+
timeout=timeout,
|
|
161
|
+
context={"pubkey_prefix_length": pubkey_prefix_length}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
async def req_neighbours_sync(self,
|
|
165
|
+
contact,
|
|
166
|
+
count=255,
|
|
167
|
+
offset=0,
|
|
168
|
+
order_by=0,
|
|
169
|
+
pubkey_prefix_length=4,
|
|
170
|
+
timeout=0,
|
|
171
|
+
min_timeout=0
|
|
172
|
+
):
|
|
173
|
+
|
|
174
|
+
res = await self.req_neighbours_async(contact,
|
|
175
|
+
count=count,
|
|
176
|
+
offset=offset,
|
|
177
|
+
order_by=order_by,
|
|
178
|
+
pubkey_prefix_length=pubkey_prefix_length,
|
|
179
|
+
timeout=timeout,
|
|
180
|
+
min_timeout=min_timeout)
|
|
181
|
+
|
|
182
|
+
if res is None or res.type == EventType.ERROR:
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
timeout = res.payload["suggested_timeout"] / 800 if timeout == 0 else timeout
|
|
186
|
+
timeout = timeout if min_timeout < timeout else min_timeout
|
|
187
|
+
|
|
188
|
+
if self.dispatcher is None:
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
# Listen for NEIGHBOUR_RESPONSE
|
|
192
|
+
neighbours_event = await self.dispatcher.wait_for_event(
|
|
193
|
+
EventType.NEIGHBOURS_RESPONSE,
|
|
194
|
+
attribute_filters={"tag": res.payload["expected_ack"].hex()},
|
|
195
|
+
timeout=timeout,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return neighbours_event.payload if neighbours_event else None
|
|
199
|
+
|
|
200
|
+
# do several queries if not all neighbours have been obtained
|
|
201
|
+
async def fetch_all_neighbours(self,
|
|
202
|
+
contact,
|
|
203
|
+
order_by=0,
|
|
204
|
+
pubkey_prefix_length=4,
|
|
205
|
+
timeout=0,
|
|
206
|
+
min_timeout=0
|
|
207
|
+
):
|
|
208
|
+
|
|
209
|
+
# Initial request
|
|
210
|
+
res = await self.req_neighbours_sync(contact,
|
|
211
|
+
count=255,
|
|
212
|
+
offset=0,
|
|
213
|
+
order_by=order_by,
|
|
214
|
+
pubkey_prefix_length=pubkey_prefix_length,
|
|
215
|
+
timeout=timeout,
|
|
216
|
+
min_timeout=min_timeout)
|
|
217
|
+
|
|
218
|
+
if res is None:
|
|
219
|
+
return None
|
|
220
|
+
|
|
221
|
+
neighbours_count = res["neighbours_count"] # total neighbours
|
|
222
|
+
results_count = res["results_count"] # obtained neighbours
|
|
223
|
+
|
|
224
|
+
del res["tag"]
|
|
225
|
+
|
|
226
|
+
while results_count < neighbours_count:
|
|
227
|
+
#await asyncio.sleep(2) # wait 2s before next fetch
|
|
228
|
+
next_res = await self.req_neighbours_sync(contact,
|
|
229
|
+
count=255,
|
|
230
|
+
offset=results_count,
|
|
231
|
+
order_by=order_by,
|
|
232
|
+
pubkey_prefix_length=pubkey_prefix_length,
|
|
233
|
+
timeout=timeout,
|
|
234
|
+
min_timeout=min_timeout+5) # requests are close, so let's have some more timeout
|
|
235
|
+
|
|
236
|
+
if next_res is None :
|
|
237
|
+
return res # caller should check it has everything
|
|
238
|
+
|
|
239
|
+
results_count = results_count + next_res["results_count"]
|
|
240
|
+
|
|
241
|
+
res["results_count"] = results_count
|
|
242
|
+
res["neighbours"] += next_res["neighbours"]
|
|
243
|
+
|
|
244
|
+
return res
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import random
|
|
3
|
+
|
|
4
|
+
from .base import CommandHandlerBase
|
|
5
|
+
from ..events import EventType, Event
|
|
6
|
+
from ..packets import ControlType, PacketType
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("meshcore")
|
|
9
|
+
|
|
10
|
+
class ControlDataCommandHandler(CommandHandlerBase):
|
|
11
|
+
"""Helper functions to handle binary requests through binary commands"""
|
|
12
|
+
|
|
13
|
+
async def send_control_data (self, control_type: int, payload: bytes) -> Event:
|
|
14
|
+
data = bytearray([PacketType.SEND_CONTROL_DATA.value])
|
|
15
|
+
data.extend(control_type.to_bytes(1, "little", signed = False))
|
|
16
|
+
data.extend(payload)
|
|
17
|
+
|
|
18
|
+
result = await self.send(data, [EventType.OK, EventType.ERROR])
|
|
19
|
+
return result
|
|
20
|
+
|
|
21
|
+
async def send_node_discover_req (
|
|
22
|
+
self,
|
|
23
|
+
filter: int,
|
|
24
|
+
prefix_only: bool=True,
|
|
25
|
+
tag: int=None,
|
|
26
|
+
since: int=None
|
|
27
|
+
) -> Event:
|
|
28
|
+
|
|
29
|
+
if tag is None:
|
|
30
|
+
tag = random.randint(1, 0xFFFFFFFF)
|
|
31
|
+
|
|
32
|
+
data = bytearray()
|
|
33
|
+
data.extend(filter.to_bytes(1, "little", signed=False))
|
|
34
|
+
data.extend(tag.to_bytes(4, "little"))
|
|
35
|
+
if not since is None:
|
|
36
|
+
data.extend(since.to_bytes(4, "little", signed=False))
|
|
37
|
+
|
|
38
|
+
logger.debug(f"sending node discover req {data.hex()}")
|
|
39
|
+
|
|
40
|
+
flags = 0
|
|
41
|
+
flags = flags | 1 if prefix_only else flags
|
|
42
|
+
|
|
43
|
+
res = await self.send_control_data(
|
|
44
|
+
ControlType.NODE_DISCOVER_REQ.value|flags, data)
|
|
45
|
+
|
|
46
|
+
if res is None:
|
|
47
|
+
return None
|
|
48
|
+
else:
|
|
49
|
+
res.payload["tag"] = tag
|
|
50
|
+
return res
|
meshcore/commands/device.py
CHANGED
|
@@ -87,14 +87,18 @@ class DeviceCommands(CommandHandlerBase):
|
|
|
87
87
|
[EventType.OK, EventType.ERROR],
|
|
88
88
|
)
|
|
89
89
|
|
|
90
|
+
# the old set_other_params function has been replaced in
|
|
91
|
+
# favour of set_other_params_from_infos to be more generic
|
|
92
|
+
# stays here for backward compatibility but does not support
|
|
93
|
+
# multi_acks for instance
|
|
90
94
|
async def set_other_params(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
self,
|
|
96
|
+
manual_add_contacts: bool,
|
|
97
|
+
telemetry_mode_base: int,
|
|
98
|
+
telemetry_mode_loc: int,
|
|
99
|
+
telemetry_mode_env: int,
|
|
100
|
+
advert_loc_policy: int,
|
|
101
|
+
) -> Event:
|
|
98
102
|
telemetry_mode = (
|
|
99
103
|
(telemetry_mode_base & 0b11)
|
|
100
104
|
| ((telemetry_mode_loc & 0b11) << 2)
|
|
@@ -108,55 +112,50 @@ class DeviceCommands(CommandHandlerBase):
|
|
|
108
112
|
)
|
|
109
113
|
return await self.send(data, [EventType.OK, EventType.ERROR])
|
|
110
114
|
|
|
115
|
+
async def set_other_params_from_infos(self, infos) -> Event:
|
|
116
|
+
telemetry_mode = (
|
|
117
|
+
(infos["telemetry_mode_base"] & 0b11)
|
|
118
|
+
| ((infos["telemetry_mode_loc"] & 0b11) << 2)
|
|
119
|
+
| ((infos["telemetry_mode_env"] & 0b11) << 4)
|
|
120
|
+
)
|
|
121
|
+
data = (
|
|
122
|
+
b"\x26"
|
|
123
|
+
+ infos["manual_add_contacts"].to_bytes(1)
|
|
124
|
+
+ telemetry_mode.to_bytes(1)
|
|
125
|
+
+ infos["adv_loc_policy"].to_bytes(1)
|
|
126
|
+
+ infos["multi_acks"].to_bytes(1)
|
|
127
|
+
)
|
|
128
|
+
return await self.send(data, [EventType.OK, EventType.ERROR])
|
|
129
|
+
|
|
111
130
|
async def set_telemetry_mode_base(self, telemetry_mode_base: int) -> Event:
|
|
112
131
|
infos = (await self.send_appstart()).payload
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
telemetry_mode_base,
|
|
116
|
-
infos["telemetry_mode_loc"],
|
|
117
|
-
infos["telemetry_mode_env"],
|
|
118
|
-
infos["adv_loc_policy"],
|
|
119
|
-
)
|
|
132
|
+
infos["telemetry_mode_base"] = telemetry_mode_base
|
|
133
|
+
return await self.set_other_params_from_infos(infos)
|
|
120
134
|
|
|
121
135
|
async def set_telemetry_mode_loc(self, telemetry_mode_loc: int) -> Event:
|
|
122
136
|
infos = (await self.send_appstart()).payload
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
infos["telemetry_mode_base"],
|
|
126
|
-
telemetry_mode_loc,
|
|
127
|
-
infos["telemetry_mode_env"],
|
|
128
|
-
infos["adv_loc_policy"],
|
|
129
|
-
)
|
|
137
|
+
infos["telemetry_mode_loc"] = telemetry_mode_loc
|
|
138
|
+
return await self.set_other_params_from_infos(infos)
|
|
130
139
|
|
|
131
140
|
async def set_telemetry_mode_env(self, telemetry_mode_env: int) -> Event:
|
|
132
141
|
infos = (await self.send_appstart()).payload
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
infos["telemetry_mode_base"],
|
|
136
|
-
infos["telemetry_mode_loc"],
|
|
137
|
-
telemetry_mode_env,
|
|
138
|
-
infos["adv_loc_policy"],
|
|
139
|
-
)
|
|
142
|
+
infos["telemetry_mode_env"] = telemetry_mode_env
|
|
143
|
+
return await self.set_other_params_from_infos(infos)
|
|
140
144
|
|
|
141
145
|
async def set_manual_add_contacts(self, manual_add_contacts: bool) -> Event:
|
|
142
146
|
infos = (await self.send_appstart()).payload
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
infos["telemetry_mode_base"],
|
|
146
|
-
infos["telemetry_mode_loc"],
|
|
147
|
-
infos["telemetry_mode_env"],
|
|
148
|
-
infos["adv_loc_policy"],
|
|
149
|
-
)
|
|
147
|
+
infos["manual_add_contacts"] = manual_add_contacts
|
|
148
|
+
return await self.set_other_params_from_infos(infos)
|
|
150
149
|
|
|
151
150
|
async def set_advert_loc_policy(self, advert_loc_policy: int) -> Event:
|
|
152
151
|
infos = (await self.send_appstart()).payload
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
)
|
|
152
|
+
infos["adv_loc_policy"] = advert_loc_policy
|
|
153
|
+
return await self.set_other_params_from_infos(infos)
|
|
154
|
+
|
|
155
|
+
async def set_multi_acks(self, multi_acks: int) -> Event:
|
|
156
|
+
infos = (await self.send_appstart()).payload
|
|
157
|
+
infos["multi_acks"] = multi_acks
|
|
158
|
+
return await self.set_other_params_from_infos(infos)
|
|
160
159
|
|
|
161
160
|
async def set_devicepin(self, pin: int) -> Event:
|
|
162
161
|
logger.debug(f"Setting device PIN to: {pin}")
|
meshcore/commands/messaging.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import random
|
|
3
3
|
from typing import Optional, Union
|
|
4
|
+
from hashlib import sha256
|
|
4
5
|
|
|
5
6
|
from ..events import Event, EventType
|
|
7
|
+
from ..packets import PacketType
|
|
6
8
|
from .base import CommandHandlerBase, DestinationType, _validate_destination
|
|
7
9
|
|
|
8
10
|
logger = logging.getLogger("meshcore")
|
|
@@ -209,3 +211,20 @@ class MessagingCommands(CommandHandlerBase):
|
|
|
209
211
|
return Event(EventType.ERROR, {"reason": "unsupported_path_type"})
|
|
210
212
|
|
|
211
213
|
return await self.send(cmd_data, [EventType.MSG_SENT, EventType.ERROR])
|
|
214
|
+
|
|
215
|
+
async def set_flood_scope(self, scope):
|
|
216
|
+
if scope.startswith("#"): # an hash
|
|
217
|
+
logger.debug(f"Setting scope from hash {scope}")
|
|
218
|
+
scope_key = sha256(scope.encode("utf-8")).digest()[0:16]
|
|
219
|
+
elif scope == "0" or scope == "None" or scope == "*" or scope == "": # disable
|
|
220
|
+
scope_key = b"\0"*16
|
|
221
|
+
else: # assume the key has been sent directly
|
|
222
|
+
scope_key = scope.encode("utf-8")
|
|
223
|
+
|
|
224
|
+
logger.debug(f"Setting scope to {scope_key.hex()}")
|
|
225
|
+
|
|
226
|
+
cmd_data = bytearray([PacketType.SET_FLOOD_SCOPE.value])
|
|
227
|
+
cmd_data.extend(b"\0")
|
|
228
|
+
cmd_data.extend(scope_key)
|
|
229
|
+
|
|
230
|
+
return await self.send(cmd_data, [EventType.OK, EventType.ERROR])
|
meshcore/events.py
CHANGED
|
@@ -44,6 +44,9 @@ class EventType(Enum):
|
|
|
44
44
|
PATH_RESPONSE = "path_response"
|
|
45
45
|
PRIVATE_KEY = "private_key"
|
|
46
46
|
DISABLED = "disabled"
|
|
47
|
+
CONTROL_DATA = "control_data"
|
|
48
|
+
DISCOVER_RESPONSE = "discover_response"
|
|
49
|
+
NEIGHBOURS_RESPONSE = "neighbours_response"
|
|
47
50
|
|
|
48
51
|
# Command response types
|
|
49
52
|
OK = "command_ok"
|
meshcore/packets.py
CHANGED
|
@@ -6,6 +6,11 @@ class BinaryReqType(Enum):
|
|
|
6
6
|
TELEMETRY = 0x03
|
|
7
7
|
MMA = 0x04
|
|
8
8
|
ACL = 0x05
|
|
9
|
+
NEIGHBOURS = 0x06
|
|
10
|
+
|
|
11
|
+
class ControlType(Enum):
|
|
12
|
+
NODE_DISCOVER_REQ = 0x80
|
|
13
|
+
NODE_DISCOVER_RESP = 0x90
|
|
9
14
|
|
|
10
15
|
# Packet prefixes for the protocol
|
|
11
16
|
class PacketType(Enum):
|
|
@@ -33,6 +38,9 @@ class PacketType(Enum):
|
|
|
33
38
|
CUSTOM_VARS = 21
|
|
34
39
|
BINARY_REQ = 50
|
|
35
40
|
FACTORY_RESET = 51
|
|
41
|
+
PATH_DISCOVERY = 52
|
|
42
|
+
SET_FLOOD_SCOPE = 54
|
|
43
|
+
SEND_CONTROL_DATA = 55
|
|
36
44
|
|
|
37
45
|
# Push notifications
|
|
38
46
|
ADVERTISEMENT = 0x80
|
|
@@ -49,3 +57,4 @@ class PacketType(Enum):
|
|
|
49
57
|
TELEMETRY_RESPONSE = 0x8B
|
|
50
58
|
BINARY_RESPONSE = 0x8C
|
|
51
59
|
PATH_DISCOVERY_RESPONSE = 0x8D
|
|
60
|
+
CONTROL_DATA = 0x8E
|
meshcore/reader.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import json
|
|
3
3
|
import time
|
|
4
|
+
import io
|
|
4
5
|
from typing import Any, Dict
|
|
5
6
|
from .events import Event, EventType, EventDispatcher
|
|
6
|
-
from .packets import BinaryReqType, PacketType
|
|
7
|
+
from .packets import BinaryReqType, PacketType, ControlType
|
|
7
8
|
from .parsing import lpp_parse, lpp_parse_mma, parse_acl, parse_status
|
|
8
9
|
from cayennelpp import LppFrame, LppData
|
|
9
10
|
from meshcore.lpp_json_encoder import lpp_json_encoder
|
|
@@ -18,20 +19,21 @@ class MessageReader:
|
|
|
18
19
|
# before events are dispatched
|
|
19
20
|
self.contacts = {} # Temporary storage during contact list building
|
|
20
21
|
self.contact_nb = 0 # Used for contact processing
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
# Track pending binary requests by tag for proper response parsing
|
|
23
24
|
self.pending_binary_requests: Dict[str, Dict[str, Any]] = {} # tag -> {request_type, expires_at}
|
|
24
25
|
|
|
25
|
-
def register_binary_request(self, prefix: str, tag: str, request_type: BinaryReqType, timeout_seconds: float):
|
|
26
|
+
def register_binary_request(self, prefix: str, tag: str, request_type: BinaryReqType, timeout_seconds: float, context={}):
|
|
26
27
|
"""Register a pending binary request for proper response parsing"""
|
|
27
28
|
# Clean up expired requests before adding new one
|
|
28
29
|
self.cleanup_expired_requests()
|
|
29
|
-
|
|
30
|
+
|
|
30
31
|
expires_at = time.time() + timeout_seconds
|
|
31
32
|
self.pending_binary_requests[tag] = {
|
|
32
33
|
"request_type": request_type,
|
|
33
34
|
"pubkey_prefix": prefix,
|
|
34
|
-
"expires_at": expires_at
|
|
35
|
+
"expires_at": expires_at,
|
|
36
|
+
"context": context # optional info we want to keep from req to resp
|
|
35
37
|
}
|
|
36
38
|
logger.debug(f"Registered binary request: tag={tag}, type={request_type}, expires in {timeout_seconds}s")
|
|
37
39
|
|
|
@@ -42,13 +44,14 @@ class MessageReader:
|
|
|
42
44
|
tag for tag, info in self.pending_binary_requests.items()
|
|
43
45
|
if current_time > info["expires_at"]
|
|
44
46
|
]
|
|
45
|
-
|
|
47
|
+
|
|
46
48
|
for tag in expired_tags:
|
|
47
49
|
logger.debug(f"Cleaning up expired binary request: tag={tag}")
|
|
48
50
|
del self.pending_binary_requests[tag]
|
|
49
|
-
|
|
51
|
+
|
|
50
52
|
async def handle_rx(self, data: bytearray):
|
|
51
|
-
|
|
53
|
+
dbuf = io.BytesIO(data)
|
|
54
|
+
packet_type_value = dbuf.read(1)[0]
|
|
52
55
|
logger.debug(f"Received data: {data.hex()}")
|
|
53
56
|
|
|
54
57
|
# Handle command responses
|
|
@@ -78,23 +81,24 @@ class MessageReader:
|
|
|
78
81
|
or packet_type_value == PacketType.PUSH_CODE_NEW_ADVERT.value
|
|
79
82
|
):
|
|
80
83
|
c = {}
|
|
81
|
-
c["public_key"] =
|
|
82
|
-
c["type"] =
|
|
83
|
-
c["flags"] =
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
c["public_key"] = dbuf.read(32).hex()
|
|
85
|
+
c["type"] = dbuf.read(1)[0]
|
|
86
|
+
c["flags"] = dbuf.read(1)[0]
|
|
87
|
+
plen = int.from_bytes(dbuf.read(1), signed=True, byteorder="little")
|
|
88
|
+
c["out_path_len"] = plen
|
|
86
89
|
if plen == -1:
|
|
87
90
|
plen = 0
|
|
88
|
-
|
|
89
|
-
c["
|
|
90
|
-
c["
|
|
91
|
+
path = dbuf.read(64)
|
|
92
|
+
c["out_path"] = path[0:plen].hex()
|
|
93
|
+
c["adv_name"] = dbuf.read(32).decode("utf-8", "ignore").replace("\0", "")
|
|
94
|
+
c["last_advert"] = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
91
95
|
c["adv_lat"] = (
|
|
92
|
-
int.from_bytes(
|
|
96
|
+
int.from_bytes(dbuf.read(4), byteorder="little", signed=True) / 1e6
|
|
93
97
|
)
|
|
94
98
|
c["adv_lon"] = (
|
|
95
|
-
int.from_bytes(
|
|
99
|
+
int.from_bytes(dbuf.read(4), byteorder="little", signed=True) / 1e6
|
|
96
100
|
)
|
|
97
|
-
c["lastmod"] = int.from_bytes(
|
|
101
|
+
c["lastmod"] = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
98
102
|
|
|
99
103
|
if packet_type_value == PacketType.PUSH_CODE_NEW_ADVERT.value:
|
|
100
104
|
await self.dispatcher.dispatch(Event(EventType.NEW_CONTACT, c))
|
|
@@ -103,7 +107,7 @@ class MessageReader:
|
|
|
103
107
|
self.contacts[c["public_key"]] = c
|
|
104
108
|
|
|
105
109
|
elif packet_type_value == PacketType.CONTACT_END.value:
|
|
106
|
-
lastmod = int.from_bytes(
|
|
110
|
+
lastmod = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
107
111
|
attributes = {
|
|
108
112
|
"lastmod": lastmod,
|
|
109
113
|
}
|
|
@@ -113,37 +117,39 @@ class MessageReader:
|
|
|
113
117
|
|
|
114
118
|
elif packet_type_value == PacketType.SELF_INFO.value:
|
|
115
119
|
self_info = {}
|
|
116
|
-
self_info["adv_type"] =
|
|
117
|
-
self_info["tx_power"] =
|
|
118
|
-
self_info["max_tx_power"] =
|
|
119
|
-
self_info["public_key"] =
|
|
120
|
+
self_info["adv_type"] = dbuf.read(1)[0]
|
|
121
|
+
self_info["tx_power"] = dbuf.read(1)[0]
|
|
122
|
+
self_info["max_tx_power"] = dbuf.read(1)[0]
|
|
123
|
+
self_info["public_key"] = dbuf.read(32).hex()
|
|
120
124
|
self_info["adv_lat"] = (
|
|
121
|
-
int.from_bytes(
|
|
125
|
+
int.from_bytes(dbuf.read(4), byteorder="little", signed=True) / 1e6
|
|
122
126
|
)
|
|
123
127
|
self_info["adv_lon"] = (
|
|
124
|
-
int.from_bytes(
|
|
128
|
+
int.from_bytes(dbuf.read(4), byteorder="little", signed=True) / 1e6
|
|
125
129
|
)
|
|
126
|
-
self_info["
|
|
127
|
-
self_info["
|
|
128
|
-
|
|
129
|
-
self_info["
|
|
130
|
-
self_info["
|
|
130
|
+
self_info["multi_acks"] = dbuf.read(1)[0]
|
|
131
|
+
self_info["adv_loc_policy"] = dbuf.read(1)[0]
|
|
132
|
+
telemetry_mode = dbuf.read(1)[0]
|
|
133
|
+
self_info["telemetry_mode_env"] = (telemetry_mode >> 4) & 0b11
|
|
134
|
+
self_info["telemetry_mode_loc"] = (telemetry_mode >> 2) & 0b11
|
|
135
|
+
self_info["telemetry_mode_base"] = (telemetry_mode) & 0b11
|
|
136
|
+
self_info["manual_add_contacts"] = dbuf.read(1)[0] > 0
|
|
131
137
|
self_info["radio_freq"] = (
|
|
132
|
-
int.from_bytes(
|
|
138
|
+
int.from_bytes(dbuf.read(4), byteorder="little") / 1000
|
|
133
139
|
)
|
|
134
140
|
self_info["radio_bw"] = (
|
|
135
|
-
int.from_bytes(
|
|
141
|
+
int.from_bytes(dbuf.read(4), byteorder="little") / 1000
|
|
136
142
|
)
|
|
137
|
-
self_info["radio_sf"] =
|
|
138
|
-
self_info["radio_cr"] =
|
|
139
|
-
self_info["name"] =
|
|
143
|
+
self_info["radio_sf"] = dbuf.read(1)[0]
|
|
144
|
+
self_info["radio_cr"] = dbuf.read(1)[0]
|
|
145
|
+
self_info["name"] = dbuf.read().decode("utf-8", "ignore")
|
|
140
146
|
await self.dispatcher.dispatch(Event(EventType.SELF_INFO, self_info))
|
|
141
147
|
|
|
142
148
|
elif packet_type_value == PacketType.MSG_SENT.value:
|
|
143
149
|
res = {}
|
|
144
|
-
res["type"] =
|
|
145
|
-
res["expected_ack"] =
|
|
146
|
-
res["suggested_timeout"] = int.from_bytes(
|
|
150
|
+
res["type"] = dbuf.read(1)[0]
|
|
151
|
+
res["expected_ack"] = dbuf.read(4)
|
|
152
|
+
res["suggested_timeout"] = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
147
153
|
|
|
148
154
|
attributes = {
|
|
149
155
|
"type": res["type"],
|
|
@@ -155,15 +161,14 @@ class MessageReader:
|
|
|
155
161
|
elif packet_type_value == PacketType.CONTACT_MSG_RECV.value:
|
|
156
162
|
res = {}
|
|
157
163
|
res["type"] = "PRIV"
|
|
158
|
-
res["pubkey_prefix"] =
|
|
159
|
-
res["path_len"] =
|
|
160
|
-
|
|
161
|
-
res["
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
res["
|
|
165
|
-
|
|
166
|
-
res["text"] = data[13:].decode("utf-8", "ignore")
|
|
164
|
+
res["pubkey_prefix"] = dbuf.read(6).hex()
|
|
165
|
+
res["path_len"] = dbuf.read(1)[0]
|
|
166
|
+
txt_type = dbuf.read(1)[0]
|
|
167
|
+
res["txt_type"] = txt_type
|
|
168
|
+
res["sender_timestamp"] = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
169
|
+
if txt_type == 2:
|
|
170
|
+
res["signature"] = dbuf.read(4).hex()
|
|
171
|
+
res["text"] = dbuf.read().decode("utf-8", "ignore")
|
|
167
172
|
|
|
168
173
|
attributes = {
|
|
169
174
|
"pubkey_prefix": res["pubkey_prefix"],
|
|
@@ -177,16 +182,16 @@ class MessageReader:
|
|
|
177
182
|
elif packet_type_value == 16: # A reply to CMD_SYNC_NEXT_MESSAGE (ver >= 3)
|
|
178
183
|
res = {}
|
|
179
184
|
res["type"] = "PRIV"
|
|
180
|
-
res["SNR"] = int.from_bytes(
|
|
181
|
-
|
|
182
|
-
res["
|
|
183
|
-
res["
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
185
|
+
res["SNR"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True) / 4
|
|
186
|
+
dbuf.read(2) # reserved
|
|
187
|
+
res["pubkey_prefix"] = dbuf.read(6).hex()
|
|
188
|
+
res["path_len"] = dbuf.read(1)[0]
|
|
189
|
+
txt_type = dbuf.read(1)[0]
|
|
190
|
+
res["txt_type"] = txt_type
|
|
191
|
+
res["sender_timestamp"] = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
192
|
+
if txt_type == 2:
|
|
193
|
+
res["signature"] = dbuf.read(4).hex()
|
|
194
|
+
res["text"] = dbuf.read().decode("utf-8", "ignore")
|
|
190
195
|
|
|
191
196
|
attributes = {
|
|
192
197
|
"pubkey_prefix": res["pubkey_prefix"],
|
|
@@ -200,11 +205,11 @@ class MessageReader:
|
|
|
200
205
|
elif packet_type_value == PacketType.CHANNEL_MSG_RECV.value:
|
|
201
206
|
res = {}
|
|
202
207
|
res["type"] = "CHAN"
|
|
203
|
-
res["channel_idx"] =
|
|
204
|
-
res["path_len"] =
|
|
205
|
-
res["txt_type"] =
|
|
206
|
-
res["sender_timestamp"] = int.from_bytes(
|
|
207
|
-
res["text"] =
|
|
208
|
+
res["channel_idx"] = dbuf.read(1)[0]
|
|
209
|
+
res["path_len"] = dbuf.read(1)[0]
|
|
210
|
+
res["txt_type"] = dbuf.read(1)[0]
|
|
211
|
+
res["sender_timestamp"] = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
212
|
+
res["text"] = dbuf.read().decode("utf-8", "ignore")
|
|
208
213
|
|
|
209
214
|
attributes = {
|
|
210
215
|
"channel_idx": res["channel_idx"],
|
|
@@ -218,12 +223,13 @@ class MessageReader:
|
|
|
218
223
|
elif packet_type_value == 17: # A reply to CMD_SYNC_NEXT_MESSAGE (ver >= 3)
|
|
219
224
|
res = {}
|
|
220
225
|
res["type"] = "CHAN"
|
|
221
|
-
res["SNR"] = int.from_bytes(
|
|
222
|
-
|
|
223
|
-
res["
|
|
224
|
-
res["
|
|
225
|
-
res["
|
|
226
|
-
res["
|
|
226
|
+
res["SNR"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True) / 4
|
|
227
|
+
dbuf.read(2) # reserved
|
|
228
|
+
res["channel_idx"] = dbuf.read(1)[0]
|
|
229
|
+
res["path_len"] = dbuf.read(1)[0]
|
|
230
|
+
res["txt_type"] = dbuf.read(1)[0]
|
|
231
|
+
res["sender_timestamp"] = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
232
|
+
res["text"] = dbuf.read().decode("utf-8", "ignore")
|
|
227
233
|
|
|
228
234
|
attributes = {
|
|
229
235
|
"channel_idx": res["channel_idx"],
|
|
@@ -235,7 +241,7 @@ class MessageReader:
|
|
|
235
241
|
)
|
|
236
242
|
|
|
237
243
|
elif packet_type_value == PacketType.CURRENT_TIME.value:
|
|
238
|
-
time_value = int.from_bytes(
|
|
244
|
+
time_value = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
239
245
|
result = {"time": time_value}
|
|
240
246
|
await self.dispatcher.dispatch(Event(EventType.CURRENT_TIME, result))
|
|
241
247
|
|
|
@@ -244,34 +250,34 @@ class MessageReader:
|
|
|
244
250
|
await self.dispatcher.dispatch(Event(EventType.NO_MORE_MSGS, result))
|
|
245
251
|
|
|
246
252
|
elif packet_type_value == PacketType.CONTACT_URI.value:
|
|
247
|
-
contact_uri = "meshcore://" +
|
|
253
|
+
contact_uri = "meshcore://" + dbuf.read().hex()
|
|
248
254
|
result = {"uri": contact_uri}
|
|
249
255
|
await self.dispatcher.dispatch(Event(EventType.CONTACT_URI, result))
|
|
250
256
|
|
|
251
257
|
elif packet_type_value == PacketType.BATTERY.value:
|
|
252
|
-
battery_level = int.from_bytes(
|
|
258
|
+
battery_level = int.from_bytes(dbuf.read(2), byteorder="little")
|
|
253
259
|
result = {"level": battery_level}
|
|
254
260
|
if len(data) > 3: # has storage info as well
|
|
255
|
-
result["used_kb"] = int.from_bytes(
|
|
256
|
-
result["total_kb"] = int.from_bytes(
|
|
261
|
+
result["used_kb"] = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
262
|
+
result["total_kb"] = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
257
263
|
await self.dispatcher.dispatch(Event(EventType.BATTERY, result))
|
|
258
264
|
|
|
259
265
|
elif packet_type_value == PacketType.DEVICE_INFO.value:
|
|
260
266
|
res = {}
|
|
261
|
-
res["fw ver"] =
|
|
267
|
+
res["fw ver"] = dbuf.read(1)[0]
|
|
262
268
|
if data[1] >= 3:
|
|
263
|
-
res["max_contacts"] =
|
|
264
|
-
res["max_channels"] =
|
|
265
|
-
res["ble_pin"] = int.from_bytes(
|
|
266
|
-
res["fw_build"] =
|
|
267
|
-
res["model"] =
|
|
268
|
-
res["ver"] =
|
|
269
|
+
res["max_contacts"] = dbuf.read(1)[0] * 2
|
|
270
|
+
res["max_channels"] = dbuf.read(1)[0]
|
|
271
|
+
res["ble_pin"] = int.from_bytes(dbuf.read(4), byteorder="little")
|
|
272
|
+
res["fw_build"] = dbuf.read(12).decode("utf-8", "ignore").replace("\0", "")
|
|
273
|
+
res["model"] = dbuf.read(40).decode("utf-8", "ignore").replace("\0", "")
|
|
274
|
+
res["ver"] = dbuf.read(20).decode("utf-8", "ignore").replace("\0", "")
|
|
269
275
|
await self.dispatcher.dispatch(Event(EventType.DEVICE_INFO, res))
|
|
270
276
|
|
|
271
277
|
elif packet_type_value == PacketType.CUSTOM_VARS.value:
|
|
272
278
|
logger.debug(f"received custom vars response: {data.hex()}")
|
|
273
279
|
res = {}
|
|
274
|
-
rawdata =
|
|
280
|
+
rawdata = dbuf.read().decode("utf-8", "ignore")
|
|
275
281
|
if not rawdata == "":
|
|
276
282
|
pairs = rawdata.split(",")
|
|
277
283
|
for p in pairs:
|
|
@@ -283,30 +289,30 @@ class MessageReader:
|
|
|
283
289
|
elif packet_type_value == PacketType.CHANNEL_INFO.value:
|
|
284
290
|
logger.debug(f"received channel info response: {data.hex()}")
|
|
285
291
|
res = {}
|
|
286
|
-
res["channel_idx"] =
|
|
292
|
+
res["channel_idx"] = dbuf.read(1)[0]
|
|
287
293
|
|
|
288
294
|
# Channel name is null-terminated, so find the first null byte
|
|
289
|
-
name_bytes =
|
|
295
|
+
name_bytes = dbuf.read(32)
|
|
290
296
|
null_pos = name_bytes.find(0)
|
|
291
297
|
if null_pos >= 0:
|
|
292
298
|
res["channel_name"] = name_bytes[:null_pos].decode("utf-8", "ignore")
|
|
293
299
|
else:
|
|
294
300
|
res["channel_name"] = name_bytes.decode("utf-8", "ignore")
|
|
295
301
|
|
|
296
|
-
res["channel_secret"] =
|
|
302
|
+
res["channel_secret"] = dbuf.read(16)
|
|
297
303
|
await self.dispatcher.dispatch(Event(EventType.CHANNEL_INFO, res, res))
|
|
298
304
|
|
|
299
305
|
# Push notifications
|
|
300
306
|
elif packet_type_value == PacketType.ADVERTISEMENT.value:
|
|
301
307
|
logger.debug("Advertisement received")
|
|
302
308
|
res = {}
|
|
303
|
-
res["public_key"] =
|
|
309
|
+
res["public_key"] = dbuf.read(32).hex()
|
|
304
310
|
await self.dispatcher.dispatch(Event(EventType.ADVERTISEMENT, res, res))
|
|
305
311
|
|
|
306
312
|
elif packet_type_value == PacketType.PATH_UPDATE.value:
|
|
307
313
|
logger.debug("Code path update")
|
|
308
314
|
res = {}
|
|
309
|
-
res["public_key"] =
|
|
315
|
+
res["public_key"] = dbuf.read(32).hex()
|
|
310
316
|
await self.dispatcher.dispatch(Event(EventType.PATH_UPDATE, res, res))
|
|
311
317
|
|
|
312
318
|
elif packet_type_value == PacketType.ACK.value:
|
|
@@ -314,7 +320,7 @@ class MessageReader:
|
|
|
314
320
|
ack_data = {}
|
|
315
321
|
|
|
316
322
|
if len(data) >= 5:
|
|
317
|
-
ack_data["code"] =
|
|
323
|
+
ack_data["code"] = dbuf.read(4).hex()
|
|
318
324
|
|
|
319
325
|
attributes = {"code": ack_data.get("code", "")}
|
|
320
326
|
|
|
@@ -326,23 +332,24 @@ class MessageReader:
|
|
|
326
332
|
|
|
327
333
|
elif packet_type_value == PacketType.RAW_DATA.value:
|
|
328
334
|
res = {}
|
|
329
|
-
res["SNR"] =
|
|
330
|
-
res["RSSI"] =
|
|
331
|
-
res["payload"] =
|
|
335
|
+
res["SNR"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True) / 4
|
|
336
|
+
res["RSSI"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True)
|
|
337
|
+
res["payload"] = dbuf.read(4).hex()
|
|
332
338
|
logger.debug("Received raw data")
|
|
333
339
|
print(res)
|
|
334
340
|
await self.dispatcher.dispatch(Event(EventType.RAW_DATA, res))
|
|
335
341
|
|
|
336
342
|
elif packet_type_value == PacketType.LOGIN_SUCCESS.value:
|
|
337
343
|
res = {}
|
|
344
|
+
attributes = {}
|
|
338
345
|
if len(data) > 1:
|
|
339
|
-
|
|
340
|
-
res["
|
|
346
|
+
perms = dbuf.read(1)[0]
|
|
347
|
+
res["permissions"] = perms
|
|
348
|
+
res["is_admin"] = (perms & 1) == 1 # Check if admin bit is set
|
|
341
349
|
|
|
342
|
-
|
|
343
|
-
res["pubkey_prefix"] = data[2:8].hex()
|
|
350
|
+
res["pubkey_prefix"] = dbuf.read(6).hex()
|
|
344
351
|
|
|
345
|
-
|
|
352
|
+
attributes = {"pubkey_prefix": res.get("pubkey_prefix")}
|
|
346
353
|
|
|
347
354
|
await self.dispatcher.dispatch(
|
|
348
355
|
Event(EventType.LOGIN_SUCCESS, res, attributes)
|
|
@@ -350,11 +357,14 @@ class MessageReader:
|
|
|
350
357
|
|
|
351
358
|
elif packet_type_value == PacketType.LOGIN_FAILED.value:
|
|
352
359
|
res = {}
|
|
360
|
+
attributes = {}
|
|
361
|
+
|
|
362
|
+
pbuf.read(1)
|
|
353
363
|
|
|
354
364
|
if len(data) > 7:
|
|
355
|
-
res["pubkey_prefix"] =
|
|
365
|
+
res["pubkey_prefix"] = pbuf.read(6).hex()
|
|
356
366
|
|
|
357
|
-
|
|
367
|
+
attributes = {"pubkey_prefix": res.get("pubkey_prefix")}
|
|
358
368
|
|
|
359
369
|
await self.dispatcher.dispatch(
|
|
360
370
|
Event(EventType.LOGIN_FAILED, res, attributes)
|
|
@@ -368,12 +378,7 @@ class MessageReader:
|
|
|
368
378
|
attributes = {
|
|
369
379
|
"pubkey_prefix": res["pubkey_pre"],
|
|
370
380
|
}
|
|
371
|
-
data_hex = data[8:].hex()
|
|
372
|
-
logger.debug(f"Status response: {data_hex}")
|
|
373
381
|
|
|
374
|
-
attributes = {
|
|
375
|
-
"pubkey_prefix": res["pubkey_pre"],
|
|
376
|
-
}
|
|
377
382
|
await self.dispatcher.dispatch(
|
|
378
383
|
Event(EventType.STATUS_RESPONSE, res, attributes)
|
|
379
384
|
)
|
|
@@ -386,22 +391,23 @@ class MessageReader:
|
|
|
386
391
|
|
|
387
392
|
# First byte is SNR (signed byte, multiplied by 4)
|
|
388
393
|
if len(data) > 1:
|
|
389
|
-
snr_byte =
|
|
394
|
+
snr_byte = dbuf.read(1)[0]
|
|
390
395
|
# Convert to signed value
|
|
391
396
|
snr = (snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0
|
|
392
397
|
log_data["snr"] = snr
|
|
393
398
|
|
|
394
399
|
# Second byte is RSSI (signed byte)
|
|
395
400
|
if len(data) > 2:
|
|
396
|
-
rssi_byte =
|
|
401
|
+
rssi_byte = dbuf.read(1)[0]
|
|
397
402
|
# Convert to signed value
|
|
398
403
|
rssi = rssi_byte if rssi_byte < 128 else rssi_byte - 256
|
|
399
404
|
log_data["rssi"] = rssi
|
|
400
405
|
|
|
401
406
|
# Remaining bytes are the raw data payload
|
|
402
407
|
if len(data) > 3:
|
|
403
|
-
|
|
404
|
-
log_data["
|
|
408
|
+
payload=dbuf.read()
|
|
409
|
+
log_data["payload"] = payload.hex()
|
|
410
|
+
log_data["payload_length"] = len(payload)
|
|
405
411
|
|
|
406
412
|
attributes = {
|
|
407
413
|
"pubkey_prefix": log_data["raw_hex"],
|
|
@@ -470,8 +476,10 @@ class MessageReader:
|
|
|
470
476
|
logger.debug(f"Received telemetry data: {data.hex()}")
|
|
471
477
|
res = {}
|
|
472
478
|
|
|
473
|
-
|
|
474
|
-
|
|
479
|
+
dbuf.read(1)
|
|
480
|
+
|
|
481
|
+
res["pubkey_pre"] = dbuf.read(6).hex()
|
|
482
|
+
buf = dbuf.read()
|
|
475
483
|
|
|
476
484
|
"""Parse a given byte string and return as a LppFrame object."""
|
|
477
485
|
i = 0
|
|
@@ -498,29 +506,31 @@ class MessageReader:
|
|
|
498
506
|
|
|
499
507
|
elif packet_type_value == PacketType.BINARY_RESPONSE.value:
|
|
500
508
|
logger.debug(f"Received binary data: {data.hex()}")
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
509
|
+
dbuf.read(1)
|
|
510
|
+
tag = dbuf.read(4).hex()
|
|
511
|
+
response_data = dbuf.read()
|
|
512
|
+
|
|
504
513
|
# Always dispatch generic BINARY_RESPONSE
|
|
505
514
|
binary_res = {"tag": tag, "data": response_data.hex()}
|
|
506
515
|
await self.dispatcher.dispatch(
|
|
507
516
|
Event(EventType.BINARY_RESPONSE, binary_res, {"tag": tag})
|
|
508
517
|
)
|
|
509
|
-
|
|
518
|
+
|
|
510
519
|
# Check for tracked request type and dispatch specific response
|
|
511
520
|
if tag in self.pending_binary_requests:
|
|
512
521
|
request_type = self.pending_binary_requests[tag]["request_type"]
|
|
513
522
|
pubkey_prefix = self.pending_binary_requests[tag]["pubkey_prefix"]
|
|
523
|
+
context = self.pending_binary_requests[tag]["context"]
|
|
514
524
|
del self.pending_binary_requests[tag]
|
|
515
525
|
logger.debug(f"Processing binary response for tag {tag}, type {request_type}, pubkey_prefix {pubkey_prefix}")
|
|
516
|
-
|
|
526
|
+
|
|
517
527
|
if request_type == BinaryReqType.STATUS and len(response_data) >= 52:
|
|
518
528
|
res = {}
|
|
519
529
|
res = parse_status(response_data, pubkey_prefix=pubkey_prefix)
|
|
520
530
|
await self.dispatcher.dispatch(
|
|
521
531
|
Event(EventType.STATUS_RESPONSE, res, {"pubkey_prefix": res["pubkey_pre"], "tag": tag})
|
|
522
532
|
)
|
|
523
|
-
|
|
533
|
+
|
|
524
534
|
elif request_type == BinaryReqType.TELEMETRY:
|
|
525
535
|
try:
|
|
526
536
|
lpp = lpp_parse(response_data)
|
|
@@ -530,7 +540,7 @@ class MessageReader:
|
|
|
530
540
|
)
|
|
531
541
|
except Exception as e:
|
|
532
542
|
logger.error(f"Error parsing binary telemetry response: {e}")
|
|
533
|
-
|
|
543
|
+
|
|
534
544
|
elif request_type == BinaryReqType.MMA:
|
|
535
545
|
try:
|
|
536
546
|
mma_result = lpp_parse_mma(response_data[4:]) # Skip 4-byte header
|
|
@@ -540,7 +550,7 @@ class MessageReader:
|
|
|
540
550
|
)
|
|
541
551
|
except Exception as e:
|
|
542
552
|
logger.error(f"Error parsing binary MMA response: {e}")
|
|
543
|
-
|
|
553
|
+
|
|
544
554
|
elif request_type == BinaryReqType.ACL:
|
|
545
555
|
try:
|
|
546
556
|
acl_result = parse_acl(response_data)
|
|
@@ -550,19 +560,54 @@ class MessageReader:
|
|
|
550
560
|
)
|
|
551
561
|
except Exception as e:
|
|
552
562
|
logger.error(f"Error parsing binary ACL response: {e}")
|
|
563
|
+
|
|
564
|
+
elif request_type == BinaryReqType.NEIGHBOURS:
|
|
565
|
+
try:
|
|
566
|
+
pk_plen = context["pubkey_prefix_length"]
|
|
567
|
+
bbuf = io.BytesIO(response_data)
|
|
568
|
+
|
|
569
|
+
res = {
|
|
570
|
+
"pubkey_prefix": pubkey_prefix,
|
|
571
|
+
"tag": tag
|
|
572
|
+
}
|
|
573
|
+
res.update(context) # add context in result
|
|
574
|
+
|
|
575
|
+
res["neighbours_count"] = int.from_bytes(bbuf.read(2), "little", signed=True)
|
|
576
|
+
results_count = int.from_bytes(bbuf.read(2), "little", signed=True)
|
|
577
|
+
res["results_count"] = results_count
|
|
578
|
+
|
|
579
|
+
neighbours_list = []
|
|
580
|
+
|
|
581
|
+
for _ in range (results_count):
|
|
582
|
+
neighb = {}
|
|
583
|
+
neighb["pubkey"] = bbuf.read(pk_plen).hex()
|
|
584
|
+
neighb["secs_ago"] = int.from_bytes(bbuf.read(4), "little", signed=True)
|
|
585
|
+
neighb["snr"] = int.from_bytes(bbuf.read(1), "little", signed=True) / 4
|
|
586
|
+
neighbours_list.append(neighb)
|
|
587
|
+
|
|
588
|
+
res["neighbours"] = neighbours_list
|
|
589
|
+
|
|
590
|
+
await self.dispatcher.dispatch(
|
|
591
|
+
Event(EventType.NEIGHBOURS_RESPONSE, res, {"tag": tag, "pubkey_prefix": pubkey_prefix})
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
except Exception as e:
|
|
595
|
+
logger.error(f"Error parsing binary NEIGHBOURS response: {e}")
|
|
596
|
+
|
|
553
597
|
else:
|
|
554
598
|
logger.debug(f"No tracked request found for binary response tag {tag}")
|
|
555
599
|
|
|
556
600
|
elif packet_type_value == PacketType.PATH_DISCOVERY_RESPONSE.value:
|
|
557
601
|
logger.debug(f"Received path discovery response: {data.hex()}")
|
|
558
602
|
res = {}
|
|
559
|
-
|
|
560
|
-
|
|
603
|
+
dbuf.read(1)
|
|
604
|
+
res["pubkey_pre"] = dbuf.read(6).hex()
|
|
605
|
+
opl = dbuf.read(1)[0]
|
|
561
606
|
res["out_path_len"] = opl
|
|
562
|
-
res["out_path"] =
|
|
563
|
-
ipl =
|
|
607
|
+
res["out_path"] = dbuf.read(opl).hex()
|
|
608
|
+
ipl = dbuf.read(1)[0]
|
|
564
609
|
res["in_path_len"] = ipl
|
|
565
|
-
res["in_path"] =
|
|
610
|
+
res["in_path"] = dbuf.read(ipl).hex()
|
|
566
611
|
|
|
567
612
|
attributes = {"pubkey_pre": res["pubkey_pre"]}
|
|
568
613
|
|
|
@@ -573,7 +618,7 @@ class MessageReader:
|
|
|
573
618
|
elif packet_type_value == PacketType.PRIVATE_KEY.value:
|
|
574
619
|
logger.debug(f"Received private key response: {data.hex()}")
|
|
575
620
|
if len(data) >= 65: # 1 byte response code + 64 bytes private key
|
|
576
|
-
private_key =
|
|
621
|
+
private_key = dbuf.read(64) # Extract 64-byte private key
|
|
577
622
|
res = {"private_key": private_key}
|
|
578
623
|
await self.dispatcher.dispatch(Event(EventType.PRIVATE_KEY, res))
|
|
579
624
|
else:
|
|
@@ -584,6 +629,50 @@ class MessageReader:
|
|
|
584
629
|
res = {"reason": "private_key_export_disabled"}
|
|
585
630
|
await self.dispatcher.dispatch(Event(EventType.DISABLED, res))
|
|
586
631
|
|
|
632
|
+
elif packet_type_value == PacketType.CONTROL_DATA.value:
|
|
633
|
+
logger.debug("Received control data packet")
|
|
634
|
+
res={}
|
|
635
|
+
res["SNR"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True) / 4
|
|
636
|
+
res["RSSI"] = int.from_bytes(dbuf.read(1), byteorder="little", signed=True)
|
|
637
|
+
res["path_len"] = dbuf.read(1)[0]
|
|
638
|
+
payload = dbuf.read()
|
|
639
|
+
payload_type = payload[0]
|
|
640
|
+
res["payload_type"] = payload_type
|
|
641
|
+
res["payload"] = payload
|
|
642
|
+
|
|
643
|
+
attributes = {"payload_type": payload_type}
|
|
644
|
+
await self.dispatcher.dispatch(
|
|
645
|
+
Event(EventType.CONTROL_DATA, res, attributes)
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
# decode NODE_DISCOVER_RESP
|
|
649
|
+
if payload_type & 0xF0 == ControlType.NODE_DISCOVER_RESP.value:
|
|
650
|
+
pbuf = io.BytesIO(payload[1:])
|
|
651
|
+
ndr = dict(res)
|
|
652
|
+
del ndr["payload_type"]
|
|
653
|
+
del ndr["payload"]
|
|
654
|
+
ndr["node_type"] = payload_type & 0x0F
|
|
655
|
+
ndr["SNR_in"] = int.from_bytes(pbuf.read(1), byteorder="little", signed=True)/4
|
|
656
|
+
ndr["tag"] = pbuf.read(4).hex()
|
|
657
|
+
|
|
658
|
+
pubkey = pbuf.read()
|
|
659
|
+
if len(pubkey) < 32:
|
|
660
|
+
pubkey = pubkey[0:8]
|
|
661
|
+
else:
|
|
662
|
+
pubkey = pubkey[0:32]
|
|
663
|
+
|
|
664
|
+
ndr["pubkey"] = pubkey.hex()
|
|
665
|
+
|
|
666
|
+
attributes = {
|
|
667
|
+
"node_type" : ndr["node_type"],
|
|
668
|
+
"tag" : ndr["tag"],
|
|
669
|
+
"pubkey" : ndr["pubkey"],
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
await self.dispatcher.dispatch(
|
|
673
|
+
Event(EventType.DISCOVER_RESPONSE, ndr, attributes)
|
|
674
|
+
)
|
|
675
|
+
|
|
587
676
|
else:
|
|
588
677
|
logger.debug(f"Unhandled data received {data}")
|
|
589
678
|
logger.debug(f"Unhandled packet type: {packet_type_value}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.24
|
|
4
4
|
Summary: Base classes for communicating with meshcore companion radios
|
|
5
5
|
Project-URL: Homepage, https://github.com/fdlamotte/meshcore_py
|
|
6
6
|
Project-URL: Issues, https://github.com/fdlamotte/meshcore_py/issues
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
meshcore/__init__.py,sha256=55PdT8gZ9Lf727s7BdSuCt3lIBlAhRzX28A8i0DHaRc,602
|
|
2
|
+
meshcore/ble_cx.py,sha256=S5uanBI88Mxwcs1zWbdQGVJ6GC4kdWz8-Z_2aQVpeGI,6864
|
|
3
|
+
meshcore/connection_manager.py,sha256=3U0TWuHDvL5FfwEwXRy5HjlCyV3nsWhhrEC7TVxFzZI,5899
|
|
4
|
+
meshcore/events.py,sha256=P40oq3C9j2cEg6iD9_-SxWK927C3ykWBTpjL8ixygFQ,8140
|
|
5
|
+
meshcore/lpp_json_encoder.py,sha256=vyn7z3VYWOo_B9xmCzqblh5xCa0QKWKcMi2eOqw18RE,1875
|
|
6
|
+
meshcore/meshcore.py,sha256=n8fH7AEx7DR8BHOYL_Qex9ns6ACKklNNqYbPnNnvqyE,16110
|
|
7
|
+
meshcore/packets.py,sha256=FT6GwH8rveLBWuFVMkbqnAuaymrk6uhd_t46Qj98uf4,1279
|
|
8
|
+
meshcore/parsing.py,sha256=48PQkig-sqvsRlkF9zkvWhJoSq6ERCbGb_aRuCND5NI,4044
|
|
9
|
+
meshcore/reader.py,sha256=inhn0Xsc7J0VKKyPVWwoM1LXfAKO-SJ4Th1c3sN4yy8,28645
|
|
10
|
+
meshcore/serial_cx.py,sha256=-kaqnqk7Ydufu2DECFfPDv4Xs-7gHUBuz8v0xf8fvvE,3969
|
|
11
|
+
meshcore/tcp_cx.py,sha256=05YRVMnjY5aVBTJcHa0uG4VfFKGbV6hQ1pPIsJg4CDI,4227
|
|
12
|
+
meshcore/commands/.binary.py.swp,sha256=SXeYOAsKheJnezxgvzUUHL43qUQWEPyc-VZUMBRsGV8,24576
|
|
13
|
+
meshcore/commands/__init__.py,sha256=qfGPzrha7pZQYHOXLZyDsK-QVRjiz5zxAEj8kMxO9uU,536
|
|
14
|
+
meshcore/commands/base.py,sha256=jO6KWSxBciEMaP0weE8OAbN6Tf-kWSaljb9py3gvihg,7126
|
|
15
|
+
meshcore/commands/binary.py,sha256=MihRjG4IppPYPeu2KMpctcBPac_hdClp6PgGirQfydg,8412
|
|
16
|
+
meshcore/commands/contact.py,sha256=-dRY68xZkDFP6-i53WujyWhEsXZsFRk98B3j3zBzaL4,5860
|
|
17
|
+
meshcore/commands/control_data.py,sha256=sXp1RoEw6Z0zPr0Nn5XBovEY6r9sePbWQwsbY0iYyXc,1512
|
|
18
|
+
meshcore/commands/device.py,sha256=V-gxA88We1KLDtzJ-rsmxv5LH04sUOacqnBc6TwB_gg,8662
|
|
19
|
+
meshcore/commands/messaging.py,sha256=5h0JecOT3KJV1AFdasVLs889xtXY4J5gguJ7KbUoZOE,9073
|
|
20
|
+
meshcore-2.1.24.dist-info/METADATA,sha256=9Ks9G4ws_jzHVCUps3JJmpWjWZ7LoKj9pbCpSViIA2c,25317
|
|
21
|
+
meshcore-2.1.24.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
22
|
+
meshcore-2.1.24.dist-info/licenses/LICENSE,sha256=o62-JWT_C-ZqEtzb1Gl_PPtPr0pVT8KDmgji_Y_bejI,1075
|
|
23
|
+
meshcore-2.1.24.dist-info/RECORD,,
|
meshcore-2.1.17.dist-info/RECORD
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
meshcore/__init__.py,sha256=55PdT8gZ9Lf727s7BdSuCt3lIBlAhRzX28A8i0DHaRc,602
|
|
2
|
-
meshcore/ble_cx.py,sha256=S5uanBI88Mxwcs1zWbdQGVJ6GC4kdWz8-Z_2aQVpeGI,6864
|
|
3
|
-
meshcore/connection_manager.py,sha256=3U0TWuHDvL5FfwEwXRy5HjlCyV3nsWhhrEC7TVxFzZI,5899
|
|
4
|
-
meshcore/events.py,sha256=ZaVy2cCjujM3--xUDoUZqHZTq09R1rR8y3-_rLL8-fQ,8014
|
|
5
|
-
meshcore/lpp_json_encoder.py,sha256=vyn7z3VYWOo_B9xmCzqblh5xCa0QKWKcMi2eOqw18RE,1875
|
|
6
|
-
meshcore/meshcore.py,sha256=n8fH7AEx7DR8BHOYL_Qex9ns6ACKklNNqYbPnNnvqyE,16110
|
|
7
|
-
meshcore/packets.py,sha256=2soo3H6FEbVdYk-ZQANIM3YpyCwsaIOrB4QIEJLLw0c,1072
|
|
8
|
-
meshcore/parsing.py,sha256=48PQkig-sqvsRlkF9zkvWhJoSq6ERCbGb_aRuCND5NI,4044
|
|
9
|
-
meshcore/reader.py,sha256=mIewSrfsgt-qe-acoxGQncHZhEhRr_3CEZuN1afnr7E,24784
|
|
10
|
-
meshcore/serial_cx.py,sha256=-kaqnqk7Ydufu2DECFfPDv4Xs-7gHUBuz8v0xf8fvvE,3969
|
|
11
|
-
meshcore/tcp_cx.py,sha256=05YRVMnjY5aVBTJcHa0uG4VfFKGbV6hQ1pPIsJg4CDI,4227
|
|
12
|
-
meshcore/commands/__init__.py,sha256=NNmkTEcL-DLyuwKLUagEzpqe3C6ui2tETbu_mUd4p-I,441
|
|
13
|
-
meshcore/commands/base.py,sha256=0bDHcNGlKoej03xJqzo_wL5ctoPQU5kcK1Ca5sZh1pk,7097
|
|
14
|
-
meshcore/commands/binary.py,sha256=MVKXrT4pFSlzFkVFLBX988Eh57tRwunw4XPKBumbQNU,4890
|
|
15
|
-
meshcore/commands/contact.py,sha256=-dRY68xZkDFP6-i53WujyWhEsXZsFRk98B3j3zBzaL4,5860
|
|
16
|
-
meshcore/commands/device.py,sha256=vstyy1crvhGbDgCAtyCWyjj3X8F2CpMw1LFTOwoHh6M,8273
|
|
17
|
-
meshcore/commands/messaging.py,sha256=MIVGM5G8J16qimKjCHdrlEiVJ4g_MsBaZKPvRJHJcLU,8306
|
|
18
|
-
meshcore-2.1.17.dist-info/METADATA,sha256=ImPYOzshM7CKAhB-wRgqE3ZoN_0wIwE4Ksk3mRykENg,25317
|
|
19
|
-
meshcore-2.1.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
20
|
-
meshcore-2.1.17.dist-info/licenses/LICENSE,sha256=o62-JWT_C-ZqEtzb1Gl_PPtPr0pVT8KDmgji_Y_bejI,1075
|
|
21
|
-
meshcore-2.1.17.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|