Engine

Covers Engine 2.0.0

DARTWIC Share

Exchange telemetry and commands in both directions, with recovery across engine, C++ client, and custom transport connections.

DARTWIC Share connects named nodes so they can keep exchanging state and commands across a link that may disconnect and recover. A node can do four things over one bidirectional connection:

DirectionWhat it doesFlight-computer example
Send telemetryReport locally owned state to the peer.Flight computer sends battery voltage to the ground engine.
Receive telemetryConsume the peer’s measurements and state.Flight computer receives ground ambient temperature.
Send commandsRequest a change to a channel owned by the peer.Flight computer updates a ground-engine channel.
Receive commandsApply requests to channels this node owns.Ground operator changes the flight computer’s telemetry rate.

The ground engine brings remote telemetry into its normal channel, interface, recording, and DCode workflows. An operator commands FLIGHT_COMPUTER:telemetry_rate_hz in the interface; Share delivers that command to the flight application’s handler. The application applies it and publishes its current state back. If the flight computer is behind a separate link adapter, that adapter implements the hardware link, forwards commands, and publishes the returned measurements.

Ground interface <--> DARTWIC ground engine <--> flight application / link adapter
                                commands -------> handler -------> hardware
                                telemetry <------ publisher <----- measurements

Automatic recovery is central to Share: built-in TEMPEST connections detect loss, reconnect, and repeat the Hello exchange. A remembered engine connection is restored by the engine; the managed C++ client’s transport reconnects while the application stays running. With a custom link, your transport supplies failure detection and reconnect, and the protocol handles readiness when the link returns. Recovery resumes communication; it does not replay commands or guarantee delivery of telemetry sent during an outage.

Choose the connection you need:

SetupEngine sideOther side
1. Engine to engineBuilt-in TEMPEST Share transportAnother engine’s TEMPEST server
2. External C++ node over TEMPESTExisting TEMPEST server; no plugin requiredDARTWICShareClient
3. Custom transportEngine plugin registers ShareTransportDARTWICShareProtocol with a matching transport

Each connection is bidirectional. Use distinct node names and avoid : and |, which delimit channel references. Open Cluster Map to inspect the resulting system.

1. Engine to engine over TEMPEST

Start both engines with distinct node names, then use the interface:

  1. Connect the interface to the first engine and open Cluster Map.
  2. Open Connect Remote Node and choose TEMPEST under Share Transport.
  3. Enter the second engine’s TEMPEST IP, TEMPEST Port (its base port), and Password, then choose Connect.
  4. Confirm the peer appears connected. Find a channel in Channel Search using SECONDARY_NODE:channel_name, with the actual remote engine name.

One connection carries traffic in both directions. Use the base port, not the HTTP port; Share uses base port and base port + 1. Two engines on the same machine need non-overlapping port ranges, such as 7000�7002 and 7200�7202.

The interface connection is remembered by default in the active project’s edge_node_connections.json. Remembered peers can be offline; the engine retries saved connections. Disconnect and forget controls manage the target. To exercise both directions, change a channel on either engine and view it from the other; command a remote channel using its owning-node prefix. Commands remain subject to authority at the owning engine.

2. External C++ node over TEMPEST

The external application connects to the engine’s existing server using DARTWICShareClient. No engine transport plugin or separate engine-side connect operation is needed.

Follow the C++ Share Client guide to build a flight-computer example that demonstrates all four directions:

Its query handler exposes current state for discovery and resynchronization. Its publication loop continues through a temporary outage, allowing the built-in transport to reconnect and resume telemetry. Your application still owns its measurements, command validation, and hardware behavior.

3. Custom transport and external application

Use this path for serial, radio, CAN, shared memory, or another connection the built-in client does not provide. Both ends implement the same ShareTransport interface.

Engine channels + ARGUS
        |
engine Share runtime
        |
plugin ShareTransport  <--- your frame delivery --->  application ShareTransport
                                                              |
                                                    DARTWICShareProtocol
                                                              |
                                                    your application state

The protocol SDK provides typed messages, Hello/readiness, session identity, and application APIs. Your transport owns delivery and request correlation. The engine supplies its own channel and ARGUS handlers; the external application supplies handlers for the state it owns.

