mirror of
https://github.com/meshcore-dev/meshcore_py.git
synced 2026-07-26 18:08:12 +00:00
Merge pull request #76 from mwolter805/fix/error-response-handling
fix: add EventType.ERROR to command expected_events and guard error payloads
This commit is contained in:
@@ -105,6 +105,14 @@ class CommandHandlerBase:
|
||||
expected_events: Optional[Union[EventType, List[EventType]]] = None,
|
||||
timeout: Optional[float] = None,
|
||||
) -> Event:
|
||||
"""Wait for the first of *expected_events* to arrive.
|
||||
|
||||
Returns the first matched ``Event``. When ``EventType.ERROR`` is
|
||||
among the expected types, the caller **must** check
|
||||
``result.is_error()`` before accessing command-specific payload
|
||||
keys — an ERROR payload is ``{"reason": "..."}`` and will
|
||||
``KeyError`` on any other key.
|
||||
"""
|
||||
try:
|
||||
# Convert single event to list if needed
|
||||
if not isinstance(expected_events, list):
|
||||
@@ -144,9 +152,6 @@ class CommandHandlerBase:
|
||||
logger.debug(f"Command error: {e}")
|
||||
return Event(EventType.ERROR, {"error": str(e)})
|
||||
|
||||
return Event(EventType.ERROR, {})
|
||||
|
||||
|
||||
async def send(
|
||||
self,
|
||||
data: bytes,
|
||||
@@ -166,7 +171,14 @@ class CommandHandlerBase:
|
||||
timeout: Timeout in seconds, or None to use default_timeout
|
||||
|
||||
Returns:
|
||||
Event: The full event object that was received in response to the command
|
||||
Event: The full event object that was received in response to
|
||||
the command.
|
||||
|
||||
Important:
|
||||
When ``EventType.ERROR`` is included in *expected_events*, the
|
||||
returned event may be an error response. Callers **must**
|
||||
check ``result.is_error()`` before accessing command-specific
|
||||
payload keys to avoid ``KeyError``.
|
||||
"""
|
||||
if not self.dispatcher:
|
||||
raise RuntimeError("Dispatcher not set, cannot send commands")
|
||||
@@ -281,6 +293,7 @@ class CommandHandlerBase:
|
||||
contact = self._get_contact_by_prefix(dst_bytes.hex()) # need a contact for return path
|
||||
if contact is None:
|
||||
logger.error("No contact found")
|
||||
return Event(EventType.ERROR, {"reason": "contact_not_found"})
|
||||
|
||||
zero_hop = False
|
||||
if contact["out_path_len"] == -1:
|
||||
|
||||
@@ -13,7 +13,7 @@ class DeviceCommands(CommandHandlerBase):
|
||||
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])
|
||||
return await self.send(b1, [EventType.SELF_INFO, EventType.ERROR])
|
||||
|
||||
async def send_device_query(self) -> Event:
|
||||
logger.debug("Sending device query command")
|
||||
@@ -129,32 +129,50 @@ class DeviceCommands(CommandHandlerBase):
|
||||
return await self.send(data, [EventType.OK, EventType.ERROR])
|
||||
|
||||
async def set_telemetry_mode_base(self, telemetry_mode_base: int) -> Event:
|
||||
infos = (await self.send_appstart()).payload
|
||||
result = await self.send_appstart()
|
||||
if result.is_error():
|
||||
return result
|
||||
infos = result.payload
|
||||
infos["telemetry_mode_base"] = telemetry_mode_base
|
||||
return await self.set_other_params_from_infos(infos)
|
||||
|
||||
async def set_telemetry_mode_loc(self, telemetry_mode_loc: int) -> Event:
|
||||
infos = (await self.send_appstart()).payload
|
||||
result = await self.send_appstart()
|
||||
if result.is_error():
|
||||
return result
|
||||
infos = result.payload
|
||||
infos["telemetry_mode_loc"] = telemetry_mode_loc
|
||||
return await self.set_other_params_from_infos(infos)
|
||||
|
||||
async def set_telemetry_mode_env(self, telemetry_mode_env: int) -> Event:
|
||||
infos = (await self.send_appstart()).payload
|
||||
result = await self.send_appstart()
|
||||
if result.is_error():
|
||||
return result
|
||||
infos = result.payload
|
||||
infos["telemetry_mode_env"] = telemetry_mode_env
|
||||
return await self.set_other_params_from_infos(infos)
|
||||
|
||||
async def set_manual_add_contacts(self, manual_add_contacts: bool) -> Event:
|
||||
infos = (await self.send_appstart()).payload
|
||||
result = await self.send_appstart()
|
||||
if result.is_error():
|
||||
return result
|
||||
infos = result.payload
|
||||
infos["manual_add_contacts"] = manual_add_contacts
|
||||
return await self.set_other_params_from_infos(infos)
|
||||
|
||||
async def set_advert_loc_policy(self, advert_loc_policy: int) -> Event:
|
||||
infos = (await self.send_appstart()).payload
|
||||
result = await self.send_appstart()
|
||||
if result.is_error():
|
||||
return result
|
||||
infos = result.payload
|
||||
infos["adv_loc_policy"] = advert_loc_policy
|
||||
return await self.set_other_params_from_infos(infos)
|
||||
|
||||
async def set_multi_acks(self, multi_acks: int) -> Event:
|
||||
infos = (await self.send_appstart()).payload
|
||||
result = await self.send_appstart()
|
||||
if result.is_error():
|
||||
return result
|
||||
infos = result.payload
|
||||
infos["multi_acks"] = multi_acks
|
||||
return await self.set_other_params_from_infos(infos)
|
||||
|
||||
|
||||
@@ -144,8 +144,12 @@ class MessagingCommands(CommandHandlerBase):
|
||||
logger.info(f"Retry sending msg: {attempts + 1}")
|
||||
|
||||
result = await self.send_msg(dst, msg, timestamp, attempt=attempts)
|
||||
if result.type == EventType.ERROR:
|
||||
logger.error(f"⚠️ Failed to send message: {result.payload}")
|
||||
if result.is_error():
|
||||
logger.error(f"Failed to send message: {result.payload}")
|
||||
attempts += 1
|
||||
if flood:
|
||||
flood_attempts += 1
|
||||
continue
|
||||
|
||||
exp_ack = result.payload["expected_ack"].hex()
|
||||
timeout = result.payload["suggested_timeout"] / 1000 * 1.2 if timeout==0 else timeout
|
||||
@@ -255,7 +259,7 @@ class MessagingCommands(CommandHandlerBase):
|
||||
elif path_hash_len == 8 :
|
||||
flags = 3
|
||||
else :
|
||||
logger.error(f"Invalid path format: {e}")
|
||||
logger.error(f"Invalid path format: unknown path_hash_len {path_hash_len}")
|
||||
return Event(EventType.ERROR, {"reason": "invalid_path_format"})
|
||||
else:
|
||||
flags = 0
|
||||
|
||||
@@ -104,6 +104,17 @@ class Event:
|
||||
if kwargs:
|
||||
self.attributes.update(kwargs)
|
||||
|
||||
def is_error(self) -> bool:
|
||||
"""Return True if this event represents an error response.
|
||||
|
||||
Callers that include ``EventType.ERROR`` in their expected-events
|
||||
list **must** check ``result.is_error()`` (or ``result.type ==
|
||||
EventType.ERROR``) before accessing keyed payload fields, because
|
||||
an ERROR payload contains ``{"reason": "..."}`` — not the
|
||||
command-specific keys the caller expects on the happy path.
|
||||
"""
|
||||
return self.type == EventType.ERROR
|
||||
|
||||
def clone(self):
|
||||
"""
|
||||
Create a copy of the event.
|
||||
|
||||
Reference in New Issue
Block a user