meshcore 2.3.8__tar.gz → 2.3.9__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 (80) hide show
  1. {meshcore-2.3.8 → meshcore-2.3.9}/PKG-INFO +4 -2
  2. {meshcore-2.3.8 → meshcore-2.3.9}/README.md +2 -0
  3. {meshcore-2.3.8 → meshcore-2.3.9}/pyproject.toml +1 -1
  4. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/commands/device.py +17 -0
  5. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/commands/messaging.py +19 -10
  6. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/events.py +1 -0
  7. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/packets.py +16 -0
  8. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/reader.py +5 -0
  9. {meshcore-2.3.8 → meshcore-2.3.9}/.github/funding.yml +0 -0
  10. {meshcore-2.3.8 → meshcore-2.3.9}/.github/python-test.yml +0 -0
  11. {meshcore-2.3.8 → meshcore-2.3.9}/.gitignore +0 -0
  12. {meshcore-2.3.8 → meshcore-2.3.9}/LICENSE +0 -0
  13. {meshcore-2.3.8 → meshcore-2.3.9}/examples/.pubsub_example.py.swp +0 -0
  14. {meshcore-2.3.8 → meshcore-2.3.9}/examples/ble_chat.py +0 -0
  15. {meshcore-2.3.8 → meshcore-2.3.9}/examples/ble_pin_pairing_example.py +0 -0
  16. {meshcore-2.3.8 → meshcore-2.3.9}/examples/ble_private_key_export.py +0 -0
  17. {meshcore-2.3.8 → meshcore-2.3.9}/examples/ble_sign_example.py +0 -0
  18. {meshcore-2.3.8 → meshcore-2.3.9}/examples/ble_stats.py +0 -0
  19. {meshcore-2.3.8 → meshcore-2.3.9}/examples/ble_t1000_chan_msg.py +0 -0
  20. {meshcore-2.3.8 → meshcore-2.3.9}/examples/ble_t1000_custom_vars.py +0 -0
  21. {meshcore-2.3.8 → meshcore-2.3.9}/examples/ble_t1000_infos.py +0 -0
  22. {meshcore-2.3.8 → meshcore-2.3.9}/examples/ble_t1000_msg.py +0 -0
  23. {meshcore-2.3.8 → meshcore-2.3.9}/examples/ble_t1000_msg_retries.py +0 -0
  24. {meshcore-2.3.8 → meshcore-2.3.9}/examples/ble_t1000_set_cv.py +0 -0
  25. {meshcore-2.3.8 → meshcore-2.3.9}/examples/chan_recv_with_path.py +0 -0
  26. {meshcore-2.3.8 → meshcore-2.3.9}/examples/connection_events_example.py +0 -0
  27. {meshcore-2.3.8 → meshcore-2.3.9}/examples/mepo_mc_gps.py +0 -0
  28. {meshcore-2.3.8 → meshcore-2.3.9}/examples/pubsub_example.py +0 -0
  29. {meshcore-2.3.8 → meshcore-2.3.9}/examples/rf_packet_monitor.py +0 -0
  30. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_battery_monitor.py +0 -0
  31. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_channel_manager.py +0 -0
  32. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_chat.py +0 -0
  33. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_contacts.py +0 -0
  34. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_infos.py +0 -0
  35. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_meshcore_ollama.py +0 -0
  36. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_msg.py +0 -0
  37. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_pingbot.py +0 -0
  38. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_repeater_status.py +0 -0
  39. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_repeater_telemetry.py +0 -0
  40. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_rss_bot.py +0 -0
  41. {meshcore-2.3.8 → meshcore-2.3.9}/examples/serial_trace.py +0 -0
  42. {meshcore-2.3.8 → meshcore-2.3.9}/examples/tcp_chat.py +0 -0
  43. {meshcore-2.3.8 → meshcore-2.3.9}/examples/tcp_login_status.py +0 -0
  44. {meshcore-2.3.8 → meshcore-2.3.9}/examples/tcp_mchome_contacts.py +0 -0
  45. {meshcore-2.3.8 → meshcore-2.3.9}/examples/tcp_mchome_infos.py +0 -0
  46. {meshcore-2.3.8 → meshcore-2.3.9}/examples/tcp_mchome_msg.py +0 -0
  47. {meshcore-2.3.8 → meshcore-2.3.9}/examples/tcp_mchome_readmsgs.py +0 -0
  48. {meshcore-2.3.8 → meshcore-2.3.9}/pytest.ini +0 -0
  49. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/__init__.py +0 -0
  50. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/ble_cx.py +0 -0
  51. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/commands/__init__.py +0 -0
  52. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/commands/base.py +0 -0
  53. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/commands/binary.py +0 -0
  54. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/commands/contact.py +0 -0
  55. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/commands/control_data.py +0 -0
  56. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/connection_manager.py +0 -0
  57. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/lpp_json_encoder.py +0 -0
  58. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/meshcore.py +0 -0
  59. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/meshcore_parser.py +0 -0
  60. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/parsing.py +0 -0
  61. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/serial_cx.py +0 -0
  62. {meshcore-2.3.8 → meshcore-2.3.9}/src/meshcore/tcp_cx.py +0 -0
  63. {meshcore-2.3.8 → meshcore-2.3.9}/tests/README.md +0 -0
  64. {meshcore-2.3.8 → meshcore-2.3.9}/tests/test_ble_connection.py +0 -0
  65. {meshcore-2.3.8 → meshcore-2.3.9}/tests/test_ble_pin_pairing.py +0 -0
  66. {meshcore-2.3.8 → meshcore-2.3.9}/tests/test_meshcore_ble_pin.py +0 -0
  67. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_asyncio_lifecycle.py +0 -0
  68. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_commands.py +0 -0
  69. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_connection_manager.py +0 -0
  70. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_error_handling.py +0 -0
  71. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_events.py +0 -0
  72. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_lpp_parsing.py +0 -0
  73. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_parse_status.py +0 -0
  74. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_path_discovery_response.py +0 -0
  75. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_private_key_export.py +0 -0
  76. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_protocol_surface_gaps.py +0 -0
  77. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_reader.py +0 -0
  78. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_serial_connection.py +0 -0
  79. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_standalone_fixes.py +0 -0
  80. {meshcore-2.3.8 → meshcore-2.3.9}/tests/unit/test_transport_symmetry.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: meshcore
