mirror of
https://github.com/meshcore-dev/meshcore_py.git
synced 2026-04-20 22:13:49 +00:00
Change contract for commands to return full event
This commit is contained in:
parent
39ea3cb3f3
commit
6fbf15885d
17 changed files with 231 additions and 104 deletions
126
README.md
126
README.md
|
|
@ -21,7 +21,12 @@ async def main():
|
|||
meshcore = await MeshCore.create_serial("/dev/ttyUSB0")
|
||||
|
||||
# Get your contacts
|
||||
contacts = await meshcore.commands.get_contacts()
|
||||
result = await meshcore.commands.get_contacts()
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error getting contacts: {result.payload}")
|
||||
return
|
||||
|
||||
contacts = result.payload
|
||||
print(f"Found {len(contacts)} contacts")
|
||||
|
||||
# Send a message to the first contact
|
||||
|
|
@ -30,7 +35,12 @@ async def main():
|
|||
contact = next(iter(contacts.items()))[1]
|
||||
|
||||
# Pass the contact object directly to send_msg
|
||||
await meshcore.commands.send_msg(contact, "Hello from Python!")
|
||||
result = await meshcore.commands.send_msg(contact, "Hello from Python!")
|
||||
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error sending message: {result.payload}")
|
||||
else:
|
||||
print("Message sent successfully!")
|
||||
|
||||
await meshcore.disconnect()
|
||||
|
||||
|
|
@ -55,6 +65,37 @@ python examples/pubsub_example.py -p /dev/ttyUSB0
|
|||
|
||||
## Usage Guide
|
||||
|
||||
### Command Return Values
|
||||
|
||||
All command methods in MeshCore return an `Event` object that contains both the event type and its payload. This allows for consistent error handling and type checking:
|
||||
|
||||
```python
|
||||
# Command result structure
|
||||
result = await meshcore.commands.some_command()
|
||||
|
||||
# Check if the command was successful or resulted in an error
|
||||
if result.type == EventType.ERROR:
|
||||
# Handle error case
|
||||
print(f"Command failed: {result.payload}")
|
||||
else:
|
||||
# Handle success case - the event type will be specific to the command
|
||||
# (e.g., EventType.DEVICE_INFO, EventType.CONTACTS, EventType.MSG_SENT)
|
||||
print(f"Command succeeded with event type: {result.type}")
|
||||
# Access the payload data
|
||||
data = result.payload
|
||||
```
|
||||
|
||||
Common error handling pattern:
|
||||
|
||||
```python
|
||||
result = await meshcore.commands.send_msg(contact, "Hello!")
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error sending message: {result.payload}")
|
||||
else:
|
||||
# For send_msg, a successful result will have type EventType.MSG_SENT
|
||||
print(f"Message sent with expected ack: {result.payload['expected_ack'].hex()}")
|
||||
```
|
||||
|
||||
### Connecting to Your Device
|
||||
|
||||
Connect via Serial, BLE, or TCP:
|
||||
|
|
@ -76,20 +117,34 @@ Send commands and wait for responses:
|
|||
|
||||
```python
|
||||
# Get device information
|
||||
device_info = await meshcore.commands.send_device_query()
|
||||
print(f"Device model: {device_info['model']}")
|
||||
result = await meshcore.commands.send_device_query()
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error getting device info: {result.payload}")
|
||||
else:
|
||||
print(f"Device model: {result.payload['model']}")
|
||||
|
||||
# Get list of contacts
|
||||
contacts = await meshcore.commands.get_contacts()
|
||||
for contact_id, contact in contacts.items():
|
||||
print(f"Contact: {contact['adv_name']} ({contact_id})")
|
||||
result = await meshcore.commands.get_contacts()
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error getting contacts: {result.payload}")
|
||||
else:
|
||||
contacts = result.payload
|
||||
for contact_id, contact in contacts.items():
|
||||
print(f"Contact: {contact['adv_name']} ({contact_id})")
|
||||
|
||||
# Send a message (destination key in bytes)
|
||||
await meshcore.commands.send_msg(dst_key, "Hello!")
|
||||
result = await meshcore.commands.send_msg(dst_key, "Hello!")
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error sending message: {result.payload}")
|
||||
|
||||
# Setting device parameters
|
||||
await meshcore.commands.set_name("My Device")
|
||||
await meshcore.commands.set_tx_power(20) # Set transmit power
|
||||
result = await meshcore.commands.set_name("My Device")
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error setting name: {result.payload}")
|
||||
|
||||
result = await meshcore.commands.set_tx_power(20) # Set transmit power
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error setting TX power: {result.payload}")
|
||||
```
|
||||
|
||||
### Finding Contacts
|
||||
|
|
@ -151,7 +206,11 @@ async def send_and_confirm_message(meshcore, dst_key, message):
|
|||
sent_result = await meshcore.commands.send_msg(dst_key, message)
|
||||
|
||||
# Extract the expected acknowledgment code from the message sent event
|
||||
expected_ack = sent_result["expected_ack"].hex()
|
||||
if sent_result.type == EventType.ERROR:
|
||||
print(f"Error sending message: {sent_result.payload}")
|
||||
return False
|
||||
|
||||
expected_ack = sent_result.payload["expected_ack"].hex()
|
||||
print(f"Message sent, waiting for ack with code: {expected_ack}")
|
||||
|
||||
# Wait specifically for this acknowledgment
|
||||
|
|
@ -196,7 +255,10 @@ async def main():
|
|||
while True:
|
||||
# Send command (returns battery level)
|
||||
result = await meshcore.commands.get_bat()
|
||||
print(f"Battery check initiated, response: {result}")
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error checking battery: {result.payload}")
|
||||
else:
|
||||
print(f"Battery level: {result.payload.get('level', 'unknown')}%")
|
||||
await asyncio.sleep(60) # Wait 60 seconds between checks
|
||||
|
||||
# Start the background task
|
||||
|
|
@ -257,24 +319,36 @@ Commands that require a destination (`send_msg`, `send_login`, `send_statusreq`,
|
|||
|
||||
```python
|
||||
# Get contacts and send to a specific one
|
||||
contacts = await meshcore.commands.get_contacts()
|
||||
for key, contact in contacts.items():
|
||||
if contact["adv_name"] == "Alice":
|
||||
# Option 1: Pass the contact object directly
|
||||
await meshcore.commands.send_msg(contact, "Hello Alice!")
|
||||
|
||||
# Option 2: Use the public key string
|
||||
await meshcore.commands.send_msg(contact["public_key"], "Hello again Alice!")
|
||||
|
||||
# Option 3 (backward compatible): Convert the hex key to bytes
|
||||
dst_key = bytes.fromhex(contact["public_key"])
|
||||
await meshcore.commands.send_msg(dst_key, "Hello once more Alice!")
|
||||
break
|
||||
result = await meshcore.commands.get_contacts()
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error getting contacts: {result.payload}")
|
||||
else:
|
||||
contacts = result.payload
|
||||
for key, contact in contacts.items():
|
||||
if contact["adv_name"] == "Alice":
|
||||
# Option 1: Pass the contact object directly
|
||||
result = await meshcore.commands.send_msg(contact, "Hello Alice!")
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error sending message: {result.payload}")
|
||||
|
||||
# Option 2: Use the public key string
|
||||
result = await meshcore.commands.send_msg(contact["public_key"], "Hello again Alice!")
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error sending message: {result.payload}")
|
||||
|
||||
# Option 3 (backward compatible): Convert the hex key to bytes
|
||||
dst_key = bytes.fromhex(contact["public_key"])
|
||||
result = await meshcore.commands.send_msg(dst_key, "Hello once more Alice!")
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error sending message: {result.payload}")
|
||||
break
|
||||
|
||||
# You can also directly use a contact found by name
|
||||
contact = meshcore.get_contact_by_name("Bob")
|
||||
if contact:
|
||||
await meshcore.commands.send_msg(contact, "Hello Bob!")
|
||||
result = await meshcore.commands.send_msg(contact, "Hello Bob!")
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error sending message: {result.payload}")
|
||||
```
|
||||
|
||||
### Monitoring Channel Messages
|
||||
|
|
|
|||
|
|
@ -69,8 +69,12 @@ async def main () :
|
|||
else :
|
||||
if line.startswith("send") :
|
||||
line = line[5:]
|
||||
ret = await mc.commands.send_msg(contact , line)
|
||||
exp_ack = ret["expected_ack"].hex()
|
||||
result = await mc.commands.send_msg(contact, line)
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"⚠️ Failed to send message: {result.payload}")
|
||||
continue
|
||||
|
||||
exp_ack = result.payload["expected_ack"].hex()
|
||||
print(" Sent ... ", end="", flush=True)
|
||||
res = await mc.wait_for_event(EventType.ACK, attribute_filters={"code": exp_ack}, timeout=5)
|
||||
if res is None :
|
||||
|
|
|
|||
|
|
@ -45,7 +45,11 @@ async def main():
|
|||
print("Connected to MeshCore device")
|
||||
|
||||
# Get contacts
|
||||
contacts = await meshcore.commands.get_contacts()
|
||||
result = await meshcore.commands.get_contacts()
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error fetching contacts: {result.payload}")
|
||||
return
|
||||
contacts = result.payload
|
||||
if contacts:
|
||||
print(f"\nFound {len(contacts)} contacts:")
|
||||
for name, contact in contacts.items():
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ async def handle_message(event):
|
|||
data = event.payload
|
||||
|
||||
contact = mc.get_contact_by_key_prefix(data['pubkey_prefix'])
|
||||
|
||||
if contact is None:
|
||||
print(f"Unknown contact with pubkey prefix: {data['pubkey_prefix']}")
|
||||
return
|
||||
print(f"{contact['adv_name']}: {data['text']}")
|
||||
|
||||
async def main () :
|
||||
|
|
@ -54,7 +56,7 @@ async def main () :
|
|||
if line.startswith("to ") :
|
||||
dest = line[3:]
|
||||
nc = mc.get_contact_by_name(dest)
|
||||
if mc is None:
|
||||
if nc is None:
|
||||
print(f"Contact '{DEST}' not found in contacts.")
|
||||
return
|
||||
else :
|
||||
|
|
@ -72,8 +74,12 @@ async def main () :
|
|||
else :
|
||||
if line.startswith("send") :
|
||||
line = line[5:]
|
||||
ret = await mc.commands.send_msg(contact , line)
|
||||
exp_ack = ret["expected_ack"].hex()
|
||||
result = await mc.commands.send_msg(contact, line)
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"⚠️ Failed to send message: {result.payload}")
|
||||
continue
|
||||
|
||||
exp_ack = result.payload["expected_ack"].hex()
|
||||
print(" Sent ... ", end="", flush=True)
|
||||
res = await mc.wait_for_event(EventType.ACK, attribute_filters={"code": exp_ack}, timeout=5)
|
||||
if res is None :
|
||||
|
|
@ -82,7 +88,7 @@ async def main () :
|
|||
print ("Ack")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
meshcore.stop()
|
||||
mc.stop()
|
||||
print("\nExiting...")
|
||||
except asyncio.CancelledError:
|
||||
# Handle task cancellation from KeyboardInterrupt in asyncio.run()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import json
|
|||
|
||||
from meshcore import MeshCore
|
||||
from meshcore import SerialConnection
|
||||
from meshcore import EventType
|
||||
|
||||
PORT = "/dev/ttyUSB0"
|
||||
BAUDRATE = 115200
|
||||
|
|
@ -16,6 +17,10 @@ async def main () :
|
|||
mc = MeshCore(con)
|
||||
await mc.connect()
|
||||
|
||||
print(json.dumps(await mc.commands.get_contacts(),indent=4))
|
||||
result = await mc.commands.get_contacts()
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error getting contacts: {result.payload}")
|
||||
else:
|
||||
print(json.dumps(result.payload, indent=4))
|
||||
|
||||
asyncio.run(main())
|
||||
|
|
|
|||
|
|
@ -37,13 +37,17 @@ async def main():
|
|||
|
||||
# Send the message and get the MSG_SENT event
|
||||
print(f"Sending message: '{args.message}'")
|
||||
send_result = await mc.commands.send_msg(
|
||||
result = await mc.commands.send_msg(
|
||||
contact,
|
||||
args.message
|
||||
)
|
||||
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"⚠️ Failed to send message: {result.payload}")
|
||||
return
|
||||
|
||||
# Extract the expected ACK code
|
||||
expected_ack = send_result["expected_ack"].hex()
|
||||
expected_ack = result.payload["expected_ack"].hex()
|
||||
print(f"Message sent, waiting for ACK with code: {expected_ack}")
|
||||
|
||||
# Wait for the specific ACK that matches our message
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ async def main():
|
|||
print(f"Logging in to repeater '{args.repeater}'...")
|
||||
login_event = await mc.commands.send_login(repeater, args.password)
|
||||
|
||||
if login_event and login_event.get("success") != False:
|
||||
if login_event.type != EventType.ERROR:
|
||||
print("Login successful")
|
||||
|
||||
# Send status request
|
||||
|
|
|
|||
|
|
@ -34,10 +34,10 @@ async def main():
|
|||
tag = random.randint(1, 0xFFFFFFFF)
|
||||
result = await mc.commands.send_trace(path=args.path, tag=tag)
|
||||
|
||||
# Check if the result has a success indicator
|
||||
if result.get("success") == False:
|
||||
print(f"Failed to send trace packet: {result.get('reason', 'unknown error')}")
|
||||
elif result:
|
||||
# Check if the result is an error
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Failed to send trace packet: {result.payload.get('reason', 'unknown error')}")
|
||||
elif result.type == EventType.MSG_SENT:
|
||||
print(f"Trace packet sent successfully with tag={tag}")
|
||||
print("Waiting for trace response matching our tag...")
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ async def handle_message(event):
|
|||
data = event.payload
|
||||
|
||||
contact = mc.get_contact_by_key_prefix(data['pubkey_prefix'])
|
||||
|
||||
if contact is None:
|
||||
print(f"Unknown contact with pubkey prefix: {data['pubkey_prefix']}")
|
||||
return
|
||||
print(f"{contact['adv_name']}: {data['text']}")
|
||||
|
||||
async def main () :
|
||||
|
|
@ -54,7 +56,7 @@ async def main () :
|
|||
if line.startswith("to ") :
|
||||
dest = line[3:]
|
||||
nc = mc.get_contact_by_name(dest)
|
||||
if mc is None:
|
||||
if nc is None:
|
||||
print(f"Contact '{DEST}' not found in contacts.")
|
||||
return
|
||||
else :
|
||||
|
|
@ -72,8 +74,12 @@ async def main () :
|
|||
else :
|
||||
if line.startswith("send") :
|
||||
line = line[5:]
|
||||
ret = await mc.commands.send_msg(contact , line)
|
||||
exp_ack = ret["expected_ack"].hex()
|
||||
result = await mc.commands.send_msg(contact, line)
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"⚠️ Failed to send message: {result.payload}")
|
||||
continue
|
||||
|
||||
exp_ack = result.payload["expected_ack"].hex()
|
||||
print(" Sent ... ", end="", flush=True)
|
||||
res = await mc.wait_for_event(EventType.ACK, attribute_filters={"code": exp_ack}, timeout=5)
|
||||
if res is None :
|
||||
|
|
@ -82,7 +88,7 @@ async def main () :
|
|||
print ("Ack")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
meshcore.stop()
|
||||
mc.stop()
|
||||
print("\nExiting...")
|
||||
except asyncio.CancelledError:
|
||||
# Handle task cancellation from KeyboardInterrupt in asyncio.run()
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import asyncio
|
|||
import json
|
||||
from meshcore import TCPConnection
|
||||
from meshcore import MeshCore
|
||||
from meshcore import EventType
|
||||
|
||||
HOSTNAME = "mchome"
|
||||
PORT = 5000
|
||||
|
|
@ -14,5 +15,9 @@ async def main () :
|
|||
mc = MeshCore(con)
|
||||
await mc.connect()
|
||||
|
||||
print(json.dumps(await mc.commands.get_contacts(),indent=4))
|
||||
result = await mc.commands.get_contacts()
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"Error getting contacts: {result.payload}")
|
||||
else:
|
||||
print(json.dumps(result.payload, indent=4))
|
||||
asyncio.run(main())
|
||||
|
|
|
|||
|
|
@ -22,9 +22,14 @@ async def main () :
|
|||
if contact is None:
|
||||
print(f"Contact '{DEST}' not found in contacts.")
|
||||
return
|
||||
ret = await mc.commands.send_msg(contact ,MSG)
|
||||
print (ret)
|
||||
exp_ack = ret["expected_ack"].hex()
|
||||
result = await mc.commands.send_msg(contact, MSG)
|
||||
print(result)
|
||||
|
||||
if result.type == EventType.ERROR:
|
||||
print(f"⚠️ Failed to send message: {result.payload}")
|
||||
return
|
||||
|
||||
exp_ack = result.payload["expected_ack"].hex()
|
||||
print(await mc.wait_for_event(EventType.ACK, attribute_filters={"code": exp_ack}, timeout=5))
|
||||
|
||||
asyncio.run(main())
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import asyncio
|
|||
import json
|
||||
from meshcore import TCPConnection
|
||||
from meshcore import MeshCore
|
||||
from meshcore import EventType
|
||||
|
||||
HOSTNAME = "mchome"
|
||||
PORT = 5000
|
||||
|
|
@ -19,9 +20,12 @@ async def main () :
|
|||
res = True
|
||||
while res:
|
||||
result = await mc.commands.get_msg()
|
||||
if result.get("success") == False:
|
||||
if result.type == EventType.NO_MORE_MSGS:
|
||||
res = False
|
||||
print("No more messages")
|
||||
print (result)
|
||||
elif result.type == EventType.ERROR:
|
||||
res = False
|
||||
print(f"Error retrieving messages: {result.payload}")
|
||||
print(result)
|
||||
|
||||
asyncio.run(main())
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import asyncio
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from .events import EventType
|
||||
import random
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from .events import Event, EventType
|
||||
|
||||
# Define types for destination parameters
|
||||
DestinationType = Union[bytes, str, Dict[str, Any]]
|
||||
|
|
@ -66,7 +66,7 @@ class CommandHandler:
|
|||
self.dispatcher = dispatcher
|
||||
|
||||
async def send(self, data: bytes, expected_events: Optional[Union[EventType, List[EventType]]] = None,
|
||||
timeout: Optional[float] = None) -> Dict[str, Any]:
|
||||
timeout: Optional[float] = None) -> Event:
|
||||
"""
|
||||
Send a command and wait for expected event responses.
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ class CommandHandler:
|
|||
timeout: Timeout in seconds, or None to use default_timeout
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: Dictionary containing the response data or status
|
||||
Event: The full event object that was received in response to the command
|
||||
"""
|
||||
if not self.dispatcher:
|
||||
raise RuntimeError("Dispatcher not set, cannot send commands")
|
||||
|
|
@ -119,66 +119,68 @@ class CommandHandler:
|
|||
for future in done:
|
||||
event = await future
|
||||
if event:
|
||||
return event.payload
|
||||
return event
|
||||
|
||||
return {"success": False, "reason": "no_event_received"}
|
||||
# Create an error event when no event is received
|
||||
return Event(EventType.ERROR, {"reason": "no_event_received"})
|
||||
except asyncio.TimeoutError:
|
||||
logger.debug(f"Command timed out {data}")
|
||||
return {"success": False, "reason": "timeout"}
|
||||
return Event(EventType.ERROR, {"reason": "timeout"})
|
||||
except Exception as e:
|
||||
logger.debug(f"Command error: {e}")
|
||||
return {"error": str(e)}
|
||||
return {"success": True}
|
||||
return Event(EventType.ERROR, {"error": str(e)})
|
||||
# For commands that don't expect events, return a success event
|
||||
return Event(EventType.OK, {})
|
||||
|
||||
|
||||
async def send_appstart(self) -> Dict[str, Any]:
|
||||
async def send_appstart(self) -> Event:
|
||||
logger.debug("Sending appstart command")
|
||||
b1 = bytearray(b'\x01\x03 mccli')
|
||||
return await self.send(b1, [EventType.SELF_INFO])
|
||||
|
||||
async def send_device_query(self) -> Dict[str, Any]:
|
||||
async def send_device_query(self) -> Event:
|
||||
logger.debug("Sending device query command")
|
||||
return await self.send(b"\x16\x03", [EventType.DEVICE_INFO, EventType.ERROR])
|
||||
|
||||
async def send_advert(self, flood: bool = False) -> Dict[str, Any]:
|
||||
async def send_advert(self, flood: bool = False) -> Event:
|
||||
logger.debug(f"Sending advertisement command (flood={flood})")
|
||||
if flood:
|
||||
return await self.send(b"\x07\x01", [EventType.OK, EventType.ERROR])
|
||||
else:
|
||||
return await self.send(b"\x07", [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def set_name(self, name: str) -> Dict[str, Any]:
|
||||
async def set_name(self, name: str) -> Event:
|
||||
logger.debug(f"Setting device name to: {name}")
|
||||
return await self.send(b'\x08' + name.encode("ascii"), [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def set_coords(self, lat: float, lon: float) -> Dict[str, Any]:
|
||||
async def set_coords(self, lat: float, lon: float) -> Event:
|
||||
logger.debug(f"Setting coordinates to: lat={lat}, lon={lon}")
|
||||
return await self.send(b'\x0e'\
|
||||
+ int(lat*1e6).to_bytes(4, 'little', signed=True)\
|
||||
+ int(lon*1e6).to_bytes(4, 'little', signed=True)\
|
||||
+ int(0).to_bytes(4, 'little'), [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def reboot(self) -> Dict[str, Any]:
|
||||
async def reboot(self) -> Event:
|
||||
logger.debug("Sending reboot command")
|
||||
return await self.send(b'\x13reboot')
|
||||
|
||||
async def get_bat(self) -> Dict[str, Any]:
|
||||
async def get_bat(self) -> Event:
|
||||
logger.debug("Getting battery information")
|
||||
return await self.send(b'\x14', [EventType.BATTERY, EventType.ERROR])
|
||||
|
||||
async def get_time(self) -> Dict[str, Any]:
|
||||
async def get_time(self) -> Event:
|
||||
logger.debug("Getting device time")
|
||||
return await self.send(b"\x05", [EventType.CURRENT_TIME, EventType.ERROR])
|
||||
|
||||
async def set_time(self, val: int) -> Dict[str, Any]:
|
||||
async def set_time(self, val: int) -> Event:
|
||||
logger.debug(f"Setting device time to: {val}")
|
||||
return await self.send(b"\x06" + int(val).to_bytes(4, 'little'), [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def set_tx_power(self, val: int) -> Dict[str, Any]:
|
||||
async def set_tx_power(self, val: int) -> Event:
|
||||
logger.debug(f"Setting TX power to: {val}")
|
||||
return await self.send(b"\x0c" + int(val).to_bytes(4, 'little'), [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def set_radio(self, freq: float, bw: float, sf: int, cr: int) -> Dict[str, Any]:
|
||||
async def set_radio(self, freq: float, bw: float, sf: int, cr: int) -> Event:
|
||||
logger.debug(f"Setting radio params: freq={freq}, bw={bw}, sf={sf}, cr={cr}")
|
||||
return await self.send(b"\x0b" \
|
||||
+ int(float(freq)*1000).to_bytes(4, 'little')\
|
||||
|
|
@ -186,7 +188,7 @@ class CommandHandler:
|
|||
+ int(sf).to_bytes(1, 'little')\
|
||||
+ int(cr).to_bytes(1, 'little'), [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def set_tuning(self, rx_dly: int, af: int) -> Dict[str, Any]:
|
||||
async def set_tuning(self, rx_dly: int, af: int) -> Event:
|
||||
logger.debug(f"Setting tuning params: rx_dly={rx_dly}, af={af}")
|
||||
return await self.send(b"\x15" \
|
||||
+ int(rx_dly).to_bytes(4, 'little')\
|
||||
|
|
@ -194,28 +196,28 @@ class CommandHandler:
|
|||
+ int(0).to_bytes(1, 'little')\
|
||||
+ int(0).to_bytes(1, 'little'), [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def set_devicepin(self, pin: int) -> Dict[str, Any]:
|
||||
async def set_devicepin(self, pin: int) -> Event:
|
||||
logger.debug(f"Setting device PIN to: {pin}")
|
||||
return await self.send(b"\x25" \
|
||||
+ int(pin).to_bytes(4, 'little'), [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def get_contacts(self) -> Dict[str, Any]:
|
||||
async def get_contacts(self) -> Event:
|
||||
logger.debug("Getting contacts")
|
||||
return await self.send(b"\x04", [EventType.CONTACTS, EventType.ERROR])
|
||||
|
||||
async def reset_path(self, key: DestinationType) -> Dict[str, Any]:
|
||||
async def reset_path(self, key: DestinationType) -> Event:
|
||||
key_bytes = _validate_destination(key, prefix_length=32)
|
||||
logger.debug(f"Resetting path for contact: {key_bytes.hex()}")
|
||||
data = b"\x0D" + key_bytes
|
||||
return await self.send(data, [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def share_contact(self, key: DestinationType) -> Dict[str, Any]:
|
||||
async def share_contact(self, key: DestinationType) -> Event:
|
||||
key_bytes = _validate_destination(key, prefix_length=32)
|
||||
logger.debug(f"Sharing contact: {key_bytes.hex()}")
|
||||
data = b"\x10" + key_bytes
|
||||
return await self.send(data, [EventType.CONTACT_SHARE, EventType.ERROR])
|
||||
|
||||
async def export_contact(self, key: Optional[DestinationType] = None) -> Dict[str, Any]:
|
||||
async def export_contact(self, key: Optional[DestinationType] = None) -> Event:
|
||||
if key:
|
||||
key_bytes = _validate_destination(key, prefix_length=32)
|
||||
logger.debug(f"Exporting contact: {key_bytes.hex()}")
|
||||
|
|
@ -225,13 +227,13 @@ class CommandHandler:
|
|||
data = b"\x11"
|
||||
return await self.send(data, [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def remove_contact(self, key: DestinationType) -> Dict[str, Any]:
|
||||
async def remove_contact(self, key: DestinationType) -> Event:
|
||||
key_bytes = _validate_destination(key, prefix_length=32)
|
||||
logger.debug(f"Removing contact: {key_bytes.hex()}")
|
||||
data = b"\x0f" + key_bytes
|
||||
return await self.send(data, [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def change_contact_path (self, contact, path) -> Dict[str, Any]:
|
||||
async def change_contact_path (self, contact, path) -> Event:
|
||||
out_path_hex = path
|
||||
out_path_len = int(len(path) / 2)
|
||||
out_path_hex = out_path_hex + (128-len(out_path_hex)) * "0"
|
||||
|
|
@ -249,29 +251,29 @@ class CommandHandler:
|
|||
+ int(contact["adv_lon"]*1e6).to_bytes(4, 'little', signed=True)
|
||||
return await self.send(data, [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def get_msg(self, timeout: Optional[float] = 1) -> Dict[str, Any]:
|
||||
async def get_msg(self, timeout: Optional[float] = 1) -> Event:
|
||||
logger.debug("Requesting pending messages")
|
||||
return await self.send(b"\x0A", [EventType.CONTACT_MSG_RECV, EventType.CHANNEL_MSG_RECV, EventType.ERROR], timeout)
|
||||
|
||||
async def send_login(self, dst: DestinationType, pwd: str) -> Dict[str, Any]:
|
||||
async def send_login(self, dst: DestinationType, pwd: str) -> Event:
|
||||
dst_bytes = _validate_destination(dst, prefix_length=32)
|
||||
logger.debug(f"Sending login request to: {dst_bytes.hex()}")
|
||||
data = b"\x1a" + dst_bytes + pwd.encode("ascii")
|
||||
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
|
||||
|
||||
async def send_logout(self, dst: DestinationType) -> Dict[str, Any]:
|
||||
async def send_logout(self, dst: DestinationType) -> Event:
|
||||
dst_bytes = _validate_destination(dst)
|
||||
self.login_resp = asyncio.Future()
|
||||
data = b"\x1d" + dst_bytes
|
||||
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
|
||||
|
||||
async def send_statusreq(self, dst: DestinationType) -> Dict[str, Any]:
|
||||
async def send_statusreq(self, dst: DestinationType) -> Event:
|
||||
dst_bytes = _validate_destination(dst, prefix_length=32)
|
||||
logger.debug(f"Sending status request to: {dst_bytes.hex()}")
|
||||
data = b"\x1b" + dst_bytes
|
||||
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
|
||||
|
||||
async def send_cmd(self, dst: DestinationType, cmd: str, timestamp: Optional[int] = None) -> Dict[str, Any]:
|
||||
async def send_cmd(self, dst: DestinationType, cmd: str, timestamp: Optional[int] = None) -> Event:
|
||||
dst_bytes = _validate_destination(dst)
|
||||
logger.debug(f"Sending command to {dst_bytes.hex()}: {cmd}")
|
||||
|
||||
|
|
@ -282,7 +284,7 @@ class CommandHandler:
|
|||
data = b"\x02\x01\x00" + timestamp.to_bytes(4, 'little') + dst_bytes + cmd.encode("ascii")
|
||||
return await self.send(data, [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def send_msg(self, dst: DestinationType, msg: str, timestamp: Optional[int] = None) -> Dict[str, Any]:
|
||||
async def send_msg(self, dst: DestinationType, msg: str, timestamp: Optional[int] = None) -> Event:
|
||||
dst_bytes = _validate_destination(dst)
|
||||
logger.debug(f"Sending message to {dst_bytes.hex()}: {msg}")
|
||||
|
||||
|
|
@ -293,7 +295,7 @@ class CommandHandler:
|
|||
data = b"\x02\x00\x00" + timestamp.to_bytes(4, 'little') + dst_bytes + msg.encode("ascii")
|
||||
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
|
||||
|
||||
async def send_chan_msg(self, chan, msg, timestamp=None):
|
||||
async def send_chan_msg(self, chan, msg, timestamp=None) -> Event:
|
||||
logger.debug(f"Sending channel message to channel {chan}: {msg}")
|
||||
|
||||
# Default to current time if timestamp not provided
|
||||
|
|
@ -304,13 +306,13 @@ class CommandHandler:
|
|||
data = b"\x03\x00" + chan.to_bytes(1, 'little') + timestamp + msg.encode("ascii")
|
||||
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
|
||||
|
||||
async def send_cli(self, cmd):
|
||||
async def send_cli(self, cmd) -> Event:
|
||||
logger.debug(f"Sending CLI command: {cmd}")
|
||||
data = b"\x32" + cmd.encode('ascii')
|
||||
return await self.send(data, [EventType.CLI_RESPONSE, EventType.ERROR])
|
||||
|
||||
async def send_trace(self, auth_code: int = 0, tag: Optional[int] = None,
|
||||
flags: int = 0, path: Optional[Union[str, bytes, bytearray]] = None) -> Dict[str, Any]:
|
||||
flags: int = 0, path: Optional[Union[str, bytes, bytearray]] = None) -> Event:
|
||||
"""
|
||||
Send a trace packet to test routing through specific repeaters
|
||||
|
||||
|
|
@ -322,7 +324,7 @@ class CommandHandler:
|
|||
or a bytes/bytearray object with the raw path data
|
||||
|
||||
Returns:
|
||||
Dictionary with sent status, tag, and estimated timeout in milliseconds, or False if command failed
|
||||
Event object with sent status, tag, and estimated timeout in milliseconds
|
||||
"""
|
||||
# Generate random tag if not provided
|
||||
if tag is None:
|
||||
|
|
@ -350,11 +352,11 @@ class CommandHandler:
|
|||
cmd_data.extend(path_bytes)
|
||||
except ValueError as e:
|
||||
logger.error(f"Invalid path format: {e}")
|
||||
return { "success": False, "reason": "invalid_path_format" }
|
||||
return Event(EventType.ERROR, {"reason": "invalid_path_format"})
|
||||
elif isinstance(path, (bytes, bytearray)):
|
||||
cmd_data.extend(path)
|
||||
else:
|
||||
logger.error(f"Unsupported path type: {type(path)}")
|
||||
return { "success": False, "reason": "unsupported_path_type" }
|
||||
return Event(EventType.ERROR, {"reason": "unsupported_path_type"})
|
||||
|
||||
return await self.send(cmd_data, [EventType.MSG_SENT, EventType.ERROR])
|
||||
|
|
|
|||
|
|
@ -262,7 +262,8 @@ class MeshCore:
|
|||
result = await self.commands.get_msg()
|
||||
|
||||
# If we got a NO_MORE_MSGS event or an error, stop fetching
|
||||
if not result.get("success") or isinstance(result, dict) and "error" in result:
|
||||
if result.type == EventType.NO_MORE_MSGS or result.type == EventType.ERROR:
|
||||
logger.debug("No more messages or error occurred, stopping auto-fetch.")
|
||||
break
|
||||
|
||||
# Small delay to prevent overwhelming the device
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class MessageReader:
|
|||
|
||||
# Handle command responses
|
||||
if packet_type_value == PacketType.OK.value:
|
||||
result: Dict[str, Any] = {"success": True}
|
||||
result: Dict[str, Any] = {}
|
||||
if len(data) == 5:
|
||||
result["value"] = int.from_bytes(data[1:5], byteorder='little')
|
||||
|
||||
|
|
@ -31,9 +31,9 @@ class MessageReader:
|
|||
|
||||
elif packet_type_value == PacketType.ERROR.value:
|
||||
if len(data) > 1:
|
||||
result = {"success": False, "error_code": data[1]}
|
||||
result = {"error_code": data[1]}
|
||||
else:
|
||||
result = {"success": False}
|
||||
result = {}
|
||||
|
||||
# Dispatch event for the ERROR response
|
||||
await self.dispatcher.dispatch(Event(EventType.ERROR, result))
|
||||
|
|
|
|||
|
|
@ -45,7 +45,10 @@ class TCPConnection:
|
|||
self.host, self.port)
|
||||
|
||||
logger.info("TCP Connection started")
|
||||
return self.host
|
||||
future = asyncio.Future()
|
||||
future.set_result(self.host)
|
||||
|
||||
return future
|
||||
|
||||
def set_reader(self, reader) :
|
||||
self.reader = reader
|
||||
|
|
|
|||
|
|
@ -47,22 +47,25 @@ def setup_event_response(mock_dispatcher, event_type, payload, attribute_filters
|
|||
async def test_send_basic(command_handler, mock_connection):
|
||||
result = await command_handler.send(b"test_data")
|
||||
mock_connection.send.assert_called_once_with(b"test_data")
|
||||
assert result == {"success": True}
|
||||
assert result.type == EventType.OK
|
||||
assert result.payload == {}
|
||||
|
||||
async def test_send_with_event(command_handler, mock_connection, mock_dispatcher):
|
||||
expected_payload = {"success": True, "value": 42}
|
||||
expected_payload = {"value": 42}
|
||||
setup_event_response(mock_dispatcher, EventType.OK, expected_payload)
|
||||
|
||||
result = await command_handler.send(b"test_command", [EventType.OK])
|
||||
|
||||
mock_connection.send.assert_called_once_with(b"test_command")
|
||||
assert result == expected_payload
|
||||
assert result.type == EventType.OK
|
||||
assert result.payload == expected_payload
|
||||
|
||||
async def test_send_timeout(command_handler, mock_connection, mock_dispatcher):
|
||||
mock_dispatcher.wait_for_event.side_effect = asyncio.TimeoutError
|
||||
|
||||
result = await command_handler.send(b"test_command", [EventType.OK], timeout=0.1)
|
||||
assert result == {"success": False, "reason": "timeout"}
|
||||
assert result.type == EventType.ERROR
|
||||
assert result.payload == {"reason": "timeout"}
|
||||
|
||||
# Destination validation tests
|
||||
async def test_validate_destination_bytes(command_handler, mock_connection):
|
||||
|
|
@ -235,7 +238,7 @@ async def test_send_trace(command_handler, mock_connection):
|
|||
|
||||
async def test_send_with_multiple_expected_events_returns_first_completed(command_handler, mock_connection, mock_dispatcher):
|
||||
# Setup the dispatcher to return an ERROR event
|
||||
error_payload = {"success": False, "reason": "command_failed"}
|
||||
error_payload = {"reason": "command_failed"}
|
||||
|
||||
async def simulate_error_event(*args, **kwargs):
|
||||
# Simulate an ERROR event being returned
|
||||
|
|
@ -251,4 +254,5 @@ async def test_send_with_multiple_expected_events_returns_first_completed(comman
|
|||
mock_connection.send.assert_called_once_with(b"test_command")
|
||||
|
||||
# Verify that even though OK was listed first, the ERROR event was returned
|
||||
assert result == error_payload
|
||||
assert result.type == EventType.ERROR
|
||||
assert result.payload == error_payload
|
||||
Loading…
Add table
Add a link
Reference in a new issue