GridConnect

GridConnect carries CAN frames as text, :X19490ABCN;, over TCP (port 12021, advertised over mDNS as _openlcb-can._tcp) and serial links. The interfaces here read in a background thread, reconnect by themselves when the link drops, and deliver pyolcb.frame.Frame objects to their listeners.

from pyolcb import Node, Address, GridConnectTcpInterface

interface = GridConnectTcpInterface("femtolcc-0001.local", 12021)
interface.wait_connected(5)
node = Node(Address("02.01.57.FF.00.01"), interface)

gridconnect

GridConnect, the ASCII encoding of CAN frames that OpenLCB uses over TCP (port 12021 by convention, advertised over mDNS as _openlcb-can._tcp) and over USB serial adapters:

:X19490ABCN;                   Verify Node ID, alias 0xABC, no data
:X19170ABCN020157000099;       Verified Node ID 02.01.57.00.00.99

:X then the 29-bit identifier in hex, N then up to eight data bytes in hex, and ;. Whitespace between frames is ignored.

GridConnectTcpInterface and GridConnectSerialInterface are Interface implementations that read in a background thread and reconnect by themselves when the link drops.

class pyolcb.gridconnect.GridConnectInterface(reconnect: bool = True, retry_interval: float = 1.0, start: bool = True)

Base for GridConnect transports: a reader thread, listener fan-out and automatic reconnection. Subclasses implement _open(), _read(), _write() and _close_link().

Parameters:
  • reconnect (bool) – Keep trying to (re)connect when the link fails. When False the interface stops after the first disconnect.

  • retry_interval (float) – Seconds between reconnection attempts (doubling up to 30 s).

  • start (bool) – Start connecting immediately. Otherwise call start().

close()

Disconnect and stop reconnecting.

property connected: bool

True while frames can be sent. A CAN bus is always connected.

describe() str

A short human-readable description of the endpoint.

register_listener(function: callable)

Register a function to be called with every frame received. On a CAN bus the argument is a can.Message; on GridConnect interfaces it is a Frame. Both have arbitration_id and data.

send(message) bool

Send a Frame or Message. Returns False (and drops the frame) while disconnected.

start()

Start the reader thread, which connects and keeps reconnecting.

wait_connected(timeout: float = None) bool

Block until the link is up. Returns False on timeout.

class pyolcb.gridconnect.GridConnectParser

Incremental GridConnect decoder.

Feed it whatever arrives on the wire, in pieces of any size. Follows the same rules as AOLCB’s parser: a : always starts a new frame (abandoning a partial one), text outside :; is skipped, and malformed or overlong frames are dropped. That makes it safe on a serial console that mixes human-readable messages with frames.

feed(data: bytes | str) list[Frame]

Consume data and return every complete frame it finished.

class pyolcb.gridconnect.GridConnectSerialInterface(port: str, baudrate: int = 115200, **kwargs)

GridConnect over a serial port: a USB-LCC adapter, or a FemtoLCC board’s USB console, which switches to bridging LCC traffic when it sees the first GridConnect frame from the computer. Requires pyserial.

RTS and DTR are held low so that opening the port does not reset an ESP32 through its USB-serial auto-reset circuit.

Parameters:
  • port (str) – Device, e.g. /dev/ttyACM0 or COM3.

  • baudrate (int) – Line speed; USB CDC adapters ignore it.

describe() str

A short human-readable description of the endpoint.

class pyolcb.gridconnect.GridConnectTcpInterface(host: str, port: int = 12021, connect_timeout: float = 5.0, **kwargs)

GridConnect over TCP, as served by an OpenLCB hub, a FemtoLCC node’s WiFi link, JMRI’s hub or OpenMRN’s hub program.

Parameters:
  • host (str) – Host name or address.

  • port (int) – TCP port, 12021 by default.

describe() str

A short human-readable description of the endpoint.

pyolcb.gridconnect.browse_mdns(timeout: float = 2.0) list[dict]

Find GridConnect TCP hubs advertised over mDNS as _openlcb-can._tcp. Needs the optional zeroconf package; returns [] without it. Each result is {"name", "host", "port", "addresses"}.

pyolcb.gridconnect.format_frame(frame: Frame) str

Encode a frame as GridConnect text, without a line ending.

pyolcb.gridconnect.list_serial_ports() list[dict]

Serial ports pyserial can see, as {"device", "description"} dicts.

pyolcb.gridconnect.mdns_available() bool

True if the optional zeroconf package is installed.

pyolcb.gridconnect.parse_frame(text: str) Frame | None

Decode one GridConnect frame such as :X19490ABCN;. Returns None for anything that is not a valid extended data frame.

Frames

frame

Transport-neutral OpenLCB CAN frames.

Every OpenLCB frame is a 29-bit extended CAN frame, whether it travels on a real CAN bus or as GridConnect text over TCP or a serial line:

bit 28      always 1
bit 27      1 = OpenLCB message frame, 0 = CAN control frame
bits 26-12  variable field
bits 11-0   source alias

In a message frame bits 26-24 are the frame type. For type 1 (global or addressed message) bits 23-12 are the MTI; for types 2-5 (datagram) they are the destination alias. In a control frame bits 26-24 are 4-7 for the CID frames that carry a 12-bit slice of the node ID, and 0 for RID/AMD/AME/AMR.

Frame is duck-type compatible with can.Message (it has arbitration_id, data and is_extended_id), so code written for python-can frames works on GridConnect frames too.

class pyolcb.frame.Frame(arbitration_id: int, data: bytes | bytearray | list[int] = b'')

A single OpenLCB CAN frame, independent of how it is carried.

Parameters:
  • arbitration_id (int) – The 29-bit CAN identifier.

  • data (bytes | bytearray | list[int]) – Up to eight payload bytes.

classmethod cid(sequence: int, node_id: int, alias: int) Frame

Check ID frame sequence (7 = CID1 with node ID bits 47-36, down to 4 = CID4 with bits 11-0).

classmethod control(variable: int, alias: int, data: bytes = b'') Frame

A CAN control frame (RID, AMD, AME, AMR, or a CID with the ID slice in variable).

property destination_alias: int | None

The destination alias of an addressed message or datagram frame, or None for a global message or control frame.

property framing: int

The multi-frame flags (ADDRESSED_*) of an addressed message.

classmethod from_can_message(message) Frame

Convert a can.Message (or anything with arbitration_id and data).

property is_addressed: bool

True for an addressed (not global) message frame.

property is_cid: bool

True for the four Check ID frames of alias allocation.

property is_control: bool

True for a CAN control frame (CID, RID, AMD, AME, AMR).

classmethod message(mti: int, alias: int, data: bytes = b'') Frame

A global message frame, or one frame of an addressed message (data includes the destination).

property mti: int

The 12-bit MTI of a global or addressed message, 0 for anything else.

property payload: bytes

The data bytes after the destination alias of an addressed message.

pyolcb.frame.addressed_frames(mti: int, source: int, destination: int, payload: bytes = b'') list[Frame]

Split an addressed message into frames: two bytes of destination alias and framing flags, then up to six payload bytes per frame.

pyolcb.frame.datagram_frames(source: int, destination: int, data: bytes) list[Frame]

Split a datagram (1-72 bytes) into only/first/middle/final frames.

pyolcb.frame.format_event_id(event_id: int) str

0x0201570000990001 -> '02.01.57.00.00.99.00.01'.

pyolcb.frame.format_node_id(node_id: int) str

0x020157000099 -> '02.01.57.00.00.99'.

pyolcb.frame.parse_id(text: str | int | bytes, length: int) int

Parse a node ID (length 6) or event ID (length 8) written as dotted hex (02.01.57.00.00.99), with other separators, or as plain hex.