3
- Version: 2.3.8
3
+ Version: 2.3.9
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
@@ -541,6 +541,8 @@ All commands are async methods that return `Event` objects. Commands are organiz
541
541
  | **Device Actions** ||||
542
542
  | `send_advert(flood=False)` | `flood: bool` | `OK` | Send advertisement (optionally flood network) |
543
543
  | `reboot()` | None | None | Reboot device (no response expected) |
544
+ | `run_cli_command()` | `cmd: str` | `CLI_REPLY/ERROR` | Send a cli command to device |
545
+ | `send_raw_packet()` | `data: str/bytes, priority=0` | `OK/ERROR` | Send a raw packet through device, can be an hex string or bytes |
544
546
  | **Security** ||||
545
547
  | `export_private_key()` | None | `PRIVATE_KEY/DISABLED` | Export device private key (requires PIN auth & enabled firmware) |
546
548
  | `import_private_key(key)` | `key: bytes` | `OK` | Import private key to device |
@@ -518,6 +518,8 @@ All commands are async methods that return `Event` objects. Commands are organiz
518
518
  | **Device Actions** ||||
519
519
  | `send_advert(flood=False)` | `flood: bool` | `OK` | Send advertisement (optionally flood network) |
520
520
  | `reboot()` | None | None | Reboot device (no response expected) |
521
+ | `run_cli_command()` | `cmd: str` | `CLI_REPLY/ERROR` | Send a cli command to device |
522
+ | `send_raw_packet()` | `data: str/bytes, priority=0` | `OK/ERROR` | Send a raw packet through device, can be an hex string or bytes |
521
523
  | **Security** ||||
522
524
  | `export_private_key()` | None | `PRIVATE_KEY/DISABLED` | Export device private key (requires PIN auth & enabled firmware) |
523
525
  | `import_private_key(key)` | `key: bytes` | `OK` | Import private key to device |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "meshcore"