Register the engine transport

Start with the example engine plugin. Its engine/include/example_share_transport.h and engine/src/example_share_transport.cpp implement a complete example transport; engine/src/example_device_plugin.cpp registers it during plugin initialization:

dartwic->registerShareTransport({
    .id = "example_flight_link",
    .name = "Example Flight Link",
    .default_config = {
        {"node_name", "FLIGHT_COMPUTER"},
        {"receive_endpoint", "tcp://127.0.0.1:17600"},
        {"send_endpoint", "tcp://127.0.0.1:17601"},
    },
    .create = [](const nlohmann::json& config) {
        return std::make_shared<Example::ExampleShareTransport>(config);
    },
});

The callback receives the connection configuration and returns a fresh transport instance. Registration qualifies the local ID with the plugin ID. For example_device_plugin, the connect request therefore uses example_device_plugin.example_flight_link.

Build and install the plugin, then restart the engine. Open Connect Remote Node and confirm Example Flight Link appears in the Share Transport list. Use your own plugin and transport names if you renamed the starter.

Prepare a matching external transport

For a concrete first connection, copy those two example transport files into a separate application directory. Keep the filenames and Example::ExampleShareTransport class name. Make these changes in the application’s copy:

  1. In the header, replace #include <sdk_api.h> with #include <nlohmann/json.hpp> and change the base class from DARTWIC::API::ShareTransport to DARTWIC::Share::ShareTransport. This lets the application use the standalone Protocol SDK.
  2. In start(), change both socket calls from bind(...) to connect(...).
  3. Reverse the endpoint configuration as shown below: the application’s PUSH socket connects to the engine’s PULL endpoint, and its PULL socket connects to the engine’s PUSH endpoint.
Engine example bindsExternal application connects
PULL receive_endpoint: tcp://127.0.0.1:17600PUSH send_endpoint: tcp://127.0.0.1:17600
PUSH send_endpoint: tcp://127.0.0.1:17601PULL receive_endpoint: tcp://127.0.0.1:17601

Supply both endpoints. The example deliberately uses a simulated peer when either endpoint is absent. This socket example is intended for a local integration test; a production transport must define its own link-failure detection, reconnect behavior, queue limits, and authentication as needed by the deployment.

Run the external application

Use the flight-computer application with the same incoming handlers and outgoing telemetry/commands. Replace its DARTWICShareClient construction with this protocol construction, and include example_share_transport.h instead of the Share Client header:

auto transport = std::make_shared<Example::ExampleShareTransport>(nlohmann::json{
    {"receive_endpoint", "tcp://127.0.0.1:17601"},
    {"send_endpoint", "tcp://127.0.0.1:17600"},
});
DARTWICShareProtocol share({.node_name = "FLIGHT_COMPUTER"}, transport);

Remove the TEMPEST password lookup from this custom socket example. Keep the four channel workflows, query handler, and setHandlers call. Give waitUntilReady enough time to connect from the interface, for example 30 seconds. The application still owns FLIGHT_COMPUTER:battery_voltage and FLIGHT_COMPUTER:telemetry_rate_hz; only frame delivery changes.

Use installed Protocol SDK and cppzmq packages with this CMakeLists.txt:

cmake_minimum_required(VERSION 3.20)
project(custom_share_node LANGUAGES CXX)
find_package(DARTWICShareProtocol CONFIG REQUIRED)
find_package(cppzmq CONFIG REQUIRED)
add_executable(custom_share_node main.cpp example_share_transport.cpp)
target_compile_features(custom_share_node PRIVATE cxx_std_20)
target_link_libraries(custom_share_node PRIVATE DARTWIC::ShareProtocol cppzmq)

Configure CMake with your dependency prefixes/toolchain, build, and start the application. While it waits for Hello, open Connect Remote Node in the ground interface:

FieldValue for this example
Share TransportExample Flight Link
Node NameFLIGHT_COMPUTER
Receive Endpointtcp://127.0.0.1:17600
Send Endpointtcp://127.0.0.1:17601

