API and Protocol docs have been updated (GPT 5.6) The API methods for fetching a sepcific signal descriptor and fetching all signal descriptors now work in offline mode, the core_tui.py example has been updated to fit the new tri mode architecture and loading recording offload -> processing and then processing -> playback engine all works
214 lines
8.5 KiB
Markdown
214 lines
8.5 KiB
Markdown
# DynaLab Core API
|
|
|
|
`Core` runs a DynaLab Core instance: it accepts connector data, retains live
|
|
values, records data, prepares DLPak archives, plays recordings, and manages
|
|
derive units. Connector authors should also read the [protocol guide](Protocol.md).
|
|
|
|
> [!IMPORTANT]
|
|
> **Packet model change.** The default packet and signal classes are lightweight
|
|
> `@dataclass(slots=True)` objects. They intentionally do not validate or coerce
|
|
> input at construction time. Pydantic models are retained only at a JSON
|
|
> boundary and are named with the `Json` prefix, such as `JsonHeartbeat` and
|
|
> `JsonConnectorHello`. Use the `Json*` classes with JSON-over-TCP and convert
|
|
> with `to_pure()`; use the unprefixed classes inside Core, connectors, and
|
|
> derive code. See [Packet representations](Protocol.md#packet-representations).
|
|
|
|
> [!WARNING]
|
|
> DynaLab Core is in alpha. Its API and protocol can change between releases.
|
|
|
|
## Quick start
|
|
|
|
```python
|
|
from dynalab_core import Core
|
|
from dynalab_core.config import CoreConfig
|
|
|
|
core = Core(CoreConfig(host="127.0.0.1", port=58763))
|
|
try:
|
|
core.start()
|
|
core.wait()
|
|
finally:
|
|
core.stop()
|
|
```
|
|
|
|
Constructing `Core` starts its internal workers. `start()` starts the TCP
|
|
server. Always call `stop()` during shutdown; a stopped core cannot be started
|
|
again.
|
|
|
|
## Configuration
|
|
|
|
`CoreConfig` is a Pydantic configuration model in `dynalab_core.config`.
|
|
|
|
| Field | Type | Default | Meaning |
|
|
| --- | --- | --- | --- |
|
|
| `host` | `str` | `"127.0.0.1"` | TCP bind address. |
|
|
| `port` | `int` | `58763` | TCP bind port; validated from 0 through 65535. |
|
|
| `thread_debug` | `bool` | `False` | Enables periodic per-thread CPU debug logging. |
|
|
| `playback_mode` | `"reprocess" \| "read"` | `"reprocess"` | Whether playback recalculates bound derived signals or reads their recorded values. |
|
|
|
|
`CoreConfig.bind_str() -> str` returns `"host:port"`.
|
|
|
|
## Lifecycle
|
|
|
|
### `Core(config: CoreConfig)`
|
|
|
|
Creates a core, its input, playback, and derive workers, plus the JSON/TCP
|
|
server object. It does not bind the network port until `start()`.
|
|
|
|
### `start() -> None`
|
|
|
|
Starts the configured JSON/TCP server. It may be called only once, while Core
|
|
is initialised; otherwise it raises `CoreError(code="invalid_state")`. A start
|
|
timeout or bind failure raises `JsonServerError` with code `"start_timeout"` or
|
|
`"startup_failed"`.
|
|
|
|
### `wait(timeout: float | None = None) -> None`
|
|
|
|
Blocks until `stop()` is called or the optional timeout elapses.
|
|
|
|
### `stop() -> bool`
|
|
|
|
Stops the server, connector registry, derive registry, input worker, and
|
|
playback worker. Returns `True` only when all required components stop within
|
|
their time limits; it returns `False` if shutdown is incomplete.
|
|
|
|
## Modes and buffers
|
|
|
|
`CoreMode` is `"realtime" | "playback" | "offline"`; new instances start in
|
|
`"realtime"`. `set_mode()` clears cached live values and cannot run during an
|
|
active recording (`CoreError(code="recording_active")`).
|
|
|
|
| Mode | Source | Purpose |
|
|
| --- | --- | --- |
|
|
| `"realtime"` | Connected connectors | Live values and recording. |
|
|
| `"playback"` | Loaded DLPak | Time-based replay. |
|
|
| `"offline"` | Recorded or loaded DLPak | Prepare a processing buffer without replaying it. |
|
|
|
|
Endpoints remain connected in non-realtime modes, but only realtime connector
|
|
values are routed into Core.
|
|
|
|
### `set_mode(mode: CoreMode) -> None` / `get_mode() -> CoreMode`
|
|
|
|
Sets or returns the current mode. `CoreMode` is a type contract; callers must
|
|
pass one of the three values above. Changing mode does not automatically pause
|
|
playback, so pause it before leaving playback mode.
|
|
|
|
### `load_playback_engine(input: Path | None = None) -> None`
|
|
|
|
Available only in `"playback"` mode (`CoreError(code="invalid_mode")`). Pass a
|
|
`.dlpak` path, or omit `input` to load the current processing buffer. With no
|
|
processing buffer, omitting it raises `CoreError(code="no_playback_data")`.
|
|
Loading does not start playback; use `play()`.
|
|
|
|
### `load_processing_buffer(input: Path | None = None) -> None`
|
|
|
|
Available only in `"offline"` mode (`CoreError(code="invalid_mode")`). Pass a
|
|
`.dlpak` path to load it, or omit `input` to turn the completed recording into
|
|
a processing buffer and create its manifest. Omitting `input` before a
|
|
recording is available raises `CoreError(code="no_playback_data")`.
|
|
|
|
## Signals and live values
|
|
|
|
`SignalDescriptor` is the lightweight internal signal type in
|
|
`dynalab_core.protocols.packets.handshake`. Its fields are `id`, `name`,
|
|
`type` (`"number"` or `"binary"`), `min_value`, `max_value`, `unit`,
|
|
`timeout_ms` (default `2000`), and `origin` (`"source"` or `"derived"`, default
|
|
`"source"`). Use `JsonSignalDescriptor` only when serializing a descriptor.
|
|
|
|
### `get_signal_descriptor(signal_id: UUID) -> SignalDescriptor | None`
|
|
|
|
Returns a copy of the matching descriptor or `None`. Realtime descriptors come
|
|
from connectors and bound derive units. Playback descriptors come from the
|
|
loaded archive; derived descriptors are recalculated only when
|
|
`playback_mode="reprocess"`.
|
|
|
|
### `get_all_signal_descriptors() -> list[SignalDescriptor]`
|
|
|
|
Returns descriptors available in the current mode. In `"read"` playback mode,
|
|
recorded derived descriptors are included; in `"reprocess"` mode, bound derive
|
|
units supply them instead.
|
|
|
|
### `get_live_value(signal_id: UUID) -> float | None`
|
|
|
|
Returns the most recent non-stale value for a signal, or `None` when unknown,
|
|
not yet received, or past the signal's `timeout_ms`.
|
|
|
|
### `get_all_live_values() -> dict[UUID, float]`
|
|
|
|
Returns a snapshot of current non-stale values. Signals without a live value
|
|
are omitted. Ingress timestamps use `time.monotonic_ns()`; see the protocol
|
|
guide for details.
|
|
|
|
## Recording and DLPak archives
|
|
|
|
### `start_recording() -> None` / `stop_recording() -> None`
|
|
|
|
Recording is available only in `"realtime"` mode. Starting clears the prior
|
|
in-memory buffer; stopping normalizes and sorts samples and creates an
|
|
offload buffer with a manifest. Errors use `"incorrect_mode"`,
|
|
`"recording_active"`, or `"not_recording"` as appropriate.
|
|
|
|
### `get_recording_state() -> bool`
|
|
|
|
Returns whether a recording is active.
|
|
|
|
### `write(dir: str | Path, filename: str) -> None`
|
|
|
|
Writes the current **processing** buffer as a `.dlpak` archive; the extension
|
|
is appended when absent. After recording, select `"offline"` mode and call
|
|
`load_processing_buffer()` before writing. If no processing buffer exists,
|
|
this raises `CoreError(code="no_recording")`. Archive failures raise
|
|
`DLPakError` with a stable code such as `"write_failed"`.
|
|
|
|
## Playback
|
|
|
|
### `play() -> None` / `pause() -> None`
|
|
|
|
Start or resume, and pause, a loaded playback engine. Both require
|
|
`"playback"` mode (`CoreError(code="invalid_mode")`). `play()` before loading
|
|
data raises `PlaybackError(code="no_data")`. Playback returns to timestamp zero
|
|
after it reaches the end.
|
|
|
|
### `seek(timestamp_ns: int) -> None`
|
|
|
|
Sets the playback position relative to the recording's first sample. Requires
|
|
`"playback"` mode; a negative timestamp raises
|
|
`PlaybackError(code="invalid_timestamp")`.
|
|
|
|
### `get_playback_state() -> bool`
|
|
|
|
Returns `True` while the playback engine is emitting data.
|
|
|
|
## Derived signals
|
|
|
|
### `bind_derive_unit(dlunit: DLUnit | None = None, file: Path | None = None) -> UUID`
|
|
|
|
Binds an in-memory `DLUnit` or loads one from a `.dlunit` path, then returns
|
|
its ID. Provide exactly one source. Missing or competing sources raise
|
|
`CoreError(code="bind_derive_failed")`; invalid source or function shape raises
|
|
`DeriveError` (for example `"invalid_source"` or `"invalid_signature"`).
|
|
|
|
Derive source operates on internal `ValueDescriptor` and `SignalDescriptor`
|
|
dataclasses, not their `Json*` equivalents. Source is executed with `exec()`;
|
|
only load units from trusted sources.
|
|
|
|
### `unbind_derive_unit(unit_id: UUID) -> None`
|
|
|
|
Stops and removes a bound derive unit. An unknown ID has no effect.
|
|
|
|
## Exceptions
|
|
|
|
Expected library errors inherit from `DynaLabError` in `dynalab_core.errors`
|
|
and expose a stable `code` attribute. Catch a specific subsystem class for
|
|
recovery, or `DynaLabError` at an application boundary. Operating-system and
|
|
validation causes are retained through `error.__cause__` where applicable.
|
|
|
|
| Exception | Area |
|
|
| --- | --- |
|
|
| `CoreError` | Lifecycle, modes, recording, buffer preparation, derive binding. |
|
|
| `DLPakError` | DLPak archive read/write and completeness. |
|
|
| `DLUnitError` | DLUnit archive read/write and completeness. |
|
|
| `DeriveError` | Derive source, signature, arguments, registration. |
|
|
| `PlaybackError` | Playback data, state, and timestamps. |
|
|
| `ConnectorError` | Connector queues and registration. |
|
|
| `JsonServerError` | JSON/TCP server startup and timeouts. |
|