{"id":30115543,"url":"https://github.com/meshcore-dev/meshcore_py","last_synced_at":"2025-08-10T08:23:14.017Z","repository":{"id":284192750,"uuid":"954129670","full_name":"meshcore-dev/meshcore_py","owner":"meshcore-dev","description":"Python bindings for meshcore","archived":false,"fork":false,"pushed_at":"2025-08-06T09:49:28.000Z","size":269,"stargazers_count":18,"open_issues_count":1,"forks_count":6,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-08-06T10:34:42.825Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/meshcore-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-03-24T15:52:24.000Z","updated_at":"2025-08-06T09:49:32.000Z","dependencies_parsed_at":"2025-06-02T04:27:54.215Z","dependency_job_id":"ed2840b9-a386-4d0d-bc1f-320d787e85ce","html_url":"https://github.com/meshcore-dev/meshcore_py","commit_stats":null,"previous_names":["fdlamotte/meshcore_py","meshcore-dev/meshcore_py"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/meshcore-dev/meshcore_py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meshcore-dev%2Fmeshcore_py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meshcore-dev%2Fmeshcore_py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meshcore-dev%2Fmeshcore_py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meshcore-dev%2Fmeshcore_py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/meshcore-dev","download_url":"https://codeload.github.com/meshcore-dev/meshcore_py/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meshcore-dev%2Fmeshcore_py/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269694064,"owners_count":24460342,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-08-10T08:23:11.703Z","updated_at":"2025-08-10T08:23:14.007Z","avatar_url":"https://github.com/meshcore-dev.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Python MeshCore\n\nPython library for interacting with [MeshCore](https://meshcore.co.uk) companion radio nodes.\n\n## Installation\n\n```bash\npip install meshcore\n```\n\n## Quick Start\n\nConnect to your device and send a message:\n\n```python\nimport asyncio\nfrom meshcore import MeshCore, EventType\n\nasync def main():\n    # Connect to your device\n    meshcore = await MeshCore.create_serial(\"/dev/ttyUSB0\")\n    \n    # Get your contacts\n    result = await meshcore.commands.get_contacts()\n    if result.type == EventType.ERROR:\n        print(f\"Error getting contacts: {result.payload}\")\n        return\n        \n    contacts = result.payload\n    print(f\"Found {len(contacts)} contacts\")\n    \n    # Send a message to the first contact\n    if contacts:\n        # Get the first contact\n        contact = next(iter(contacts.items()))[1]\n        \n        # Pass the contact object directly to send_msg\n        result = await meshcore.commands.send_msg(contact, \"Hello from Python!\")\n        \n        if result.type == EventType.ERROR:\n            print(f\"Error sending message: {result.payload}\")\n        else:\n            print(\"Message sent successfully!\")\n    \n    await meshcore.disconnect()\n\nasyncio.run(main())\n```\n\n## Development Setup\n\nTo set up for development:\n\n```bash\n# Create and activate virtual environment\npython -m venv venv\nsource venv/bin/activate  # On Windows: venv\\Scripts\\activate\n\n# Install in development mode\npip install -e .\n\n# Run examples\npython examples/pubsub_example.py -p /dev/ttyUSB0\n```\n\n## Usage Guide\n\n### Command Return Values\n\nAll command methods in MeshCore return an `Event` object that contains both the event type and its payload. This allows for consistent error handling and type checking:\n\n```python\n# Command result structure\nresult = await meshcore.commands.some_command()\n\n# Check if the command was successful or resulted in an error\nif result.type == EventType.ERROR:\n    # Handle error case\n    print(f\"Command failed: {result.payload}\")\nelse:\n    # Handle success case - the event type will be specific to the command\n    # (e.g., EventType.DEVICE_INFO, EventType.CONTACTS, EventType.MSG_SENT)\n    print(f\"Command succeeded with event type: {result.type}\")\n    # Access the payload data\n    data = result.payload\n```\n\nCommon error handling pattern:\n\n```python\nresult = await meshcore.commands.send_msg(contact, \"Hello!\")\nif result.type == EventType.ERROR:\n    print(f\"Error sending message: {result.payload}\")\nelse:\n    # For send_msg, a successful result will have type EventType.MSG_SENT\n    print(f\"Message sent with expected ack: {result.payload['expected_ack'].hex()}\")\n```\n\n### Connecting to Your Device\n\nConnect via Serial, BLE, or TCP:\n\n```python\n# Serial connection\nmeshcore = await MeshCore.create_serial(\"/dev/ttyUSB0\", 115200, debug=True)\n\n# BLE connection (scans for devices if address not provided)\nmeshcore = await MeshCore.create_ble(\"12:34:56:78:90:AB\")\n\n# TCP connection\nmeshcore = await MeshCore.create_tcp(\"192.168.1.100\", 4000)\n```\n\n#### Auto-Reconnect and Connection Events\n\nEnable automatic reconnection when connections are lost:\n\n```python\n# Enable auto-reconnect with custom retry limits\nmeshcore = await MeshCore.create_tcp(\n    \"192.168.1.100\", 4000,\n    auto_reconnect=True,\n    max_reconnect_attempts=5\n)\n\n# Subscribe to connection events\nasync def on_connected(event):\n    print(f\"Connected: {event.payload}\")\n    if event.payload.get('reconnected'):\n        print(\"Successfully reconnected!\")\n\nasync def on_disconnected(event):\n    print(f\"Disconnected: {event.payload['reason']}\")\n    if event.payload.get('max_attempts_exceeded'):\n        print(\"Max reconnection attempts exceeded\")\n\nmeshcore.subscribe(EventType.CONNECTED, on_connected)\nmeshcore.subscribe(EventType.DISCONNECTED, on_disconnected)\n\n# Check connection status\nif meshcore.is_connected:\n    print(\"Device is currently connected\")\n```\n\n**Auto-reconnect features:**\n- Exponential backoff (1s, 2s, 4s, 8s max delay)\n- Configurable retry limits (default: 3 attempts)\n- Automatic disconnect detection (especially useful for TCP connections)\n- Connection events with detailed information\n\n### Using Commands (Synchronous Style)\n\nSend commands and wait for responses:\n\n```python\n# Get device information\nresult = await meshcore.commands.send_device_query()\nif result.type == EventType.ERROR:\n    print(f\"Error getting device info: {result.payload}\")\nelse:\n    print(f\"Device model: {result.payload['model']}\")\n\n# Get list of contacts\nresult = await meshcore.commands.get_contacts()\nif result.type == EventType.ERROR:\n    print(f\"Error getting contacts: {result.payload}\")\nelse:\n    contacts = result.payload\n    for contact_id, contact in contacts.items():\n        print(f\"Contact: {contact['adv_name']} ({contact_id})\")\n\n# Send a message (destination key in bytes)\nresult = await meshcore.commands.send_msg(dst_key, \"Hello!\")\nif result.type == EventType.ERROR:\n    print(f\"Error sending message: {result.payload}\")\n\n# Setting device parameters\nresult = await meshcore.commands.set_name(\"My Device\")\nif result.type == EventType.ERROR:\n    print(f\"Error setting name: {result.payload}\")\n    \nresult = await meshcore.commands.set_tx_power(20)  # Set transmit power\nif result.type == EventType.ERROR:\n    print(f\"Error setting TX power: {result.payload}\")\n```\n\n### Finding Contacts\n\nEasily find contacts by name or key:\n\n```python\n# Find a contact by name\ncontact = meshcore.get_contact_by_name(\"Bob's Radio\")\nif contact:\n    print(f\"Found Bob at: {contact['adv_lat']}, {contact['adv_lon']}\")\n    \n# Find by partial key prefix\ncontact = meshcore.get_contact_by_key_prefix(\"a1b2c3\")\n```\n\n### Event-Based Programming (Asynchronous Style)\n\nSubscribe to events to handle them asynchronously:\n\n```python\n# Subscribe to incoming messages\nasync def handle_message(event):\n    data = event.payload\n    print(f\"Message from {data['pubkey_prefix']}: {data['text']}\")\n    \nsubscription = meshcore.subscribe(EventType.CONTACT_MSG_RECV, handle_message)\n\n# Subscribe to advertisements\nasync def handle_advert(event):\n    print(\"Advertisement detected!\")\n    \nmeshcore.subscribe(EventType.ADVERTISEMENT, handle_advert)\n\n# When done, unsubscribe\nmeshcore.unsubscribe(subscription)\n```\n\n#### Filtering Events by Attributes\n\nFilter events based on their attributes to handle only specific ones:\n\n```python\n# Subscribe only to messages from a specific contact\nasync def handle_specific_contact_messages(event):\n    print(f\"Message from Alice: {event.payload['text']}\")\n    \ncontact = meshcore.get_contact_by_name(\"Alice\")\nif contact:\n    alice_subscription = meshcore.subscribe(\n        EventType.CONTACT_MSG_RECV,\n        handle_specific_contact_messages,\n        attribute_filters={\"pubkey_prefix\": contact[\"public_key\"][:12]}\n    )\n\n# Send a message and wait for its specific acknowledgment\nasync def send_and_confirm_message(meshcore, dst_key, message):\n    # Send the message and get information about the sent message\n    sent_result = await meshcore.commands.send_msg(dst_key, message)\n    \n    # Extract the expected acknowledgment code from the message sent event\n    if sent_result.type == EventType.ERROR:\n        print(f\"Error sending message: {sent_result.payload}\")\n        return False\n        \n    expected_ack = sent_result.payload[\"expected_ack\"].hex()\n    print(f\"Message sent, waiting for ack with code: {expected_ack}\")\n    \n    # Wait specifically for this acknowledgment\n    result = await meshcore.wait_for_event(\n        EventType.ACK,\n        attribute_filters={\"code\": expected_ack},\n        timeout=10.0\n    )\n    \n    if result:\n        print(\"Message confirmed delivered!\")\n        return True\n    else:\n        print(\"Message delivery confirmation timed out\")\n        return False\n```\n\n### Hybrid Approach (Commands + Events)\n\nCombine command-based and event-based styles:\n\n```python\nimport asyncio\n\nasync def main():\n    # Connect to device\n    meshcore = await MeshCore.create_serial(\"/dev/ttyUSB0\")\n    \n    # Set up event handlers\n    async def handle_ack(event):\n        print(\"Message acknowledged!\")\n    \n    async def handle_battery(event):\n        print(f\"Battery level: {event.payload}%\")\n    \n    # Subscribe to events\n    meshcore.subscribe(EventType.ACK, handle_ack)\n    meshcore.subscribe(EventType.BATTERY, handle_battery)\n    \n    # Create background task for battery checking\n    async def check_battery_periodically():\n        while True:\n            # Send command (returns battery level)\n            result = await meshcore.commands.get_bat()\n            if result.type == EventType.ERROR:\n                print(f\"Error checking battery: {result.payload}\")\n            else:\n                print(f\"Battery level: {result.payload.get('level', 'unknown')}%\")\n            await asyncio.sleep(60)  # Wait 60 seconds between checks\n    \n    # Start the background task\n    battery_task = asyncio.create_task(check_battery_periodically())\n    \n    # Send manual command and wait for response\n    await meshcore.commands.send_advert(flood=True)\n    \n    try:\n        # Keep the main program running\n        await asyncio.sleep(float('inf'))\n    except asyncio.CancelledError:\n        # Clean up when program ends\n        battery_task.cancel()\n        await meshcore.disconnect()\n\n# Run the program\nasyncio.run(main())\n```\n\n### Auto-Fetching Messages\n\nLet the library automatically fetch incoming messages:\n\n```python\n# Start auto-fetching messages\nawait meshcore.start_auto_message_fetching()\n\n# Just subscribe to message events - the library handles fetching\nasync def on_message(event):\n    print(f\"New message: {event.payload['text']}\")\n    \nmeshcore.subscribe(EventType.CONTACT_MSG_RECV, on_message)\n\n# When done\nawait meshcore.stop_auto_message_fetching()\n```\n\n### Debug Mode\n\nEnable debug logging for troubleshooting:\n\n```python\n# Enable debug mode when creating the connection\nmeshcore = await MeshCore.create_serial(\"/dev/ttyUSB0\", debug=True)\n```\n\nThis logs detailed information about commands sent and events received.\n\n## Common Examples\n\n### Sending Messages to Contacts\n\nCommands that require a destination (`send_msg`, `send_login`, `send_statusreq`, etc.) now accept either:\n- A string with the hex representation of a public key\n- A contact object with a \"public_key\" field\n- Bytes object (for backward compatibility)\n\n```python\n# Get contacts and send to a specific one\nresult = await meshcore.commands.get_contacts()\nif result.type == EventType.ERROR:\n    print(f\"Error getting contacts: {result.payload}\")\nelse:\n    contacts = result.payload\n    for key, contact in contacts.items():\n        if contact[\"adv_name\"] == \"Alice\":\n            # Option 1: Pass the contact object directly\n            result = await meshcore.commands.send_msg(contact, \"Hello Alice!\")\n            if result.type == EventType.ERROR:\n                print(f\"Error sending message: {result.payload}\")\n            \n            # Option 2: Use the public key string\n            result = await meshcore.commands.send_msg(contact[\"public_key\"], \"Hello again Alice!\")\n            if result.type == EventType.ERROR:\n                print(f\"Error sending message: {result.payload}\")\n            \n            # Option 3 (backward compatible): Convert the hex key to bytes\n            dst_key = bytes.fromhex(contact[\"public_key\"])\n            result = await meshcore.commands.send_msg(dst_key, \"Hello once more Alice!\")\n            if result.type == EventType.ERROR:\n                print(f\"Error sending message: {result.payload}\")\n            break\n\n# You can also directly use a contact found by name\ncontact = meshcore.get_contact_by_name(\"Bob\")\nif contact:\n    result = await meshcore.commands.send_msg(contact, \"Hello Bob!\")\n    if result.type == EventType.ERROR:\n        print(f\"Error sending message: {result.payload}\")\n```\n\n### Monitoring Channel Messages\n\n```python\n# Subscribe to channel messages\nasync def channel_handler(event):\n    msg = event.payload\n    print(f\"Channel {msg['channel_idx']}: {msg['text']}\")\n    \nmeshcore.subscribe(EventType.CHANNEL_MSG_RECV, channel_handler)\n```\n\n## Examples in the Repo\n\nCheck the `examples/` directory for more:\n\n- `pubsub_example.py`: Event subscription system with auto-fetching\n- `serial_infos.py`: Quick device info retrieval\n- `serial_msg.py`: Message sending and receiving\n- `ble_t1000_infos.py`: BLE connections\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeshcore-dev%2Fmeshcore_py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmeshcore-dev%2Fmeshcore_py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeshcore-dev%2Fmeshcore_py/lists"}