Choose Connect. Custom input fields come from the transport’s registered default_config; use your plugin’s labels if you changed them. The connection is remembered by default. The interface should show FLIGHT_COMPUTER, and Channel Search should find the queryable remote channels. Exercise the same four workflows from the C++ Share guide: publish flight state, receive ground state, command the ground node, and change the flight telemetry rate from the interface.

Replace the socket example with your transport

Keep the facade and engine registration. Replace socket delivery with your hardware or application link, preserving these contracts:

Transport method or callbackRequired behavior
start(callbacks)Retain callbacks, start workers, report state through on_state, and allow control frames to flow. A physical Connected state starts the Hello exchange.
request(request, timeout)Assign an ID, send the request, correlate its response, and return or throw within the timeout. Unblock pending requests on stop or disconnect.
publish(telemetry) / control(control)Deliver one-way frames independently of a pending request; the protocol can call these concurrently with request.
on_requestHandle on a worker separate from receive/response processing. Preserve arrival order for non-response frames so Hello and updates cannot overtake one another.
stop()Stop workers and release callbacks before returning; no receive callback may outlive its owner.

For a JSON wire format, use the JSON Codec. Send complete encoded frames with your own message framing. On receipt, use delivery(frame) to dispatch requests, responses, telemetry, and controls; use requestId(frame) for correlation. Reply with encodeResponse(response, original_request, original_id). Guard decoding against malformed fields, and handle unsupported frames explicitly. The codec does not provide sockets, length prefixes, retransmission, or a request worker.

Remote values, commands, and timing

ActionWhat completion means
waitUntilReady(timeout)A valid Share Hello completed; throws on timeout or disconnection. transportConnected() alone is insufficient.
Channel upsert/remove/bulk commands and channel/ARGUS queriesSynchronous request/response calls. An acknowledgement confirms handler completion, not physical hardware feedback.
ARGUS respond/updateStatus/releaseHold/removeSynchronous action request/response calls.
Channel telemetry and ARGUS message/warning/error/abort/hold/prompt publicationOne-way publication. Returning does not confirm arrival or remote execution. Disconnected telemetry is dropped.
User state and telemetry callbacksRun serially on a protocol-owned thread. They can issue synchronous Share requests; a slow callback delays later callbacks.

Call stop() outside the user callback thread when you need it to drain already-dispatched callbacks before returning. Treat a request timeout as an unknown outcome: inspect state before repeating a command that must not execute twice.

A channel’s owning node determines where commands go. Use SECONDARY_NODE:tank_pressure as a remote channel key, and |SECONDARY_NODE:tank_pressure| in DCode or a graph reference. Remote commands still obey command authority.

Local reads see the most recently received remote state. An inline calculation runs when an update arrives; Share does not synchronize two engines’ execution clocks. See Execution and Timing for engine scheduling boundaries. RAPID sample timestamps and ARGUS event creation timestamps use Unix nanoseconds.

Channel filters use keys, not field paths. With rapid.share_all: false, list allowed keys in rapid.channels. Selected remote channels can be relayed; preserve owner/session/revision lineage and route metadata. ARGUS sharing covers direct-node event state and actions.

Diagnose a connection

Share exchanges snapshots and pushed updates. The legacy poll_interval_ms connection setting is not a control-loop period or a guaranteed telemetry interval. Per-peer connected, uplink, and downlink diagnostics update once per second. Mbps counts encoded Share payload, excluding transport framing; zero traffic can simply mean a quiet source.

SymptomCheck
Unknown custom transportPlugin loaded successfully; connect ID uses the plugin-qualified registration ID.
Physical connection without readinessBoth sides send/receive Hello, use compatible protocol versions, and process control frames independently of pending requests.
Custom example cannot exchange framesBoth endpoints supplied; engine binds, application connects, and PUSH/PULL endpoint roles are reversed.
Ready peer but no channelOwner name, sharing filters, initial query handler, and whether the source actually published an update.
Commands time out while telemetry worksRequest IDs and response correlation; request handling must not block the receive worker.
Channel stops changing after reconnectResume publication or supply a query handler for current state; unsent telemetry is not replayed.

Use dartwic-share/list, transports, disconnect, and reconnect-remembered to inspect and manage peers.

The complete API is split into Share Client, Protocol, Transport, and engine transport registration.