timeout for each contact in get_contacts

This commit is contained in:
Florent de Lamotte
2025-10-22 10:21:07 +02:00
parent 8f0be09f9a
commit d619423078
5 changed files with 77 additions and 41 deletions

View File

@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "meshcore" name = "meshcore"
version = "2.1.12" version = "2.1.13"
authors = [ authors = [
{ name="Florent de Lamotte", email="florent@frizoncorrea.fr" }, { name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
{ name="Alex Wolden", email="awolden@gmail.com" }, { name="Alex Wolden", email="awolden@gmail.com" },

View File

@@ -80,36 +80,11 @@ class CommandHandlerBase:
)-> None: )-> None:
self._get_contact_by_prefix = func self._get_contact_by_prefix = func
async def send( async def wait_for_events(
self, self,
data: bytes,
expected_events: Optional[Union[EventType, List[EventType]]] = None, expected_events: Optional[Union[EventType, List[EventType]]] = None,
timeout: Optional[float] = None, timeout: Optional[float] = None,
) -> Event: ) -> Event:
"""
Send a command and wait for expected event responses.
Args:
data: The data to send
expected_events: EventType or list of EventTypes to wait for
timeout: Timeout in seconds, or None to use default_timeout
Returns:
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")
# Use the provided timeout or fall back to default_timeout
timeout = timeout if timeout is not None else self.default_timeout
if self._sender_func:
logger.debug(
f"Sending raw data: {data.hex() if isinstance(data, bytes) else data}"
)
await self._sender_func(data)
if expected_events:
try: try:
# Convert single event to list if needed # Convert single event to list if needed
if not isinstance(expected_events, list): if not isinstance(expected_events, list):
@@ -148,7 +123,43 @@ class CommandHandlerBase:
except Exception as e: except Exception as e:
logger.debug(f"Command error: {e}") logger.debug(f"Command error: {e}")
return Event(EventType.ERROR, {"error": str(e)}) return Event(EventType.ERROR, {"error": str(e)})
return Event(EventType.ERROR, {})
async def send(
self,
data: bytes,
expected_events: Optional[Union[EventType, List[EventType]]] = None,
timeout: Optional[float] = None,
) -> Event:
"""
Send a command and wait for expected event responses.
Args:
data: The data to send
expected_events: EventType or list of EventTypes to wait for
timeout: Timeout in seconds, or None to use default_timeout
Returns:
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")
# Use the provided timeout or fall back to default_timeout
timeout = timeout if timeout is not None else self.default_timeout
if self._sender_func:
logger.debug(
f"Sending raw data: {data.hex() if isinstance(data, bytes) else data}"
)
await self._sender_func(data)
if expected_events:
# For commands that don't expect events, return a success event # For commands that don't expect events, return a success event
return await self.wait_for_events(expected_events, timeout)
return Event(EventType.OK, {}) return Event(EventType.OK, {})
# attached at base because its a common method # attached at base because its a common method

View File

@@ -8,12 +8,35 @@ logger = logging.getLogger("meshcore")
class ContactCommands(CommandHandlerBase): class ContactCommands(CommandHandlerBase):
async def get_contacts(self, lastmod=0) -> Event: async def get_contacts(self, lastmod=0, anim=False) -> Event:
logger.debug("Getting contacts") logger.debug("Getting contacts")
data = b"\x04" data = b"\x04"
if lastmod > 0: if lastmod > 0:
data = data + lastmod.to_bytes(4, "little") data = data + lastmod.to_bytes(4, "little")
return await self.send(data, [EventType.CONTACTS, EventType.ERROR], timeout=30) if anim:
print("Fetching contacts ", end="", flush=True)
# wait first event
res = await self.send(data)
while True:
# wait next event
res = await self.wait_for_events(
[EventType.NEXT_CONTACT, EventType.CONTACTS, EventType.ERROR],
timeout=5)
if res is None: # Timeout
if anim:
print(" Timeout")
return res
if res.type == EventType.ERROR:
if anim:
print(" Error")
return res
elif res.type == EventType.CONTACTS:
if anim:
print(" Done")
return res
elif res.type == EventType.NEXT_CONTACT:
if anim:
print(".", end="", flush=True)
async def reset_path(self, key: DestinationType) -> Event: async def reset_path(self, key: DestinationType) -> Event:
key_bytes = _validate_destination(key, prefix_length=32) key_bytes = _validate_destination(key, prefix_length=32)

View File

@@ -21,6 +21,7 @@ class EventType(Enum):
DEVICE_INFO = "device_info" DEVICE_INFO = "device_info"
MSG_SENT = "message_sent" MSG_SENT = "message_sent"
NEW_CONTACT = "new_contact" NEW_CONTACT = "new_contact"
NEXT_CONTACT = "next_contact"
# Push notifications # Push notifications
ADVERTISEMENT = "advertisement" ADVERTISEMENT = "advertisement"

View File

@@ -99,6 +99,7 @@ class MessageReader:
if packet_type_value == PacketType.PUSH_CODE_NEW_ADVERT.value: if packet_type_value == PacketType.PUSH_CODE_NEW_ADVERT.value:
await self.dispatcher.dispatch(Event(EventType.NEW_CONTACT, c)) await self.dispatcher.dispatch(Event(EventType.NEW_CONTACT, c))
else: else:
await self.dispatcher.dispatch(Event(EventType.NEXT_CONTACT, c))
self.contacts[c["public_key"]] = c self.contacts[c["public_key"]] = c
elif packet_type_value == PacketType.CONTACT_END.value: elif packet_type_value == PacketType.CONTACT_END.value: