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 (.logdataor ELF). SeeResolverfor 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 (beforedevice_timeoutstarts).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 themdnsextra).max_queue: entries kept per queue (logs, frames, each port); the oldest are dropped and counted indropped.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:
- the given
files(.logdataor ELF); a file named<app_hash>.logdatais used for that hash only; <cache_dir>/<app_hash>.logdata(default: $UCLOG_CACHE_DIR, else $XDG_CACHE_HOME/uclog, else ~/.cache/uclog);- the logdata service, when
domainis set (GET {url}/logdata/{domain}/{app_hash}, withtokenif set; a refused token is retried without it, andtoken_refusedis set). The download is saved in the cache; - 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 |