SGKEMBEDDED PERFORMANCEEpicTunerSDK · PrereleaseContact ↗
EpicTuner SDK/ Read services (SDK 1.0)

Download the complete Markdown manual ↓
One file with guides, API headers and examples for developers and coding agents.

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 at offset, up to count records. total reports the catalog size; each item provides ID, display label, units and derived flag.
  • SNAPSHOT: channels selects values. Items contain finite value, source timestamp (Unix seconds for live data), sequence, units and flags. Invalid numeric values use a zero payload without ET_READ_VALID; never interpret that payload as a valid measurement. Disconnection or no fresh source for 500 ms sets ET_READ_STALE. Freshness uses a host monotonic clock.
  • SUBSCRIBE: channels and flags 0 (analysis batches) or ET_READ_LATEST (coalesced display). Completion returns a session/project-bound subscription handle. UNSUBSCRIBE consumes 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: id is <plugin-id>:<local-name>, text is units, channels are 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. PUBLISH supplies ID, value, timestamp and ET_READ_VALID when 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_CHANNELS pages a log's columns using log ID in id; column IDs are opaque strings to echo in requests.
  • LOG_BATCH: log ID, column IDs in channels, row offset and count. Returns row-major values with log-relative timestamps, row index in sequence, validity and units. total is 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: id is a simple key, schema is the expected nonzero schema; flags 0 select per-user storage or ET_READ_PROJECT_SETTING select per-project storage. A mismatch returns ET_ERROR_CONFLICT with the old schema and text so the plugin can migrate it. Missing keys return ET_ERROR_NOT_FOUND.
  • SETTING_SET: text is the replacement, schema its new schema, offset is 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.