Merge pull request #6 from fdlamotte/awolden/fix-issues

Improvements and fixes
This commit is contained in:
fdlamotte 2025-05-15 21:32:25 +02:00 committed by GitHub
commit fb62b7888c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 164 additions and 19 deletions

View file

@ -91,3 +91,9 @@ class BLEConnection:
logger.error("RX characteristic not found")
return False
await self.client.write_gatt_char(self.rx_char, bytes(data), response=False)
async def disconnect(self):
"""Disconnect from the BLE device."""
if self.client and self.client.is_connected:
await self.client.disconnect()
logger.debug("BLE Connection closed")

View file

@ -64,6 +64,15 @@ class Event:
# 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
return Event(self.type, copied_payload, self.attributes.copy())
class Subscription:
@ -79,7 +88,7 @@ class Subscription:
class EventDispatcher:
def __init__(self):
self.queue = asyncio.Queue()
self.queue: asyncio.Queue[Event] = asyncio.Queue()
self.subscriptions: List[Subscription] = []
self.running = False
self._task = None
@ -118,7 +127,6 @@ class EventDispatcher:
event = await self.queue.get()
logger.debug(f"Dispatching event: {event.type}, {event.payload}, {event.attributes}")
for subscription in self.subscriptions.copy():
logger.debug(f"Checking subscription: {subscription.event_type}, {subscription.attribute_filters}")
# Check if event type matches
if subscription.event_type is None or subscription.event_type == event.type:
# Check if all attribute filters match
@ -128,7 +136,7 @@ class EventDispatcher:
for key, value in subscription.attribute_filters.items()):
continue
try:
result = subscription.callback(event)
result = subscription.callback(event.clone())
if asyncio.iscoroutine(result):
await result
except Exception as e:

View file

@ -5,7 +5,9 @@ from typing import Optional, Dict, Any, Union
from .events import EventDispatcher, EventType
from .reader import MessageReader
from .commands import CommandHandler
from .ble_cx import BLEConnection
from .tcp_cx import TCPConnection
from .serial_cx import SerialConnection
# Setup default logger
logger = logging.getLogger("meshcore")
@ -45,9 +47,7 @@ class MeshCore:
@classmethod
async def create_tcp(cls, host: str, port: int, debug: bool = False, default_timeout=None) -> 'MeshCore':
"""Create and connect a MeshCore instance using TCP connection"""
from .tcp_cx import TCPConnection
"""Create and connect a MeshCore instance using TCP connection"""
connection = TCPConnection(host, port)
await connection.connect()
@ -58,9 +58,6 @@ class MeshCore:
@classmethod
async def create_serial(cls, port: str, baudrate: int = 115200, debug: bool = False, default_timeout=None) -> 'MeshCore':
"""Create and connect a MeshCore instance using serial connection"""
from .serial_cx import SerialConnection
import asyncio
connection = SerialConnection(port, baudrate)
await connection.connect()
await asyncio.sleep(0.1) # Time for transport to establish
@ -75,7 +72,6 @@ class MeshCore:
If address is None, it will scan for and connect to the first available MeshCore device.
"""
from .ble_cx import BLEConnection
connection = BLEConnection(address)
result = await connection.connect()
@ -91,7 +87,17 @@ class MeshCore:
return await self.commands.send_appstart()
async def disconnect(self):
"""Disconnect from the device and clean up resources."""
# First stop the dispatcher to prevent any new events
await self.dispatcher.stop()
# Stop auto message fetching if it's running
if hasattr(self, '_auto_fetch_subscription') and self._auto_fetch_subscription:
await self.stop_auto_message_fetching()
# Disconnect the connection object
if self.cx:
await self.cx.disconnect()
def stop(self):
"""Synchronously stop the event dispatcher task"""
@ -99,7 +105,7 @@ class MeshCore:
self.dispatcher.running = False
self.dispatcher._task.cancel()
def subscribe(self, event_type: EventType, callback, attribute_filters: Optional[Dict[str, Any]] = None):
def subscribe(self, event_type: Union[EventType, None], callback, attribute_filters: Optional[Dict[str, Any]] = None):
"""
Subscribe to events using EventType enum with optional attribute filtering

View file

@ -256,14 +256,31 @@ class MessageReader:
await self.dispatcher.dispatch(Event(EventType.RAW_DATA, res))
elif packet_type_value == PacketType.LOGIN_SUCCESS.value:
logger.debug("Login success")
# TODO: Read login attributes
await self.dispatcher.dispatch(Event(EventType.LOGIN_SUCCESS, {}))
res = {}
if len(data) > 1:
res["permissions"] = data[1]
res["is_admin"] = (data[1] & 1) == 1 # Check if admin bit is set
if len(data) > 7:
res["pubkey_prefix"] = data[2:8].hex()
attributes = {
"pubkey_prefix": res.get("pubkey_prefix")
}
await self.dispatcher.dispatch(Event(EventType.LOGIN_SUCCESS, res, attributes))
elif packet_type_value == PacketType.LOGIN_FAILED.value:
logger.debug("Login failed")
# TODO: Read login attributes
await self.dispatcher.dispatch(Event(EventType.LOGIN_FAILED, {}))
res = {}
if len(data) > 7:
res["pubkey_prefix"] = data[2:8].hex()
attributes = {
"pubkey_prefix": res.get("pubkey_prefix")
}
await self.dispatcher.dispatch(Event(EventType.LOGIN_FAILED, res, attributes))
elif packet_type_value == PacketType.STATUS_RESPONSE.value:
res = {}

View file

@ -86,4 +86,11 @@ class SerialConnection:
size = len(data)
pkt = b"\x3c" + size.to_bytes(2, byteorder="little") + data
logger.debug(f"sending pkt : {pkt}")
self.transport.write(pkt)
self.transport.write(pkt)
async def disconnect(self):
"""Close the serial connection."""
if self.transport:
self.transport.close()
self.transport = None
logger.debug("Serial Connection closed")

View file

@ -85,3 +85,10 @@ class TCPConnection:
pkt = b"\x3c" + size.to_bytes(2, byteorder="little") + data
logger.debug(f"sending pkt : {pkt}")
self.transport.write(pkt)
async def disconnect(self):
"""Close the TCP connection."""
if self.transport:
self.transport.close()
self.transport = None
logger.debug("TCP Connection closed")