meshcore 1.9.7__tar.gz → 1.9.8.dev2__tar.gz
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.
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/PKG-INFO +2 -1
- meshcore-1.9.8.dev2/examples/ble_t1000_custom_vars.py +17 -0
- meshcore-1.9.8.dev2/examples/ble_t1000_set_cv.py +20 -0
- meshcore-1.9.8.dev2/examples/serial_repeater_telemetry.py +61 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/pyproject.toml +2 -2
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/commands.py +22 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/events.py +3 -1
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/packets.py +11 -1
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/reader.py +45 -1
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/.gitignore +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/LICENSE +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/README.md +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/ble_chat.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/ble_t1000_chan_msg.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/ble_t1000_infos.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/ble_t1000_msg.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/mepo_mc_gps.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/pubsub_example.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/rf_packet_monitor.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_battery_monitor.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_chat.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_contacts.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_infos.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_msg.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_repeater_status.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_trace.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/tcp_chat.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/tcp_mchome_contacts.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/tcp_mchome_infos.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/tcp_mchome_msg.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/tcp_mchome_readmsgs.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/pytest.ini +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/__init__.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/ble_cx.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/meshcore.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/serial_cx.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/tcp_cx.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/tests/README.md +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/tests/unit/test_commands.py +0 -0
- {meshcore-1.9.7 → meshcore-1.9.8.dev2}/tests/unit/test_events.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.8.dev2
|
|
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
|
|
@@ -11,6 +11,7 @@ Classifier: Operating System :: OS Independent
|
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.10
|
|
13
13
|
Requires-Dist: bleak
|
|
14
|
+
Requires-Dist: pycayennelpp
|
|
14
15
|
Requires-Dist: pyserial-asyncio
|
|
15
16
|
Provides-Extra: dev
|
|
16
17
|
Requires-Dist: pytest; extra == 'dev'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from meshcore import MeshCore
|
|
5
|
+
from meshcore import BLEConnection
|
|
6
|
+
|
|
7
|
+
ADDRESS = "T1000_S" # node ble adress or name
|
|
8
|
+
|
|
9
|
+
async def main () :
|
|
10
|
+
con = BLEConnection(ADDRESS)
|
|
11
|
+
await con.connect()
|
|
12
|
+
mc = MeshCore(con)
|
|
13
|
+
await mc.connect()
|
|
14
|
+
|
|
15
|
+
print(await mc.commands.get_custom_vars())
|
|
16
|
+
|
|
17
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from meshcore import MeshCore
|
|
5
|
+
from meshcore import BLEConnection
|
|
6
|
+
|
|
7
|
+
ADDRESS = "T1000_S" # node ble adress or name
|
|
8
|
+
VAR = "gps"
|
|
9
|
+
VALUE = "1"
|
|
10
|
+
|
|
11
|
+
async def main () :
|
|
12
|
+
con = BLEConnection(ADDRESS)
|
|
13
|
+
await con.connect()
|
|
14
|
+
mc = MeshCore(con, debug=True)
|
|
15
|
+
await mc.connect()
|
|
16
|
+
|
|
17
|
+
print(await mc.commands.set_custom_var(VAR, VALUE))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import argparse
|
|
5
|
+
|
|
6
|
+
from meshcore import MeshCore
|
|
7
|
+
from meshcore.events import EventType
|
|
8
|
+
|
|
9
|
+
async def main():
|
|
10
|
+
# Parse command line arguments
|
|
11
|
+
parser = argparse.ArgumentParser(description='Get status from a repeater via serial connection')
|
|
12
|
+
parser.add_argument('-p', '--port', required=True, help='Serial port')
|
|
13
|
+
parser.add_argument('-b', '--baudrate', type=int, default=115200, help='Baud rate')
|
|
14
|
+
parser.add_argument('-r', '--repeater', required=True, help='Repeater name')
|
|
15
|
+
parser.add_argument('-pw', '--password', required=True, help='Password for login')
|
|
16
|
+
parser.add_argument('-t', '--timeout', type=float, default=10.0, help='Timeout for responses in seconds')
|
|
17
|
+
args = parser.parse_args()
|
|
18
|
+
|
|
19
|
+
# Connect to the device
|
|
20
|
+
mc = await MeshCore.create_serial(args.port, args.baudrate, debug=True)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
# Get contacts
|
|
24
|
+
await mc.ensure_contacts()
|
|
25
|
+
repeater = mc.get_contact_by_name(args.repeater)
|
|
26
|
+
|
|
27
|
+
if repeater is None:
|
|
28
|
+
print(f"Repeater '{args.repeater}' not found in contacts.")
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
# Send login request
|
|
32
|
+
print(f"Logging in to repeater '{args.repeater}'...")
|
|
33
|
+
login_event = await mc.commands.send_login(repeater, args.password)
|
|
34
|
+
|
|
35
|
+
if login_event.type != EventType.ERROR:
|
|
36
|
+
print("Login successful")
|
|
37
|
+
|
|
38
|
+
# Send status request
|
|
39
|
+
print("Sending status request...")
|
|
40
|
+
await mc.commands.send_telemetry_req(repeater)
|
|
41
|
+
|
|
42
|
+
# Wait for status response
|
|
43
|
+
telemetry_event = await mc.wait_for_event(EventType.TELEMETRY_RESPONSE, timeout=args.timeout)
|
|
44
|
+
print(telemetry_event.payload["lpp"])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
else:
|
|
48
|
+
print("Login failed or timed out")
|
|
49
|
+
|
|
50
|
+
finally:
|
|
51
|
+
# Always disconnect properly
|
|
52
|
+
await mc.disconnect()
|
|
53
|
+
print("Disconnected from device")
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
try:
|
|
57
|
+
asyncio.run(main())
|
|
58
|
+
except KeyboardInterrupt:
|
|
59
|
+
print("\nOperation cancelled by user")
|
|
60
|
+
except Exception as e:
|
|
61
|
+
print(f"Error: {e}")
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "meshcore"
|
|
7
|
-
version = "1.9.
|
|
7
|
+
version = "1.9.8.dev2"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
|
|
10
10
|
{ name="Alex Wolden", email="awolden@gmail.com" },
|
|
@@ -18,7 +18,7 @@ classifiers = [
|
|
|
18
18
|
]
|
|
19
19
|
license = "MIT"
|
|
20
20
|
license-files = ["LICEN[CS]E*"]
|
|
21
|
-
dependencies = [ "bleak", "pyserial-asyncio" ]
|
|
21
|
+
dependencies = [ "bleak", "pyserial-asyncio", "pycayennelpp" ]
|
|
22
22
|
|
|
23
23
|
[project.optional-dependencies]
|
|
24
24
|
dev = ["pytest", "pytest-asyncio"]
|
|
@@ -306,6 +306,28 @@ class CommandHandler:
|
|
|
306
306
|
data = b"\x03\x00" + chan.to_bytes(1, 'little') + timestamp + msg.encode("utf-8")
|
|
307
307
|
return await self.send(data, [EventType.OK, EventType.ERROR])
|
|
308
308
|
|
|
309
|
+
async def send_other_params(self, manual_add_contacts : bool, telemetry_mode_base : bool, telemetry_mode_loc : bool) :
|
|
310
|
+
telemetry_mode = (telemetry_mode_base & 0b11) | ((telemetry_mode_loc & 0b11) << 2)
|
|
311
|
+
data = b"\x26" + manual_add_contacts.to_bytes(1) + telemetry_mode.to_bytes(1)
|
|
312
|
+
return await self.send(data, [EventType.OK, EventType.ERROR])
|
|
313
|
+
|
|
314
|
+
async def send_telemetry_req(self, dst: DestinationType) -> Event :
|
|
315
|
+
dst_bytes = _validate_destination(dst, prefix_length=32)
|
|
316
|
+
logger.debug(f"Asking telemetry to {dst_bytes.hex()}")
|
|
317
|
+
|
|
318
|
+
data = b"\x27\x00\x00\x00" + dst_bytes
|
|
319
|
+
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
|
|
320
|
+
|
|
321
|
+
async def get_custom_vars(self) -> Event:
|
|
322
|
+
logger.debug(f"Asking for custom vars")
|
|
323
|
+
data = b"\x28"
|
|
324
|
+
return await self.send(data, [EventType.CUSTOM_VARS, EventType.ERROR])
|
|
325
|
+
|
|
326
|
+
async def set_custom_var(self, key, value) -> Event:
|
|
327
|
+
logger.debug(f"Setting custom var {key} to {value}")
|
|
328
|
+
data = b"\x29" + key.encode("utf-8") + b":" + value.encode("utf-8")
|
|
329
|
+
return await self.send(data, [EventType.OK, EventType.ERROR])
|
|
330
|
+
|
|
309
331
|
async def send_cli(self, cmd) -> Event:
|
|
310
332
|
logger.debug(f"Sending CLI command: {cmd}")
|
|
311
333
|
data = b"\x32" + cmd.encode('utf-8')
|
|
@@ -33,6 +33,8 @@ class EventType(Enum):
|
|
|
33
33
|
LOG_DATA = "log_data"
|
|
34
34
|
TRACE_DATA = "trace_data"
|
|
35
35
|
RX_LOG_DATA = "rx_log_data"
|
|
36
|
+
TELEMETRY_RESPONSE = "telemetry_response"
|
|
37
|
+
CUSTOM_VARS = "custom_vars"
|
|
36
38
|
|
|
37
39
|
# Command response types
|
|
38
40
|
OK = "command_ok"
|
|
@@ -182,4 +184,4 @@ class EventDispatcher:
|
|
|
182
184
|
except asyncio.TimeoutError:
|
|
183
185
|
return None
|
|
184
186
|
finally:
|
|
185
|
-
subscription.unsubscribe()
|
|
187
|
+
subscription.unsubscribe()
|
|
@@ -16,6 +16,14 @@ class PacketType(Enum):
|
|
|
16
16
|
CONTACT_URI = 11
|
|
17
17
|
BATTERY = 12
|
|
18
18
|
DEVICE_INFO = 13
|
|
19
|
+
PRIVATE_KEY = 14
|
|
20
|
+
DISABLED = 15
|
|
21
|
+
CONTACT_MSG_RECV_V3 = 16
|
|
22
|
+
CHANNEL_MSG_RECV_V3 = 17
|
|
23
|
+
CHANNEL_INFO = 18
|
|
24
|
+
SIGN_START = 19
|
|
25
|
+
SIGNATURE = 20
|
|
26
|
+
CUSTOM_VARS = 21
|
|
19
27
|
CLI_RESPONSE = 50
|
|
20
28
|
|
|
21
29
|
# Push notifications
|
|
@@ -28,4 +36,6 @@ class PacketType(Enum):
|
|
|
28
36
|
LOGIN_FAILED = 0x86
|
|
29
37
|
STATUS_RESPONSE = 0x87
|
|
30
38
|
LOG_DATA = 0x88
|
|
31
|
-
TRACE_DATA = 0x89
|
|
39
|
+
TRACE_DATA = 0x89
|
|
40
|
+
PUSH_CODE_NEW_ADVERT = 0x8A
|
|
41
|
+
TELEMETRY_RESPONSE = 0x8B
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
import logging
|
|
3
3
|
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
from cayennelpp import LppFrame, LppData, LppUtil
|
|
4
6
|
from typing import Any, Optional, Dict
|
|
5
7
|
from .events import Event, EventType, EventDispatcher
|
|
6
8
|
from .packets import PacketType
|
|
@@ -71,6 +73,9 @@ class MessageReader:
|
|
|
71
73
|
self_info["public_key"] = data[4:36].hex()
|
|
72
74
|
self_info["adv_lat"] = int.from_bytes(data[36:40], byteorder='little', signed=True)/1e6
|
|
73
75
|
self_info["adv_lon"] = int.from_bytes(data[40:44], byteorder='little', signed=True)/1e6
|
|
76
|
+
self_info["telemetry_mode_loc"] = (data[46] >> 2) & 0b11
|
|
77
|
+
self_info["telemetry_mode_base"] = (data[46]) & 0b11
|
|
78
|
+
self_info["manual_add_contacts"] = data[47] > 0
|
|
74
79
|
self_info["radio_freq"] = int.from_bytes(data[48:52], byteorder='little') / 1000
|
|
75
80
|
self_info["radio_bw"] = int.from_bytes(data[52:56], byteorder='little') / 1000
|
|
76
81
|
self_info["radio_sf"] = data[56]
|
|
@@ -201,6 +206,18 @@ class MessageReader:
|
|
|
201
206
|
res["response"] = data[1:].decode()
|
|
202
207
|
await self.dispatcher.dispatch(Event(EventType.CLI_RESPONSE, res))
|
|
203
208
|
|
|
209
|
+
elif packet_type_value == PacketType.CUSTOM_VARS.value:
|
|
210
|
+
logger.debug(f"received custom vars response: {data.hex()}")
|
|
211
|
+
res = {}
|
|
212
|
+
rawdata = data[1:].decode()
|
|
213
|
+
if not rawdata == "" :
|
|
214
|
+
pairs = rawdata.split(",")
|
|
215
|
+
for p in pairs :
|
|
216
|
+
psplit = p.split(":")
|
|
217
|
+
res[psplit[0]] = psplit[1]
|
|
218
|
+
logger.debug(f"got custom vars : {res}")
|
|
219
|
+
await self.dispatcher.dispatch(Event(EventType.CUSTOM_VARS, res))
|
|
220
|
+
|
|
204
221
|
# Push notifications
|
|
205
222
|
elif packet_type_value == PacketType.ADVERTISEMENT.value:
|
|
206
223
|
logger.debug("Advertisement received")
|
|
@@ -358,7 +375,34 @@ class MessageReader:
|
|
|
358
375
|
}
|
|
359
376
|
|
|
360
377
|
await self.dispatcher.dispatch(Event(EventType.TRACE_DATA, res, attributes))
|
|
378
|
+
|
|
379
|
+
elif packet_type_value == PacketType.TELEMETRY_RESPONSE.value:
|
|
380
|
+
logger.debug(f"Received telemetry data: {data.hex()}")
|
|
381
|
+
res = {}
|
|
382
|
+
|
|
383
|
+
res["pubkey_pre"] = data[2:8].hex()
|
|
384
|
+
buf = data[8:]
|
|
385
|
+
res["data"] = buf.hex()
|
|
386
|
+
|
|
387
|
+
"""Parse a given byte string and return as a LppFrame object."""
|
|
388
|
+
i = 0
|
|
389
|
+
data = []
|
|
390
|
+
while i < len(buf) and buf[i] != 0:
|
|
391
|
+
lppdata = LppData.from_bytes(buf[i:])
|
|
392
|
+
data.append(lppdata)
|
|
393
|
+
i = i + len(lppdata)
|
|
394
|
+
|
|
395
|
+
lpp = json.loads(json.dumps(LppFrame(data), default=LppUtil.json_encode_type_str))
|
|
396
|
+
|
|
397
|
+
res["lpp"] = lpp
|
|
398
|
+
|
|
399
|
+
attributes = {
|
|
400
|
+
"raw" : buf.hex(),
|
|
401
|
+
}
|
|
361
402
|
|
|
403
|
+
await self.dispatcher.dispatch(Event(EventType.TELEMETRY_RESPONSE, res, attributes))
|
|
404
|
+
|
|
362
405
|
else:
|
|
363
406
|
logger.debug(f"Unhandled data received {data}")
|
|
364
|
-
logger.debug(f"Unhandled packet type: {packet_type_value}")
|
|
407
|
+
logger.debug(f"Unhandled packet type: {packet_type_value}")
|
|
408
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|