mirror of
https://github.com/meshcore-dev/meshcore_py.git
synced 2026-04-20 22:13:49 +00:00
Merge pull request #6 from fdlamotte/awolden/fix-issues
Improvements and fixes
This commit is contained in:
commit
fb62b7888c
7 changed files with 164 additions and 19 deletions
94
examples/tcp_login_status.py
Normal file
94
examples/tcp_login_status.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import asyncio
|
||||
import argparse
|
||||
import logging
|
||||
from math import log
|
||||
|
||||
from meshcore import MeshCore
|
||||
from meshcore.events import EventType
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def main():
|
||||
# Parse command line arguments
|
||||
parser = argparse.ArgumentParser(description='Get status from a repeater via TCP connection')
|
||||
parser.add_argument('-host', '--hostname', required=True, help='TCP hostname or IP address')
|
||||
parser.add_argument('-port', '--port', type=int, required=True, help='TCP port number')
|
||||
parser.add_argument('-r', '--repeater', required=True, help='Repeater name')
|
||||
parser.add_argument('-pw', '--password', required=True, help='Password for login')
|
||||
args = parser.parse_args()
|
||||
|
||||
# Connect to the device
|
||||
print(f"Connecting to TCP {args.hostname}:{args.port}...")
|
||||
mc = await MeshCore.create_tcp(args.hostname, args.port, debug=True)
|
||||
|
||||
try:
|
||||
# Set up a simple event handler to log all events
|
||||
async def log_event(event):
|
||||
print(f"EVENT: {event.type.name} - Payload: {event.payload}")
|
||||
|
||||
# Subscribe to login events
|
||||
mc.subscribe(EventType.LOGIN_SUCCESS, log_event)
|
||||
mc.subscribe(EventType.LOGIN_FAILED, log_event)
|
||||
mc.subscribe(EventType.STATUS_RESPONSE, log_event)
|
||||
|
||||
# Get contacts
|
||||
await mc.ensure_contacts()
|
||||
|
||||
repeater = mc.get_contact_by_name(args.repeater)
|
||||
|
||||
if repeater is None:
|
||||
print(f"Repeater '{args.repeater}' not found in contacts.")
|
||||
print(f"Available contacts: {mc.contacts}")
|
||||
return
|
||||
|
||||
print(f"Found repeater: {repeater}")
|
||||
|
||||
# Send login request
|
||||
print(f"Sending login request to '{args.repeater}'...")
|
||||
login_cmd = await mc.commands.send_login(repeater, args.password)
|
||||
if login_cmd.type == EventType.ERROR:
|
||||
print(f"Login failed: {login_cmd.payload}")
|
||||
return
|
||||
|
||||
filter = {"pubkey_prefix": repeater["public_key"][0:12]}
|
||||
login_result = await mc.wait_for_event(EventType.LOGIN_SUCCESS, filter, timeout=10)
|
||||
print(f"Login result: {login_result}")
|
||||
|
||||
send_ver = await mc.commands.send_cmd(repeater, "ver")
|
||||
if send_ver.type == EventType.ERROR:
|
||||
print(f"Error sending version command: {send_ver.payload}")
|
||||
return
|
||||
await mc.wait_for_event(EventType.MESSAGES_WAITING)
|
||||
ver_msg = await mc.commands.get_msg()
|
||||
print(f"Version message: {ver_msg.payload}")
|
||||
|
||||
# Send status request
|
||||
print("Sending status request...")
|
||||
await mc.commands.send_statusreq(repeater)
|
||||
|
||||
# Wait for status response
|
||||
print("Waiting for status response event...")
|
||||
status_event = await mc.wait_for_event(EventType.STATUS_RESPONSE, timeout=5.0)
|
||||
|
||||
if status_event:
|
||||
print(f"Status response received: {status_event.payload}")
|
||||
else:
|
||||
print("No status response received within timeout")
|
||||
|
||||
finally:
|
||||
# Always disconnect properly
|
||||
print("Disconnecting...")
|
||||
await mc.disconnect()
|
||||
print("Disconnected from device")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
print("\nOperation cancelled by user")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue