Covers Engine 2.0.0
Channels
Name and configure live signals, recordings, freshness, and startup values.
A RAPID channel is a named numeric signal with metadata. It can represent a measurement, command target, task state, diagnostic, or calculated value.
For the relationship between fixed storage, task timing, health diagnostics, and TaskGroup ordering, see Deterministic Automation. Fixed channels exist to remove recurring allocation and storage-growth work from a declared repeated path; they do not turn a value into a timing or safety guarantee.
Keys and references
tank_pressure # local key
SECONDARY_NODE:tank_pressure # remote key
|tank_pressure| # value reference
|tank_pressure|:units # metadata reference
|SECONDARY_NODE:tank_pressure| # remote value referenceUse flat signal names such as tank_pressure. Keep metadata outside the closing pipe; the earlier dotted-field notation is not the field-access syntax. API requests that accept channel_name take a key without pipes and a separate field.
Fields you will use
| Field | Meaning |
|---|---|
value | Current numeric value. |
timestamp | Latest value timestamp, Unix-epoch nanoseconds. |
units | Display units, such as bar or degC. |
stale_timeout | Freshness threshold in seconds. |
record_mode | on_value_change, every_value, or never. |
data_frame | Group name attached to subsequent recorded samples. |
control_policy | free, automatic, automatic_override, manual_override, or observe_only. |
control_owner, active_controller | Automatic owner and currently permitted controller. |
commanded_by | Trusted origin of the latest accepted value write. |
value_options | Numeric values with operator-facing labels. |
startup_value | Optional next-start override, with enabled and numeric value. |
linked_calculation_scripts | Calculation links associated with the channel. |
A current value does not establish freshness or recording. Inspect timestamps and history coverage separately.
Read and write
In DCode:
local pressure = |tank_pressure|
|demo_pressure| = pressure
|demo_pressure|:units = "bar"Through a TEMPEST client:
response = client.operation("rapid/upsert-channel-field", {
"channel_name": "demo_pressure",
"field": "value",
"value": 2.5,
})
response.raise_for_error()The engine checks authority on value writes. Metadata editing and value commanding are distinct operations. SDK code uses typed ChannelField and ChannelValue; see Engine Plugin.
Calculations and recording
Use channel calculations for scaling, offsets, and derived signals. These are explicit scripts, not implicit scale or mapping fields on a channel.
on_value_change records changed values; every_value records each accepted value write; never leaves the channel live without recording it. Samples retain timestamp, data frame, value, and provenance. Recording is asynchronous and does not delay inline dependency reactions until a disk flush.
Startup values
Set startup_value when a channel needs a configured value at engine startup:
{"enabled": true, "value": 0}Changing this field does not command the channel immediately. On startup, dynamic channels apply the override while restoring their snapshot; fixed channels apply it after declaration and before deferred runtimes start. The driver still determines whether and when that value reaches hardware.
Fixed and dynamic channels
Both storage classes use the same channel names, public fields, recording options, and authority rules. A fixed value still changes normally; fixed describes its allocated storage and binding, not a constant value.
| Storage | Use it for | Runtime behavior |
|---|---|---|
| Dynamic | Ad hoc signals and channels whose set of names changes at runtime. | Ordinary creation defaults to dynamic. Native readers use name-based queries; these inputs do not participate in the fixed input snapshot. |
| Fixed | A declared repeating acquisition, calculation, or control path whose recurring memory and storage work must be prepared. | RAPID reserves slots, resolves handles during configuration, and supports coherent task input snapshots and staged output commits. |
Why use fixed channels with native DCode?
For the strongest supported repeated-release path, use fixed channels with Native C++ DCode, or a plugin that resolves fixed handles and reuses its own buffers during configuration. The first reason for fixed storage is memory behavior: fixed numeric slots, task snapshots, output staging, and normal publication resources are prepared before the task is released so the ordinary cycle does not allocate or grow channel storage. Resolved handles also avoid repeated fixed-name lookup, but that is a secondary benefit. Native DCode removes Lua interpretation from supported callback logic. Together these choices reduce recurring allocation and variable framework work so a control task has a declared, repeatable execution cost. See the qualified path and its limits.
Fixed task snapshots provide a second benefit: coherent inputs. The engine captures declared fixed inputs when a task transaction begins; resolved snapshot reads use a slot-to-entry index. Fixed writes are staged and committed as a generation, with dependent calculations and event conditions following the existing inline post-commit path. Keep intermediate arithmetic in local or state: a staged pipe write does not turn the pipe into a local variable.
Prepared numeric fixed writes use bounded asynchronous notification handoff, so a slow consumer does not keep the task’s reusable publication buffers occupied. Its logical capacity protects delivery backlog; do not mistake it for proof that every queue implementation is allocation-free. Inline calculations still run immediately: a calculation may be native C++ or Lua, and its mutex wait plus execution time are part of the triggering task’s completion time. The registered-tasks operation reports the calculation’s execution mode, execution boundary, active state, invocation count, and recent duration so this dependency is visible rather than implied. If the notification queue fills, it drops old asynchronous notifications and counts the loss; committed values and command authority remain intact. See notification capacity.
The qualified prepared core path has a narrower allocation result than the complete callback path. In the tested Release numeric fixed-channel workload, the prepared core transaction observed zero host C++ allocations/frees per release. That result does not cover Lua, direct malloc, separately linked native/plugin DLL allocators, arbitrary callback work, driver I/O, or every inline dependency. Ordinary tasks can still grow beyond their reservations, and deeply nested or unsupported publications can allocate fallback storage. Interceptors, authority operations, inline reactions, and synchronization also remain part of the execution cost. Prepared releases reject promptly when fixed configuration is in progress; configuration itself waits for active transactions at the lifecycle boundary. Compiling a callback does not bound its loops, sleeps, driver calls, or inline calculation chain.
These are separate questions: whether execution does less variable work, whether inputs are coherent, and whether a deployment meets its deadline. Fixed storage improves the first two; qualify the third on the target machine under load. Independent tasks do not acquire a prescribed order merely by using fixed channels. Use a task group for explicit ordering. Separate device samples are not synchronized by a RAPID snapshot. Lua tasks also use fixed snapshots.
Fixed slots keep their storage identity while bindings change: removal leaves a reusable empty slot and generation checks prevent stale handles from silently reaching a later channel. Prepare mappings at configuration time. A prepared task release encountering concurrent fixed configuration reports configuration_contention instead of waiting indefinitely; decide whether that rejected release is a reconfiguration gap, an alarm, or a device fault in the application.
See Building a hardware driver for the configure, bind, and execute workflow.
Declare the producer’s channels
For Native C++ DCode, activation automatically declares direct |output| = expression targets, calculation targets, and state-machine current/target channels as fixed. It includes outputs from other configured native files when resolving producer/consumer bindings.
Reading |input| does not declare a fixed producer. If no channel exists, native activation creates a dynamic channel with value zero and reports a warning. Configure the acquiring plugin to publish fixed storage, or supply the intended native producer. Do not add a dummy writer merely to suppress a dynamic-binding warning.
For an engine plugin, declare channels during task configuration (on_configure), using its SDK pointer:
api->createFixedChannel("sensor_pressure", 0.0);
api->createFixedChannel("sensor_temperature", 0.0);This creates or promotes storage and writes the supplied initial value. Call it during configuration, not every acquisition iteration. Subsequent ordinary upsertChannelField calls preserve fixed storage. For a driver that publishes or reads a known batch, the SDK also provides resolveFixedChannels, queryFixedChannelValues, and upsertFixedChannelValues. Resolve once during configuration, preallocate value buffers, and reuse them in callbacks. TaskRuntime::setFixedInputChannels declares the inputs a plugin task needs in its snapshot; native DCode supplies its own list automatically.
Configure and verify the layout
DARTWIC Builder applies declared configuration changes after active task transactions drain, even when the layout is protected. Direct fixed-layout changes outside that boundary remain restricted. Normal DCode activation and task configuration do not require a manual unlock.
Inspect the channel’s storage value (fixed or dynamic) and the editor’s native binding chip. Builder lists each binding and reports changes that rebind loaded consumers. The engine’s rapid_fixed_channel_capacity setting defaults to 4096 slots and is read when RAPID is constructed. If capacity is exhausted, increase it and restart; the pool does not grow automatically at runtime.