mirror of
https://github.com/meshcore-dev/meshcore_py.git
synced 2026-06-11 11:56:18 +00:00
Add mesh request lock to serialize firmware-bound mesh commands
The companion firmware can only track one outstanding mesh request at a time — clearPendingReqs() zeros all pending response flags before each outgoing mesh request. Overlapping mesh commands cause silent response drops. Adds _mesh_request_lock to CommandHandlerBase and wraps all _sync methods with it. Also adds send_login_sync and send_path_discovery_sync for complete round-trip serialization of those commands. Local commands (get_bat, get_channel, set_time, send_msg, etc.) are unaffected — they don't trigger clearPendingReqs() on the firmware.
This commit is contained in:
@@ -64,6 +64,7 @@ class CommandHandlerBase:
|
|||||||
self._sender_func: Optional[Callable[[bytes], Coroutine[Any, Any, None]]] = None
|
self._sender_func: Optional[Callable[[bytes], Coroutine[Any, Any, None]]] = None
|
||||||
self._reader: Optional[MessageReader] = None
|
self._reader: Optional[MessageReader] = None
|
||||||
self.dispatcher: Optional[EventDispatcher] = None
|
self.dispatcher: Optional[EventDispatcher] = None
|
||||||
|
self._mesh_request_lock = asyncio.Lock()
|
||||||
self.default_timeout = (
|
self.default_timeout = (
|
||||||
default_timeout if default_timeout is not None else self.DEFAULT_TIMEOUT
|
default_timeout if default_timeout is not None else self.DEFAULT_TIMEOUT
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
return await self.req_status_sync(contact, timeout, min_timeout)
|
return await self.req_status_sync(contact, timeout, min_timeout)
|
||||||
|
|
||||||
async def req_status_sync(self, contact, timeout=0, min_timeout=0):
|
async def req_status_sync(self, contact, timeout=0, min_timeout=0):
|
||||||
|
async with self._mesh_request_lock:
|
||||||
res = await self.send_binary_req(
|
res = await self.send_binary_req(
|
||||||
contact,
|
contact,
|
||||||
BinaryReqType.STATUS,
|
BinaryReqType.STATUS,
|
||||||
@@ -48,6 +49,7 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
return await self.req_telemetry_sync(contact, timeout, min_timeout)
|
return await self.req_telemetry_sync(contact, timeout, min_timeout)
|
||||||
|
|
||||||
async def req_telemetry_sync(self, contact, timeout=0, min_timeout=0):
|
async def req_telemetry_sync(self, contact, timeout=0, min_timeout=0):
|
||||||
|
async with self._mesh_request_lock:
|
||||||
res = await self.send_binary_req(
|
res = await self.send_binary_req(
|
||||||
contact,
|
contact,
|
||||||
BinaryReqType.TELEMETRY,
|
BinaryReqType.TELEMETRY,
|
||||||
@@ -63,7 +65,6 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
if self.dispatcher is None:
|
if self.dispatcher is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Listen for TELEMETRY_RESPONSE event
|
|
||||||
telem_event = await self.dispatcher.wait_for_event(
|
telem_event = await self.dispatcher.wait_for_event(
|
||||||
EventType.TELEMETRY_RESPONSE,
|
EventType.TELEMETRY_RESPONSE,
|
||||||
attribute_filters={"tag": res.payload["expected_ack"].hex()},
|
attribute_filters={"tag": res.payload["expected_ack"].hex()},
|
||||||
@@ -77,6 +78,7 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
return await self.req_mma_sync(contact, start, end, timeout,min_timeout)
|
return await self.req_mma_sync(contact, start, end, timeout,min_timeout)
|
||||||
|
|
||||||
async def req_mma_sync(self, contact, start, end, timeout=0,min_timeout=0):
|
async def req_mma_sync(self, contact, start, end, timeout=0,min_timeout=0):
|
||||||
|
async with self._mesh_request_lock:
|
||||||
req = (
|
req = (
|
||||||
start.to_bytes(4, "little", signed=False)
|
start.to_bytes(4, "little", signed=False)
|
||||||
+ end.to_bytes(4, "little", signed=False)
|
+ end.to_bytes(4, "little", signed=False)
|
||||||
@@ -97,7 +99,6 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
if self.dispatcher is None:
|
if self.dispatcher is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Listen for MMA_RESPONSE
|
|
||||||
mma_event = await self.dispatcher.wait_for_event(
|
mma_event = await self.dispatcher.wait_for_event(
|
||||||
EventType.MMA_RESPONSE,
|
EventType.MMA_RESPONSE,
|
||||||
attribute_filters={"tag": res.payload["expected_ack"].hex()},
|
attribute_filters={"tag": res.payload["expected_ack"].hex()},
|
||||||
@@ -111,6 +112,7 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
return await self.req_acl_sync(contact, timeout, min_timeout)
|
return await self.req_acl_sync(contact, timeout, min_timeout)
|
||||||
|
|
||||||
async def req_acl_sync(self, contact, timeout=0, min_timeout=0):
|
async def req_acl_sync(self, contact, timeout=0, min_timeout=0):
|
||||||
|
async with self._mesh_request_lock:
|
||||||
req = b"\0\0"
|
req = b"\0\0"
|
||||||
res = await self.send_binary_req(
|
res = await self.send_binary_req(
|
||||||
contact,
|
contact,
|
||||||
@@ -127,7 +129,6 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
if self.dispatcher is None:
|
if self.dispatcher is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Listen for ACL_RESPONSE event with matching tag
|
|
||||||
acl_event = await self.dispatcher.wait_for_event(
|
acl_event = await self.dispatcher.wait_for_event(
|
||||||
EventType.ACL_RESPONSE,
|
EventType.ACL_RESPONSE,
|
||||||
attribute_filters={"tag": res.payload["expected_ack"].hex()},
|
attribute_filters={"tag": res.payload["expected_ack"].hex()},
|
||||||
@@ -172,7 +173,7 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
timeout=0,
|
timeout=0,
|
||||||
min_timeout=0
|
min_timeout=0
|
||||||
):
|
):
|
||||||
|
async with self._mesh_request_lock:
|
||||||
res = await self.req_neighbours_async(contact,
|
res = await self.req_neighbours_async(contact,
|
||||||
count=count,
|
count=count,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
@@ -190,7 +191,6 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
if self.dispatcher is None:
|
if self.dispatcher is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Listen for NEIGHBOUR_RESPONSE
|
|
||||||
neighbours_event = await self.dispatcher.wait_for_event(
|
neighbours_event = await self.dispatcher.wait_for_event(
|
||||||
EventType.NEIGHBOURS_RESPONSE,
|
EventType.NEIGHBOURS_RESPONSE,
|
||||||
attribute_filters={"tag": res.payload["expected_ack"].hex()},
|
attribute_filters={"tag": res.payload["expected_ack"].hex()},
|
||||||
@@ -259,6 +259,7 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def req_regions_sync(self, contact, timeout=0, min_timeout=0):
|
async def req_regions_sync(self, contact, timeout=0, min_timeout=0):
|
||||||
|
async with self._mesh_request_lock:
|
||||||
res = await self.req_regions_async(contact, timeout, min_timeout)
|
res = await self.req_regions_async(contact, timeout, min_timeout)
|
||||||
|
|
||||||
if res.type == EventType.ERROR:
|
if res.type == EventType.ERROR:
|
||||||
@@ -294,7 +295,7 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def req_owner_sync(self, contact, timeout=0, min_timeout=0):
|
async def req_owner_sync(self, contact, timeout=0, min_timeout=0):
|
||||||
|
async with self._mesh_request_lock:
|
||||||
res = await self.req_owner_async(contact, timeout, min_timeout)
|
res = await self.req_owner_async(contact, timeout, min_timeout)
|
||||||
|
|
||||||
if res.type == EventType.ERROR:
|
if res.type == EventType.ERROR:
|
||||||
@@ -332,7 +333,7 @@ class BinaryCommandHandler(CommandHandlerBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def req_basic_sync(self, contact, timeout=0, min_timeout=0):
|
async def req_basic_sync(self, contact, timeout=0, min_timeout=0):
|
||||||
|
async with self._mesh_request_lock:
|
||||||
res = await self.req_basic_async(contact, timeout, min_timeout)
|
res = await self.req_basic_async(contact, timeout, min_timeout)
|
||||||
|
|
||||||
if res.type == EventType.ERROR:
|
if res.type == EventType.ERROR:
|
||||||
|
|||||||
@@ -30,6 +30,18 @@ class MessagingCommands(CommandHandlerBase):
|
|||||||
data = b"\x1a" + dst_bytes + pwd.encode("utf-8")
|
data = b"\x1a" + dst_bytes + pwd.encode("utf-8")
|
||||||
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
|
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
|
||||||
|
|
||||||
|
async def send_login_sync(self, dst: DestinationType, pwd: str, timeout: float = 10.0) -> Optional[Event]:
|
||||||
|
"""Send login to a remote node and wait for the response."""
|
||||||
|
async with self._mesh_request_lock:
|
||||||
|
result = await self.send_login(dst, pwd)
|
||||||
|
if result is None or result.type == EventType.ERROR:
|
||||||
|
return None
|
||||||
|
login_event = await self.dispatcher.wait_for_event(
|
||||||
|
EventType.LOGIN_SUCCESS,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
return login_event
|
||||||
|
|
||||||
async def send_logout(self, dst: DestinationType) -> Event:
|
async def send_logout(self, dst: DestinationType) -> Event:
|
||||||
dst_bytes = _validate_destination(dst, prefix_length=32)
|
dst_bytes = _validate_destination(dst, prefix_length=32)
|
||||||
data = b"\x1d" + dst_bytes
|
data = b"\x1d" + dst_bytes
|
||||||
@@ -106,7 +118,7 @@ class MessagingCommands(CommandHandlerBase):
|
|||||||
# if we have a full key (meaning we can reset path) consider direct
|
# if we have a full key (meaning we can reset path) consider direct
|
||||||
# else consider flood
|
# else consider flood
|
||||||
flood = len(dst_bytes) < 32
|
flood = len(dst_bytes) < 32
|
||||||
logger.info(f"send_msg_with_retry: can't determine if flood, assume {flood}")
|
logger.info(f"send_msg_with_retry: can't determine if flood, assume {flood}")
|
||||||
res = None
|
res = None
|
||||||
while attempts < max_attempts and res is None \
|
while attempts < max_attempts and res is None \
|
||||||
and (not flood or flood_attempts < max_flood_attempts):
|
and (not flood or flood_attempts < max_flood_attempts):
|
||||||
@@ -122,7 +134,7 @@ class MessagingCommands(CommandHandlerBase):
|
|||||||
contact["out_path_len"] = -1
|
contact["out_path_len"] = -1
|
||||||
|
|
||||||
if attempts > 0:
|
if attempts > 0:
|
||||||
logger.info(f"Retry sending msg: {attempts + 1}")
|
logger.info(f"Retry sending msg: {attempts + 1}")
|
||||||
|
|
||||||
result = await self.send_msg(dst, msg, timestamp, attempt=attempts)
|
result = await self.send_msg(dst, msg, timestamp, attempt=attempts)
|
||||||
if result.type == EventType.ERROR:
|
if result.type == EventType.ERROR:
|
||||||
@@ -177,6 +189,18 @@ class MessagingCommands(CommandHandlerBase):
|
|||||||
data = b"\x34\x00" + dst_bytes
|
data = b"\x34\x00" + dst_bytes
|
||||||
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
|
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
|
||||||
|
|
||||||
|
async def send_path_discovery_sync(self, dst: DestinationType, timeout: float = 30.0) -> Optional[Event]:
|
||||||
|
"""Send path discovery request and wait for the response."""
|
||||||
|
async with self._mesh_request_lock:
|
||||||
|
result = await self.send_path_discovery(dst)
|
||||||
|
if result is None or result.type == EventType.ERROR:
|
||||||
|
return None
|
||||||
|
path_event = await self.dispatcher.wait_for_event(
|
||||||
|
EventType.PATH_RESPONSE,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
return path_event
|
||||||
|
|
||||||
async def send_trace(
|
async def send_trace(
|
||||||
self,
|
self,
|
||||||
auth_code: int = 0,
|
auth_code: int = 0,
|
||||||
|
|||||||
Reference in New Issue
Block a user