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.
Files changed (40) hide show
  1. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/PKG-INFO +2 -1
  2. meshcore-1.9.8.dev2/examples/ble_t1000_custom_vars.py +17 -0
  3. meshcore-1.9.8.dev2/examples/ble_t1000_set_cv.py +20 -0
  4. meshcore-1.9.8.dev2/examples/serial_repeater_telemetry.py +61 -0
  5. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/pyproject.toml +2 -2
  6. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/commands.py +22 -0
  7. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/events.py +3 -1
  8. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/packets.py +11 -1
  9. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/reader.py +45 -1
  10. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/.gitignore +0 -0
  11. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/LICENSE +0 -0
  12. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/README.md +0 -0
  13. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/ble_chat.py +0 -0
  14. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/ble_t1000_chan_msg.py +0 -0
  15. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/ble_t1000_infos.py +0 -0
  16. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/ble_t1000_msg.py +0 -0
  17. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/mepo_mc_gps.py +0 -0
  18. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/pubsub_example.py +0 -0
  19. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/rf_packet_monitor.py +0 -0
  20. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_battery_monitor.py +0 -0
  21. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_chat.py +0 -0
  22. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_contacts.py +0 -0
  23. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_infos.py +0 -0
  24. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_msg.py +0 -0
  25. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_repeater_status.py +0 -0
  26. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/serial_trace.py +0 -0
  27. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/tcp_chat.py +0 -0
  28. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/tcp_mchome_contacts.py +0 -0
  29. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/tcp_mchome_infos.py +0 -0
  30. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/tcp_mchome_msg.py +0 -0
  31. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/examples/tcp_mchome_readmsgs.py +0 -0
  32. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/pytest.ini +0 -0
  33. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/__init__.py +0 -0
  34. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/ble_cx.py +0 -0
  35. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/meshcore.py +0 -0
  36. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/serial_cx.py +0 -0
  37. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/src/meshcore/tcp_cx.py +0 -0
  38. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/tests/README.md +0 -0
  39. {meshcore-1.9.7 → meshcore-1.9.8.dev2}/tests/unit/test_commands.py +0 -0
  40. {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.7
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"
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