7
- version = "2.3.8"
7
+ version = "2.3.9"
8
8
  authors = [
9
9
  { name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
10
10
  { name="Alex Wolden", email="awolden@gmail.com" },
@@ -20,6 +20,23 @@ class DeviceCommands(CommandHandlerBase):
20
20
  logger.debug("Sending device query command")
21
21
  return await self.send(b"\x16\x03", [EventType.DEVICE_INFO, EventType.ERROR])
22
22
 
23
+ async def run_cli_command(self, cmd: str) -> Event:
24
+ logger.debug(f"Sending cli command: {cmd}")
25
+ cmd_data = bytearray([CommandType.RUN_CLI_COMMAND.value])
26
+ cmd_data.extend(cmd.encode("utf-8"))
27
+ return await self.send(cmd_data, [EventType.CLI_REPLY, EventType.ERROR])
28
+
29
+ async def send_raw_packet(self, data, priority=0):
30
+ if isinstance(data, str): # got a string (assuming hex), change it to bytes
31
+ data=bytes.fromhex(data)
32
+
33
+ cmd_data = bytearray([CommandType.SEND_RAW_PACKET.value])
34
+ cmd_data.append(priority)
35
+ cmd_data.extend(data)
36
+
37
+ logger.debug(f"Sending raw packet {data.hex()}")
38
+ return await self.send(cmd_data, [EventType.OK, EventType.ERROR])
39
+
23
40
  async def send_advert(self, flood: bool = False) -> Event:
24
41
  logger.debug(f"Sending advertisement command (flood={flood})")
25
42
  if flood:
@@ -4,7 +4,7 @@ from typing import Optional, Union
4
4
  from hashlib import sha256
5
5
 
6
6
  from ..events import Event, EventType
7
- from ..packets import CommandType
7
+ from ..packets import CommandType, TxtType, AdvType
8
8
  from .base import CommandHandlerBase, DestinationType, _validate_destination
9
9
 
10
10
  logger = logging.getLogger("meshcore")
@@ -61,23 +61,32 @@ class MessagingCommands(CommandHandlerBase):
61
61
  return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
62
62
 
63
63
  async def send_cmd(
64
- self, dst: DestinationType, cmd: str, timestamp: Optional[int] = None
64
+ self, dst: DestinationType, cmd: str, timestamp: Optional[int] = None,
65
+ dst_type = None # if None, will have to get a contact to know this
65
66
  ) -> Event:
66
67
  dst_bytes = _validate_destination(dst)
67
68
  logger.debug(f"Sending command to {dst_bytes.hex()}: {cmd}")
68
69
 
69
70
  if timestamp is None:
70
71
  import time
71
-
72
72
  timestamp = int(time.time())
73
73
 
74
- data = (
75
- b"\x02\x01\x00"
76
- + timestamp.to_bytes(4, "little")
77
- + dst_bytes
78
- + cmd.encode("utf-8")
79
- )
80
- return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
74
+ if dst_type is None:
75
+ dst_type = dst["type"]
76
+ else: # assume destination is a repeater
77
+ logger.warning("Can't determine destination type, please ensure contacts first or specify dst_type when calling `send_cmd`. Assuming it's a repeater.")
78
+ dst_type = AdvType.REPEATER.value
79
+
80
+ cmd_data = bytearray([CommandType.SEND_TXT_MSG.value])
81
+ if dst_type == AdvType.REPEATER.value :
82
+ cmd_data.append(TxtType.CLI_DATA.value)
83
+ else:
84
+ cmd_data.append(TxtType.CLI_CMD.value)
85
+ cmd_data.append(0) # first and only attempt
86
+ cmd_data.extend(timestamp.to_bytes(4, "little"))
87
+ cmd_data.extend(dst_bytes)
88
+ cmd_data.extend(cmd.encode("utf-8"))
89
+ return await self.send(cmd_data, [EventType.MSG_SENT, EventType.ERROR])
81
90
 
82
91
  async def send_msg(
83
92
  self, dst: DestinationType, msg: str, timestamp: Optional[int] = None,
@@ -25,6 +25,7 @@ class EventType(Enum):
25
25
  NEXT_CONTACT = "next_contact"
26
26
  AUTOADD_CONFIG = "autoadd_config"
27
27
  ADVERT_PATH = "advert_path"
28
+ CLI_REPLY = "cli_reply"
28
29
 
29
30
  # Push notifications
30
31
  ADVERTISEMENT = "advertisement"
@@ -1,5 +1,18 @@
1
1
  from enum import Enum
2
2
 
3
+ class AdvType(Enum):
4
+ NONE = 0x00
5
+ CHAT = 0x01
6
+ REPEATER = 0x02
7
+ ROOM = 0x03
8
+ SENSOR = 0x04
9
+
10
+ class TxtType(Enum):
11
+ PLAIN = 0x00
12
+ CLI_DATA = 0x01
13
+ SIGNED_PLAIN = 0x02
14
+ CLI_CMD = 0x03
15
+
3
16
  class AnonReqType(Enum):
4
17
  REGIONS = 0x01
5
18
  OWNER = 0x02
@@ -75,6 +88,8 @@ class CommandType(Enum):
75
88
  SET_PATH_HASH_MODE = 61
76
89
  SET_DEFAULT_FLOOD_SCOPE = 63
77
90
  GET_DEFAULT_FLOOD_SCOPE = 64
91
+ SEND_RAW_PACKET = 65
92
+ RUN_CLI_COMMAND = 66
78
93
 
79
94
  # Packet prefixes for the protocol
80
95
  class PacketType(Enum):
@@ -107,6 +122,7 @@ class PacketType(Enum):
107
122
  ALLOWED_REPEAT_FREQ = 26
108
123
  CHANNEL_DATA_RECV = 27
109
124
  DEFAULT_FLOOD_SCOPE = 28
125
+ CLI_REPLY = 29
110
126
 
111
127
  # Push notifications
112
128
  ADVERTISEMENT = 0x80
@@ -1052,6 +1052,11 @@ class MessageReader:
1052
1052
  res = {"rx_delay": rx_delay, "airtime_factor": airtime_factor}
1053
1053
  await self.dispatcher.dispatch(Event(EventType.TUNING_PARAMS, res))
1054
1054
 
1055
+ elif packet_type_value == PacketType.CLI_REPLY.value:
1056
+ res = {}
1057
+ res["text"] = data[1:].decode("utf-8", "ignore")
1058
+ await self.dispatcher.dispatch(Event(EventType.CLI_REPLY, res))
1059
+
1055
1060
  else:
1056
1061
  logger.debug(f"Unhandled data received {data}")
1057
1062
  logger.debug(f"Unhandled packet type: {packet_type_value}")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes