CmdMessenger

repository·master·Indexed 42 days ago

https://github.com/thijse/arduino-cmdmessenger

A command-based messaging library for Arduino that enables structured, typed communication between microcontrollers and PC host applications over Serial, Bluetooth, or TCP/IP. It includes a line-based protocol supporting bidirectional communication and provides client implementations for C#/.NET, Python (py-cmdmessenger v0.1.0), and TypeScript/Node.js.

Tokens
20.4K
Snippets
47
Records
80
Agent score
52%

What's inside CmdMessenger

  1. Overview of Py-CmdMessenger samples

    master

    The Python samples demonstrate the host (PC) side of the CmdMessenger protocol. Each sample is designed to pair with a specific Arduino sketch found in the examples/ directory of the repository. They cover various communication patterns including simple receiving, two-way command/status exchange, multi-argument requests, binary data handling, and real-time data logging.

    | Python sample | Arduino sketch | Notes |
    |---|---|---|
    | [receive/](receive/) | `examples/Receive/` | Send only — toggles an LED |
    | [send_and_receive/](send_and_receive/) | `examples/SendAndReceive/` | Send `SetLed`, receive `Status` |
    | [send_and_receive_arguments/](send_and_receive_arguments/) | `examples/SendAndReceiveArguments/` | Multi-arg ack request/reply |
    | [send_and_receive_binary_arguments/](send_and_receive_binary_arguments/) | `examples/SendAndReceiveBinaryArguments/` | Binary encoded floats |
    | [data_logging/](data_logging/) | `examples/DataLogging/` | Real-time chart via web UI + console variant |
    | [arduino_controller/](arduino_controller/) | `examples/ArduinoController/` | Slider + toggle via web UI |
    | [simple_watchdog/](simple_watchdog/) | `examples/SimpleWatchdog/` | `SerialConnectionManager` + watchdog |
    | [temperature_control/](temperature_control/) | `examples/TemperatureControl/` | Full: chart + slider + ConnectionManager |
  2. Understand the Send/ACK lifecycle

    master

    The Send/ACK flow is identical across all language ports, following these steps:

    1. Queue command: The SendCommandQueue worker picks up the command.
    2. Suspend receive: The receive queue is suspended via Suspend() (or suspend()).
    3. Arm signal: The signal is prepared for waiting via PrepareForWait() (or prepare_for_wait()).
    4. Write to wire: Data is sent via the transport (e.g., transport.Write(bytes) or await transport.write(data)).
    5. Wait for ACK: The thread/loop waits for the signal (blocking in C#/Python, suspending in TypeScript).
    6. ACK arrives: The signal fires on the receive/transport thread (or resolves the Promise in TypeScript).
    7. Resume receive: The receive queue is resumed via Resume() (or resume()).
  3. Core Components of the Python CmdMessenger Library

    master

    The library is organized into several functional layers:

    • Core: Handles the fundamental data structures: Command, SendCommand, ReceivedCommand, Escaping, and BinaryConverter.
    • Engine: Manages the logic flow via CommunicationManager, CmdMessenger, and internal queues.
    • Transport: Provides the communication abstraction via the Transport ABC (Abstract Base Class), including SerialTransport and SerialSettings.
    • Connection Management: Handles lifecycle and connectivity via ConnectionManager and SerialConnectionManager.
    • Network: Provides non-serial communication via TcpTransport and TcpConnectionManager.
  4. Persist connection settings with ConnectionStorer

    master

    The Python library provides a ConnectionStorer abstraction to persist transport connection settings (like port names or baud rates) between sessions. This prevents users from having to re-select settings every time the application runs.

    JsonConnectionStorer is the standard implementation, which saves settings to a JSON file. By default, it uses the location: ~/.cmdmessenger/connection.json.

    Supported Transport Settings

    When using a storer, the following keys are used depending on the transport type:

    • Serial: port_name, baud_rate, dtr_enable
    • TCP/Network: host, port
    • Bluetooth (future): address, channel
    • Wi-Fi (future): ssid, host, port
    from pathlib import Path
    from py_cmdmessenger import JsonConnectionStorer
    
    # Initialize with default path (~/.cmdmessenger/connection.json)
    storer = JsonConnectionStorer()
    
    # Manually saving settings
    storer.save({"port_name": "COM3", "baud_rate": 115200, "dtr_enable": True})
    
    # Loading settings
    settings = storer.load()
  5. Understand the CmdMessenger wire format protocol

    master

    CmdMessenger uses a specific text and binary protocol to communicate with Arduino firmware. Understanding these delimiters and escaping rules is essential for implementing custom transports or debugging communication.

    Text Mode

    Messages follow the pattern: <cmdId>,<arg1>,<arg2>,...;

    Protocol Elements

    ElementDefaultNotes
    Command separator;Terminates a message
    Field separator,Between cmd ID and args
    Escape char/Prefixes literal , ; / in payload
    Line ending\r\nOptional, after ; when printLfCr=true

    Binary Mode

    Values are serialized byte-by-byte, with each byte individually escaped. The BoardType (16-bit AVR vs 32-bit ARM) determines the width of integers and floats.

    Arduino Constraints

    When designing messages, be aware of the hardware limits:

    • Command buffer: 64 bytes
    • Stream buffer: 512 bytes
    • ACK timeout: 5 seconds
    • Max callbacks: 50
  6. How SendCommand and ReceivedCommand are designed

    master

    The library uses two primary abstractions for command handling:

    1. SendCommand: Used to dispatch commands to the hardware. It supports a fluent API for adding arguments and configuring behavior, such as .withAck() for waiting for an acknowledgment, or .addArgument() to append data.
    2. ReceivedCommand: Used to read data received from the hardware. It provides an iterator-style API for extracting arguments, such as readInt32Arg(), and supports optional format-string reading via read("ifs").
  7. Manage command queues with SendQueue and ReceiveQueue

    master

    The Python library provides fine-grained control over how commands are queued using the following enums:

    SendQueue (controls outgoing commands):

    • DEFAULT: Standard queuing.
    • IN_FRONT_QUEUE: Places the command at the head of the queue.
    • AT_END_QUEUE: Places the command at the tail.
    • WAIT_FOR_EMPTY_QUEUE: Waits until the queue is empty before sending.
    • CLEAR_QUEUE: Clears the existing queue before sending.

    ReceiveQueue (controls incoming responses):

    • DEFAULT: Standard receiving.
    • WAIT_FOR_EMPTY_QUEUE: Waits for the receive queue to clear.
    • CLEAR_QUEUE: Clears the receive queue.
  8. How the CmdMessenger async pump architecture works

    master

    The CmdMessenger host-side library (TypeScript, Python, and C#) uses an Async Pump architecture. This is a single sequential processing loop that manages the lifecycle of all messages.

    The Pump's Responsibilities

    1. Ingestion: Receives incoming bytes from the transport, parses them into commands, and dispatches them to registered callbacks.
    2. Egress: Accepts outgoing commands posted from any context and processes the send queue sequentially.
    3. Command Collapsing: Because the send queue is processed sequentially, the pump can apply strategies (like CollapseCommandStrategy) to deduplicate or merge pending commands before they are sent.
    4. ACK Handling: When a user calls await sendCommand(cmd), the pump posts the command and creates a one-shot waiter that resolves when the corresponding ACK command ID is received (or times out).

    This architecture ensures that communication remains ordered and that the host can handle high-frequency command updates without overwhelming the serial buffer.

  9. Understand the Python CmdMessenger threading model

    master

    The library uses a threading core with an asyncio adapter to balance ease of use with modern asynchronous requirements.

    Threading Core (Default)

    • Synchronous/Blocking: send_command() is a blocking call, making it ideal for simple scripts.
    • Parity: Directly ports the C# synchronization primitives (lock, Sleep, Wait).
    • Concurrency: Uses threading.Lock for internal queue management and command strategies.

    Asyncio Adapter

    For integration with asynchronous frameworks like FastAPI or WebSockets, the library provides an AsyncCmdMessenger wrapper (or allows using asyncio.to_thread()).

    • Callbacks: Receive callbacks are integrated into the event loop using loop.call_soon_threadsafe().
    • Use Case: Best for web-based or highly concurrent applications where you want to avoid blocking the main event loop.
  10. Understand the Python CmdMessenger Sample Application Architecture

    master

    Python sample applications follow a two-tier architecture to separate application logic from user interface complexity:

    1. Console Tier: Uses plain Python print() and loops for direct command-line interaction (e.g., 1_receive, 2_send_and_receive).
    2. Web UI Tier: Uses FastAPI + WebSocket + HTML/JS to provide cross-platform, real-time GUIs (e.g., 5_data_logging, 6_arduino_controller). This replaces desktop frameworks like WinForms to ensure compatibility with Raspberry Pi, Linux, Mac, and Windows without heavy dependencies.

    Application Structure Pattern: To keep application logic clean, samples are split into three parts:

    • program.py: The thin entry point that initializes the UI and the logic class.
    • [logic_name].py: The core logic class containing setup(), loop(), and exit() methods. This is where you write your actual application code.
    • shared/ or static/: Modules that hide the complexity of WebSockets, FastAPI, or console utilities.
    # Typical program.py structure
    from shared import WebForm, ConsoleUtils
    from data_logging import DataLogging
    
    def main():
        form = WebForm(title="Data Logging", static_dir="static")
        app = DataLogging()
        ConsoleUtils.on_close = app.exit
        app.setup(form)
        form.start()       # blocks until Ctrl+C or window close
    
    if __name__ == "__main__":
        main()
  11. How to manage serial connections in Py-CmdMessenger

    master

    The library provides two ways to handle serial communication:

    1. Direct Transport: Use cmd_messenger.SerialTransport directly if you want to manually specify and manage the connection.
    2. Auto-detection: Use SerialConnectionManager if you want the library to automatically detect the correct port and verify that the connected device responds correctly. This is recommended for more robust applications (see simple_watchdog/simple_watchdog.py for an implementation example).
  12. How the CmdMessenger event system works

    master

    CmdMessenger uses a typed EventEmitter pattern to handle asynchronous communication events. Instead of generic string-based events, it provides specific, typed event handlers.

    Key events include:

    • newLineReceived: Fired when a new ReceivedCommand is parsed from the transport.
    • newLineSent: Fired when a SendCommand has been processed by the messenger.

    You subscribe to these events using the .on(event, handler) method and unsubscribe using .off(event, handler).

    // Example subscription pattern
    messenger.on('newLineReceived', (cmd: ReceivedCommand) => {
      console.log('Received:', cmd);
    });