Read services (SDK 1.0)
epictuner/read.h is a Qt-free C11 API. epictuner/sdk/read.hpp supplies C++20
wrappers and exception-contained callbacks. ABI 1.1 and all prior prefixes remain
unchanged. Query ET_SERVICE_PROJECT, CHANNELS, DATALOG or SETTINGS with
version ET_SERVICE_VERSION_1 into an et_read_service. Each table permits only
its own operations. The host independently checks the manifest capability again.
| Feature | Manifest capability | Operations |
|---|---|---|
et.project.v1 |
project.read |
PROJECT |
et.channels.v1 |
channels.read |
CHANNELS, SNAPSHOT, SUBSCRIBE, UNSUBSCRIBE, DERIVE, PUBLISH |
et.datalog.v1 |
datalog.read |
LOGS, LOG_CHANNELS, LOG_BATCH, LOG_SELECT |
et.settings.v1 |
settings |
SETTING_GET, SETTING_SET |
Ownership and asynchronous completion
Zero-initialize requests, set struct_size, operation and count (1..32).
Reads::make supplies these defaults. All calls require the worker dispatcher.
Inputs are copied before return. A successful request returns a nonzero operation
ID; it has exactly one terminal callback, including cancellation during shutdown.
An immediate error queues no callback. Keep callback context alive through that
callback. Reply arrays and strings are borrowed only during the callback; copy
anything needed by a background task. The C++ ReadCallback catches exceptions
inside the plugin's compiler/runtime and returns ET_ERROR_CALLBACK.
cancel races completion: an operation already completed keeps its actual result;
queued work completes with ET_ERROR_CANCELLED. There is no automatic retry.
Cancel cannot roll back a setting already persisted or a selected log range.
Read requests time out at 3000 ms; the worker fails instead of misattributing late
replies. Project replacement rejects queued old-generation operations. Stop/failure
revokes subscriptions and derived channels, discards queued work and cancels local
callbacks before destroying the plugin instance. Worker restart starts a new session.
listen installs one read-event listener per worker, shared by its read tables.
Subscribe from that listener or an asynchronous project reply, never by guessing a
generation. It receives the current project/connection on startup and coalesced
changes, including close/reopen at the same path. The project reply contains the
display name in text, total 0/1 for closed/open, project_generation, and
ET_READ_CONNECTED in flags. Private paths and tune bytes are not exposed.
Operation fields
All project-scoped requests must echo the current project_generation. User
settings and the project query are global. Unused request fields must be zero or
empty (the host currently tolerates unused valid fields for forward compatibility).
Unsupported flags and non-finite numbers are rejected. The id/text UTF-8 spans
are limited to 4096 bytes; channel IDs are at most 256 bytes, 16 distinct selections.
CHANNELS: metadata page atoffset, up tocountrecords.totalreports the catalog size; each item provides ID, display label, units and derived flag.SNAPSHOT:channelsselects values. Items contain finitevalue, sourcetimestamp(Unix seconds for live data),sequence, units and flags. Invalid numeric values use a zero payload withoutET_READ_VALID; never interpret that payload as a valid measurement. Disconnection or no fresh source for 500 ms setsET_READ_STALE. Freshness uses a host monotonic clock.SUBSCRIBE:channelsand flags 0 (analysis batches) orET_READ_LATEST(coalesced display). Completion returns a session/project-bound subscription handle.UNSUBSCRIBEconsumes that handle. Close panel-owned subscriptions explicitly; the example does so on its close event. Background subscriptions may remain active until explicit unsubscribe, project close or worker stop.DERIVE:idis<plugin-id>:<local-name>,textis units,channelsare immutable dependencies. At most 16 derived channels per worker. Derived metadata is visible to read-service clients; its label lists dependency IDs. Registration rejects unknown dependencies, duplicates and dependency cycles, including cycles across a publisher restart.PUBLISHsupplies ID, value, timestamp andET_READ_VALIDwhen valid. Publication must match the latest source sample; stale calculations are rejected. The host also checks dependency validity. Values become invalid when the source advances or a dependency leaves. Plugins cannot overwrite ECU channels or another publisher's values. This does not inject channels into the ECU definition, recorder, or tune expression engine.LOGS: pages host-approved already-open log IDs; item label is the basename and value is the current row count. No plugin-provided path is opened. IDs are revoked when the log closes or is replaced.LOG_CHANNELSpages a log's columns using log ID inid; column IDs are opaque strings to echo in requests.LOG_BATCH: log ID, column IDs inchannels, rowoffsetandcount. Returns row-major values with log-relative timestamps, row index insequence, validity and units.totalis current rows. The returned page can be shorter than requested to respect the 48 KB payload budget; advance by actual returned rows. A log can grow during recording; request a fixed ending row for reproducible analysis.LOG_SELECT: log ID plus row offset/count selects that already-open log and its host view range. Busy or unavailable logs are rejected. Export and recording control are outside these read tables; the user uses EpicTuner's existing controls.SETTING_GET:idis a simple key,schemais the expected nonzero schema; flags 0 select per-user storage orET_READ_PROJECT_SETTINGselect per-project storage. A mismatch returnsET_ERROR_CONFLICTwith the old schema and text so the plugin can migrate it. Missing keys returnET_ERROR_NOT_FOUND.SETTING_SET:textis the replacement,schemaits new schema,offsetis the expected previous schema (0 means create only). This explicit comparison prevents accidental schema downgrade/overwrite. Storage is namespaced by authenticated plugin ID and hashed project identity, at most 64 keys per scope. It persists across worker/app restart. Storage is plain text: do not store passwords, tokens, licenses or other sensitive material. There is no secrets storage claim. Settings do not grant ECU write access.
Bounded transport and loss reporting
There are 32 outstanding read operations and 100 requests/second per worker, within the shared 200-message transport ceiling. The public limit of 64 subscriptions applies per worker. Source ingestion has a 128-snapshot mailbox; the broker retains 128 snapshots. Neither source path queues a GUI event per sample or calls plugin code. Existing comms/recording remain on their own thread.
At most one read event per worker is unacknowledged. The proxy acknowledges after
the listener returns; a slow listener therefore cannot fill the socket with data.
The broker distributes at most 50 events/second per worker, round-robin across
subscriptions, with at most 32 rows/16 channels/512 items and 48 KB per event.
gaps counts skipped source snapshots since the preceding event, including
mailbox overflow, analysis overflow and intentional latest-only coalescing.
Sample sequence numbers preserve the underlying source identity. A fresh
subscription starts after the current sample. Silence crossing the stale threshold
produces one stale notification with the last sample sequence; connection events
also invalidate displayed values. Old subscriptions never resume after project
replacement. There is no lossless-recording promise for this bounded stream.
Examples and validation
live-telemetry uses 16-channel catalog pages, a picker, numeric readout and graph,
timestamps/units/gaps, connection events and a compiled RPM/2 derived channel.
log-analyzer selects open logs/channel/row range, processes bounded batches in
cancellable background jobs, and compares two logs using result table/chart controls.
The example picker shows the first 32 open logs and first 32 channels; the API
supports paging beyond those example limits. M4 supplies the dedicated calibration
and datalog prefab library; these examples use M2's general ET controls.
Build either example with find_package(EpicTunerSDK 0.5 CONFIG REQUIRED) from
the SDK export. The public headers and example sources do not link Qt or private
application code. Each directory contains its CMake project and development
manifest template. live-telemetry/synthetic.ini is a safe mock definition.
Host verification: plugin_reads covers validation, handles, generations, derived
validity, settings schemas, cancellation and flood admission. plugin_reads_app
uses the actual app, mock ECU and two generated MLG files, validates exact results,
disconnect/reconnect, panel release, cancellation and stale log/project rejection.
plugin_reads_load compares a 30-second baseline against a 30-second five-worker
fault run while 100 Hz mock polling and recording continue. Evidence records poll
interval percentiles, missed intervals, recorded rows and explicit stream gaps.
An interval of at least 20.5 ms at 100 Hz counts as one missed poll interval
(two periods, with 0.5 ms allowance for the millisecond timestamp resolution).
These are software gates, not physical ECU/Pi or endurance certification.