mirror of
https://github.com/meshcore-dev/meshcore_py.git
synced 2026-04-20 22:13:49 +00:00
feat: Refactor binary commands and apply BLE fixes
Refactored the BinaryCommandHandler to align with the other command handlers, inheriting from CommandHandlerBase. This resolves an AttributeError and simplifies the command structure. Moved binary_commands.py into the commands module. Applied fixes to the BLE connection handler based on feedback, improving reliability on macOS and ensuring the device address is correctly handled.
This commit is contained in:
parent
c19fd166f8
commit
36727f4ea3
22 changed files with 1603 additions and 1206 deletions
|
|
@ -1,13 +1,13 @@
|
|||
from enum import Enum
|
||||
import logging
|
||||
from math import log
|
||||
from typing import Any, Dict, Optional, Callable, List, Union
|
||||
import asyncio
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
logger = logging.getLogger("meshcore")
|
||||
|
||||
# Public event types for users to subscribe to
|
||||
|
||||
# Public event types for users to subscribe to
|
||||
class EventType(Enum):
|
||||
CONTACTS = "contacts"
|
||||
SELF_INFO = "self_info"
|
||||
|
|
@ -20,7 +20,7 @@ class EventType(Enum):
|
|||
DEVICE_INFO = "device_info"
|
||||
MSG_SENT = "message_sent"
|
||||
NEW_CONTACT = "new_contact"
|
||||
|
||||
|
||||
# Push notifications
|
||||
ADVERTISEMENT = "advertisement"
|
||||
PATH_UPDATE = "path_update"
|
||||
|
|
@ -28,7 +28,7 @@ class EventType(Enum):
|
|||
MESSAGES_WAITING = "messages_waiting"
|
||||
RAW_DATA = "raw_data"
|
||||
LOGIN_SUCCESS = "login_success"
|
||||
LOGIN_FAILED = "login_failed"
|
||||
LOGIN_FAILED = "login_failed"
|
||||
STATUS_RESPONSE = "status_response"
|
||||
LOG_DATA = "log_data"
|
||||
TRACE_DATA = "trace_data"
|
||||
|
|
@ -38,11 +38,11 @@ class EventType(Enum):
|
|||
CUSTOM_VARS = "custom_vars"
|
||||
CHANNEL_INFO = "channel_info"
|
||||
PATH_RESPONSE = "path_response"
|
||||
|
||||
|
||||
# Command response types
|
||||
OK = "command_ok"
|
||||
ERROR = "command_error"
|
||||
|
||||
|
||||
# Connection events
|
||||
CONNECTED = "connected"
|
||||
DISCONNECTED = "disconnected"
|
||||
|
|
@ -53,11 +53,17 @@ class Event:
|
|||
type: EventType
|
||||
payload: Any
|
||||
attributes: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def __init__(self, type: EventType, payload: Any, attributes: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
type: EventType,
|
||||
payload: Any,
|
||||
attributes: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Initialize an Event
|
||||
|
||||
|
||||
Args:
|
||||
type: The event type
|
||||
payload: The event payload
|
||||
|
|
@ -67,18 +73,21 @@ class Event:
|
|||
self.type = type
|
||||
self.payload = payload
|
||||
self.attributes = attributes or {}
|
||||
|
||||
|
||||
# Add any keyword arguments to the attributes dictionary
|
||||
if kwargs:
|
||||
self.attributes.update(kwargs)
|
||||
|
||||
def clone(self):
|
||||
"""
|
||||
Create a copy of the event.
|
||||
|
||||
|
||||
Returns:
|
||||
A new Event object with the same type, payload, and attributes.
|
||||
"""
|
||||
copied_payload = self.payload.copy() if isinstance(self.payload, dict) else self.payload
|
||||
copied_payload = (
|
||||
self.payload.copy() if isinstance(self.payload, dict) else self.payload
|
||||
)
|
||||
return Event(self.type, copied_payload, self.attributes.copy())
|
||||
|
||||
|
||||
|
|
@ -88,7 +97,7 @@ class Subscription:
|
|||
self.event_type = event_type
|
||||
self.callback = callback
|
||||
self.attribute_filters = attribute_filters or {}
|
||||
|
||||
|
||||
def unsubscribe(self):
|
||||
self.dispatcher._remove_subscription(self)
|
||||
|
||||
|
|
@ -99,12 +108,16 @@ class EventDispatcher:
|
|||
self.subscriptions: List[Subscription] = []
|
||||
self.running = False
|
||||
self._task = None
|
||||
|
||||
def subscribe(self, event_type: Union[EventType, None], callback: Callable[[Event], Union[None, asyncio.Future]],
|
||||
attribute_filters: Optional[Dict[str, Any]] = None) -> Subscription:
|
||||
|
||||
def subscribe(
|
||||
self,
|
||||
event_type: Union[EventType, None],
|
||||
callback: Callable[[Event], Union[None, asyncio.Future]],
|
||||
attribute_filters: Optional[Dict[str, Any]] = None,
|
||||
) -> Subscription:
|
||||
"""
|
||||
Subscribe to events with optional attribute filtering.
|
||||
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
event_type : EventType or None
|
||||
|
|
@ -113,7 +126,7 @@ class EventDispatcher:
|
|||
Function to call when a matching event is received.
|
||||
attribute_filters : Dict[str, Any], optional
|
||||
Dictionary of attribute key-value pairs that must match for the event to trigger the callback.
|
||||
|
||||
|
||||
Returns:
|
||||
--------
|
||||
Subscription object that can be used to unsubscribe.
|
||||
|
|
@ -121,26 +134,36 @@ class EventDispatcher:
|
|||
subscription = Subscription(self, event_type, callback, attribute_filters)
|
||||
self.subscriptions.append(subscription)
|
||||
return subscription
|
||||
|
||||
|
||||
def _remove_subscription(self, subscription: Subscription):
|
||||
if subscription in self.subscriptions:
|
||||
self.subscriptions.remove(subscription)
|
||||
|
||||
|
||||
async def dispatch(self, event: Event):
|
||||
await self.queue.put(event)
|
||||
|
||||
|
||||
async def _process_events(self):
|
||||
while self.running:
|
||||
event = await self.queue.get()
|
||||
logger.debug(f"Dispatching event: {event.type}, {event.payload}, {event.attributes}")
|
||||
logger.debug(
|
||||
f"Dispatching event: {event.type}, {event.payload}, {event.attributes}"
|
||||
)
|
||||
for subscription in self.subscriptions.copy():
|
||||
# Check if event type matches
|
||||
if subscription.event_type is None or subscription.event_type == event.type:
|
||||
if (
|
||||
subscription.event_type is None
|
||||
or subscription.event_type == event.type
|
||||
):
|
||||
# Check if all attribute filters match
|
||||
if subscription.attribute_filters and subscription.attribute_filters != {}:
|
||||
if (
|
||||
subscription.attribute_filters
|
||||
and subscription.attribute_filters != {}
|
||||
):
|
||||
# Skip if any filter doesn't match the corresponding event attribute
|
||||
if not all(event.attributes.get(key) == value
|
||||
for key, value in subscription.attribute_filters.items()):
|
||||
if not all(
|
||||
event.attributes.get(key) == value
|
||||
for key, value in subscription.attribute_filters.items()
|
||||
):
|
||||
continue
|
||||
try:
|
||||
result = subscription.callback(event.clone())
|
||||
|
|
@ -148,14 +171,14 @@ class EventDispatcher:
|
|||
await result
|
||||
except Exception as e:
|
||||
print(f"Error in event handler: {e}")
|
||||
|
||||
|
||||
self.queue.task_done()
|
||||
|
||||
|
||||
async def start(self):
|
||||
if not self.running:
|
||||
self.running = True
|
||||
self._task = asyncio.create_task(self._process_events())
|
||||
|
||||
|
||||
async def stop(self):
|
||||
if self.running:
|
||||
self.running = False
|
||||
|
|
@ -167,12 +190,16 @@ class EventDispatcher:
|
|||
except asyncio.CancelledError:
|
||||
pass
|
||||
self._task = None
|
||||
|
||||
async def wait_for_event(self, event_type: EventType, attribute_filters: Optional[Dict[str, Any]] = None,
|
||||
timeout: float | None = None) -> Optional[Event]:
|
||||
|
||||
async def wait_for_event(
|
||||
self,
|
||||
event_type: EventType,
|
||||
attribute_filters: Optional[Dict[str, Any]] = None,
|
||||
timeout: float | None = None,
|
||||
) -> Optional[Event]:
|
||||
"""
|
||||
Wait for an event of the specified type that matches all attribute filters.
|
||||
|
||||
|
||||
Parameters:
|
||||
-----------
|
||||
event_type : EventType
|
||||
|
|
@ -181,19 +208,19 @@ class EventDispatcher:
|
|||
Dictionary of attribute key-value pairs that must match for the event to be returned.
|
||||
timeout : float | None, optional
|
||||
Maximum time to wait for the event, in seconds.
|
||||
|
||||
|
||||
Returns:
|
||||
--------
|
||||
The matched event, or None if timeout occurred before a matching event.
|
||||
"""
|
||||
future = asyncio.Future()
|
||||
|
||||
|
||||
def event_handler(event: Event):
|
||||
if not future.done():
|
||||
future.set_result(event)
|
||||
|
||||
|
||||
subscription = self.subscribe(event_type, event_handler, attribute_filters)
|
||||
|
||||
|
||||
try:
|
||||
return await asyncio.wait_for(future, timeout)
|
||||
except asyncio.TimeoutError:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue