Skip to content

Python reference

The uclog package, generated from its docstrings (source). A guide: Python.

ucLog for scripts: connect to a device through the ucLog hub, send and receive on any port, and decode logs.

import uclog

with uclog.connect(logdata="build/zephyr/zephyr.elf") as dev:
    print(dev.device_info)
    dev.send(61, b"version s\r")
    print(dev.recv(61, timeout=1.0))
    for rec in dev.logs(timeout=5.0):
        print(rec)

All device I/O goes through the native hub (uclog-hub); a hub is started when none is running and stopped when the script exits.

Connecting

uclog.connect

uclog.connect(host=None, device=None, *, logdata=None, files_dir=None, stdin=None, cache_dir=None, logdata_domain=None, logdata_url=None, logdata_token=None, discover_remote=False, approval_timeout=60.0, device_timeout=10.0, max_queue=10000, skip_backlog=False, hub_args=())

Connect to a hub, starting one if none is running.

  • host: a hub's address ("192.168.1.20", "labpc.local:9001"); default $UCLOG_HOST.
  • device: choose the hub (or start one) for this device: a URI (serial:/dev/ttyACM0) or a serial number.
  • logdata: a file or a list of files (.logdata or ELF). See Resolver for the order.
  • files_dir: answer the device's file syscalls inside this directory (else OPEN and MKDIR get EACCES).
  • stdin: callable(max_bytes) → bytes/str for the device's stdin reads (else EOF).
  • device_timeout: how long to wait for data from the device itself (a frame, or a pulse the hub sends only while the device is alive), after the hub's backlog replay, before raising DeviceNotRespondingError. A hub opens a BLE device in about 5 s. None: don't wait.
  • approval_timeout: for remote hubs, how long the hub may hold the connection while its operator approves it (before device_timeout starts).
  • skip_backlog: drop the frames the hub buffered before this connection.
  • cache_dir, logdata_domain, logdata_url, logdata_token: the logdata cache and service (default: $UCLOG_CACHE_DIR, $UCLOG_LOGDATA_DOMAIN, $UCLOG_LOGDATA_URL, $UCLOG_LOGDATA_TOKEN).
  • discover_remote: also look for hubs on the network (mDNS; needs the mdns extra).
  • max_queue: entries kept per queue (logs, frames, each port); the oldest are dropped and counted in dropped.
  • hub_args: more arguments for a hub this call starts (e.g. ["--bind", "0.0.0.0"]).

Connection

One client connection to a hub. Create it with uclog.connect().

A reader thread receives frames, answers port-63 syscalls and decodes logs; a writer thread sends frames and a pulse every 0.5 s. Received data waits in bounded queues: per port (recv), all frames (frames), log records (logs) and semihost console output (console). When a queue is full the oldest entry is dropped and counted in dropped.

Connection.closed

The connection is closed (by close(), or by the hub).

Connection.alive

Bytes (data or pulses) arrived within the last 2 s.

Connection.device_info

The port-62 device info (board, app_hash, ...). Waits up to 2 s for it, or longer while the hub may still be opening the device (until connect()'s device_timeout ends).

Connection.wait_device_info

Connection.wait_device_info(timeout=2.0)

The device info, waiting up to timeout seconds for it (None: as long as it takes); None if none came.

Connection.logdata

The logdata in use, by target digit.

Connection.load_logdata

Connection.load_logdata(path)

Use a logdata file (or ELF) from now on, for its target.

Connection.send

Connection.send(port, data)

Send data (bytes, or str as UTF-8) on port.

Connection.send_frame

Connection.send_frame(payload)

Send a complete payload (frame header included).

Connection.recv

Connection.recv(port, timeout=None)

The next payload received on port, or None after timeout seconds.

Connection.frames

Connection.frames(timeout=None, duration=None)

Every received payload (frame header included, pulses excluded), until timeout seconds pass without one, or duration seconds in all.

Connection.logs

Connection.logs(timeout=None, duration=None)

Decoded log records (and error records), until timeout seconds pass without one, or duration seconds in all.

Connection.console

Connection.console(timeout=None)

Semihost stdout/stderr output: (fid, bytes), or None after timeout seconds. Writes to the same stream that have not been read yet are joined.

Connection.command

Connection.command(obj, timeout=2.0)

Send a CBOR command map on port 0 and wait for the reply whose rsp equals its cmd. Other port-0 frames stay queued for recv(0). Returns the reply map, or None on timeout.

Connection.close

Connection.close(flush_timeout=2.0)

Close the connection, after sending what send() queued (waiting up to flush_timeout seconds). A hub started for it by the library is stopped.

uclog.aio

asyncio API: the same calls as the sync one, awaited.

async with await uclog.aio.connect(logdata="build/zephyr/zephyr.elf") as dev:
    await dev.send(61, b"version s\r")
    print(await dev.recv(61, timeout=1.0))
    async for rec in dev.logs(timeout=5.0):
        print(rec)

The connection's I/O runs on its own threads (see uclog.Connection); waits run in the default executor so the event loop is never blocked.

aio.connect

aio.connect(*args, **kw)

Same arguments as uclog.connect().

aio.Connection

Async wrapper of uclog.Connection. sync is the wrapped connection.

Connection.device_info(timeout=None)

The port-62 map. Without timeout, waits as long as the sync device_info property.

Connection.send(port, data)

As Connection.send, awaited.

Connection.send_frame(payload)

As Connection.send_frame, awaited.

Connection.recv(port, timeout=None)

As Connection.recv, awaited.

Connection.console(timeout=None)

As Connection.console, awaited.

Connection.command(obj, timeout=2.0)

As Connection.command, awaited.

Connection.logs(timeout=None, duration=None)

As Connection.logs, awaited.

Connection.frames(timeout=None, duration=None)

As Connection.frames, awaited.

Connection.close()

As Connection.close, awaited.

Hubs and devices

uclog.list_hubs

uclog.list_hubs(discover_remote=False, timeout=1.0)

Hubs on this machine (registry), plus remote hubs over mDNS when discover_remote is set (needs the uclog[mdns] extra).

uclog.list_devices

uclog.list_devices(usb_filters=())

Local USB devices the hub can open (uclog-hub list --json).

uclog.start_hub

uclog.start_hub(device, extra_args=(), timeout=15.0)

Start a hub for device (a URI such as serial:/dev/ttyACM0) and wait until it listens. It is stopped when the script exits.

The hub is private to this machine: it listens on 127.0.0.1 and is not advertised over mDNS (pass --bind/other options in extra_args to change that). It still writes its registry file, so other local clients and connect() find it.

uclog.stop_hub

uclog.stop_hub(host_or_pid)

Stop a hub on this machine: by pid (an int), or by port ("9001", "localhost:9001"). Returns True if one was found.

A hub this process did not start is signalled only if its registry entry names the pid, its port is listening and the process looks like a hub, so a stale entry whose pid has been reused does not kill an unrelated process.

HubInfo

A running hub.

Field Type
name str
host str
port int
device str
serial_number str
kind str
busy Union
pid Union
local bool
description str

HubInfo.address

host:port, as connect(host=...) takes it.

DeviceInfo

A device the hub can open (uclog-hub list --json).

Field Type
kind str
uri str
name str
serial str
description str
extra dict

Logs and logdata

LogRecord

A decoded log line, or an error record when error is set (protocol.md §7).

Field Type
ts float
level str
target Union
file str
line str
text str
error Union

LogData

One logdata file: the formats of one target (§6.1).

LogData.load

LogData.load(cls, path)

Load a .logdata file or an ELF file with a .logdata_cbor section.

LogData.target

The target digit this logdata decodes.

LogData.lookup_sym

LogData.lookup_sym(a)

An address as {sym}%p shows it: function+0xOFF, else the nearest variable below it (within 0x3000) as file:name+0xOFF, else hex (protocol.md §6.3).

LogData.enum_name

LogData.enum_name(name, v)

An enum value as {enum:name}%d shows it: the enumerator, <name:v> for a value the enum lacks, or <!name:v> when there is no such enum.

LogData.decode

LogData.decode(addr, args)

Decode one type-0/1 log frame. Returns (level, file, line, text); raises DecodeError (and nothing else, even for a malformed logdata entry).

Resolver

Finds the logdata for a device's app hash, in this order:

  1. the given files (.logdata or ELF); a file named <app_hash>.logdata is used for that hash only;
  2. <cache_dir>/<app_hash>.logdata (default: $UCLOG_CACHE_DIR, else $XDG_CACHE_HOME/uclog, else ~/.cache/uclog);
  3. the logdata service, when domain is set (GET {url}/logdata/{domain}/{app_hash}, with token if set; a refused token is retried without it, and token_refused is set). The download is saved in the cache;
  4. the first given file, with a note.

connect() makes one; use it directly to resolve logdata without a device.

Resolver.manual

Resolver.manual()

The given files, loaded (used before any device info arrives).

Resolver.resolve

Resolver.resolve(app_hash)

The logdata for app_hash: (list of LogData, where it came from, notes on the way).

Errors

UclogError

Exception, a Exception. Base class.

NoHubError

Exception, a UclogError. No hub is running and none could be started (no device, or no uclog-hub executable). devices lists the devices that were found, if any.

MultipleHubsError

Exception, a UclogError. More than one hub (or device) matches. candidates lists them; pass host= (or set UCLOG_HOST) or device= to choose.

HubBusyError

Exception, a UclogError. The hub closed the connection at once: another client is connected (a hub serves one client at a time), or the hub refused this one.

ApprovalTimeoutError

Exception, a UclogError. A remote hub held the connection open without data while its operator was asked to approve it, and nobody approved it in time.

DeviceNotRespondingError

Exception, a UclogError. Connected to the hub, but the device sent nothing.

HubStartError

Exception, a UclogError. A hub started by the library exited or did not report that it was ready.

ConnectionClosedError

Exception, a UclogError. The connection to the hub is closed.

DecodeError

Exception, a ValueError. A log frame could not be decoded. kind is the error-record kind (unknown_format, missing_arg, extra_bytes, format).

LogDataError

Exception, a ValueError. A logdata file could not be loaded.

Protocol helpers

uclog.cbor

Minimal CBOR (RFC 8949): enough for every payload in the ucLog protocol.

From spec/reference/ucref.py. Standard library only, so the package has no dependencies.

cbor.dumps

cbor.dumps(v)

Encode: shortest heads, map keys in insertion order, floats as float64.

cbor.loads

cbor.loads(data)

Decode one item. Array map keys become tuples; tags are ignored. Any malformed input raises CborError (and nothing else).

uclog.proto

Framing: COBS, the frame scanner, the 2-byte peek and the frame header (protocol.md §2–§3).

From spec/reference/ucref.py; the scanner works on whole chunks instead of byte by byte.

proto.cobs_encode

proto.cobs_encode(data)

COBS encode. A final 0xFF block that ends exactly at the end of the input is not followed by a 0x01 code byte (the decoder infers it).

proto.cobs_decode

proto.cobs_decode(data)

COBS decode. Raises CobsError on a zero byte or a code that runs past the end.

proto.FrameScanner

Splits a byte stream on 0x00 (§2.2).

Bytes before the first 0x00 are dropped (they belong to a frame whose start was missed). feed returns ('frame', encoded) for every non-empty run and ('oversize', n) for a run longer than max_frame (discarded up to the next 0x00). Empty runs, such as pulses, yield nothing.

FrameScanner.feed(data)

Scan received bytes; returns the events of the runs they complete.

proto.peek

proto.peek(encoded)

Classify an encoded (still COBS) frame from its first 2 bytes. Returns ('pulse',) | ('log', type) | ('port', port).

proto.mux_decode

proto.mux_decode(payload)

Split a decoded frame. Returns ('pulse',) | ('port', port, data) | ('log', type, target, addr, args) | ('error', reason).

proto.mux_encode

proto.mux_encode(port, data)

A port frame's payload: the header byte for port, then data (not yet COBS-encoded).

proto.wire

proto.wire(payload)

The complete on-the-wire frame, both delimiters included (§2.1).

proto.wire_port

proto.wire_port(port, data)

The complete on-the-wire frame for data on port.

Constants

Name Value
proto.MAX_WIRE 1452
proto.MAX_FRAME 1450
proto.MAX_PAYLOAD 1444
proto.MAX_PORT_DATA 1443
proto.TYPE_LOG 0
proto.TYPE_MEM 1
proto.TYPE_RES 2
proto.TYPE_PORT 3
proto.PORT_COMMAND 0
proto.PORT_PLOT 60
proto.PORT_TERMINAL 61
proto.PORT_DEVICE_INFO 62
proto.PORT_SEMIHOST 63