Skip to content
AI-driventedge-dot is developed largely by AI coding agents, and it is alpha software. Check what it does before you rely on it, and report what you find.

<protocol> Connector Spec — Template

Copy this file to connectors/<protocol>-connector-spec.md and fill in every section. A spec is “done” when an implementer (human or AI agent) can build the connector from it plus the SDK spec and pass the conformance suite. Keep prose minimal and tables precise. Anything you leave vague will be implemented inconsistently.

FieldValue
StatusDraft / Implementable
Protocol id<protocol>
Crateconnector-<protocol> (feature <protocol>)
Builds on<rust crate(s) used>, Connector SDK
ImplementsOT Connector Contract v0.4.0

State what this connector reads/writes and explicitly that scaling/naming/alarms/shaping are flow responsibilities. List the protocol operations supported and the underlying crate calls.

Object kindReadWriteLibrary call
<kind>yes/noyes/no<call>

Provide the exact JSON the connector publishes (contract §7). Set subscribe correctly: is this protocol polled, push, or both?

{
"protocol": "<protocol>",
"version": "0.1.0",
"modes": ["raw", "typed"],
"datatypes": ["..."],
"point_kinds": ["..."],
"command_verbs": ["write"],
"features": ["polling"],
"subscribe": false
}

Define the shape of the three opaque contract slots and provide a JSON Schema (<protocol>.schema.json).

For each, give a table of fields (name, required, notes) and a TOML example.

Specify how raw bytes map to the contract datatypes via the SDK decode_primitive helper:

  • What is the smallest addressable unit and its native endianness?
  • How do endianness and word_order apply (or not)?
  • Datatype → read-size table.
  • Any allowed refinement (e.g. bit-fields), declared as a feature.

Describe read_points (polled) and/or subscribe (push): batching strategy, error → bad sample behaviour, what goes in addr.

The connector returns every reading and never filters on its own; the SDK runtime applies the point’s report table (contract §5.3). See Reducing data volume.

State whether this protocol’s points can be read on demand, which decides whether a heartbeat (max_interval) applies: a polled point publishes its next poll, a pushed point is read through read_points / read_point, and a point that cannot be read (a received frame, a notification) gets none. Such a point must return Unsupported (Rust) or TDOT_READ_NO_DATA (C), not a bad sample. State whether the packaged config sets [connector] report = { max_interval = "30m" }, or shows it only as a comment.

The connector returns the decoded value; the SDK runtime applies the point’s map (contract §4.3) and maps a written value back before write, so the module needs no code for it. Name the values of this protocol that are commonly mapped (state enumerations, numbers carried as text). See Mapping values.

Describe execute for verb write (and any extra verbs you declare): typed vs raw encoding, access checks, read-modify-write if needed.

Describe link-status semantics for this protocol (what counts as connected/degraded).

Provide normative vectors: input bytes/frames → expected Sample. Cover every advertised datatype, both word orders if applicable, a bad read, and a write round-trip. These feed the conformance suite.

A numbered list an agent can follow end-to-end.


Every connector ships with a complete, self-contained e2e harness under connectors/<protocol>/. When you fill in this spec, also create these files:

connectors/<protocol>/
sim/ # simulator image
Dockerfile # builds the simulator container
<server-code> # e.g. server.py or a pre-built binary
tests/
<protocol>_e2e.robot # Robot Framework suite (see §11)
packaging/
<protocol>.toml # default installed config (no devices, correct service_name)
docker-compose.yaml # 3 services: broker, simulator, connector
Dockerfile.connector # builds the Rust connector binary from project root
connector.toml # connector config baked into the container
entrypoint.sh # waits for deps, then execs tedge-dot
requirements.txt # protocol-specific Python deps (omit if none needed)

Shared across all connectors (do not duplicate these):

connectors/_shared/
MqttClient.py # Robot keyword library — import via ../../_shared/MqttClient.py
mosquitto.conf # identical broker config for every stack
requirements.txt # base deps: robotframework, paho-mqtt

Pick the next unused host port from the table in connectors/README.md and add a row for your protocol. Port clashes make simultaneous multi-protocol runs fail silently.


The just recipes in the project root are protocol-neutral. They derive the Docker compose file path, venv location, and test directory from the protocol name you pass:

Terminal window
just sim <protocol> # start only the simulator
just sim-down <protocol> # stop it
just e2e-up <protocol> # bring the full stack up
just e2e-down <protocol> # tear it down
just test-e2e <protocol> # up → robot → down (CI entry-point)

No justfile changes are required when adding a new protocol. The only preconditions are:

  1. connectors/<protocol>/docker-compose.yaml exists and defines broker, simulator, and connector services.
  2. connectors/<protocol>/tests/ contains at least one .robot file.
  3. The broker uses a unique host port (see §10).

test-e2e installs connectors/_shared/requirements.txt first, then connectors/<protocol>/requirements.txt if present (use this for protocol-specific Python deps such as asyncua).


Use this list to track progress when implementing a new connector. Cross-reference each item with the corresponding section of this spec.

  • Rust crate — impl/rust/crates/connector-<protocol>/ created, Connector trait implemented (§5, §6, §7), feature flag added in impl/rust/Cargo.toml.
  • Spec — this template filled out and saved as doc/connectors/<protocol>-connector-spec.md.
  • Simulator — connectors/<protocol>/sim/ created with a working Dockerfile (§10).
  • Docker stack — docker-compose.yaml, Dockerfile.connector, connector.toml, entrypoint.sh created with correct paths and a unique broker port (§10, §11).
  • Tests — connectors/<protocol>/tests/<protocol>_e2e.robot covers capabilities descriptor, health, link status, at least one typed read, a bad read, and a write round-trip (§8).
  • Packaging config — packaging/config/<protocol>.toml created with protocol, service_name, and sensible defaults, and listed in .goreleaser.yaml (§10).
  • just test-e2e <protocol> passes locally.
  • Port table in connectors/README.md updated.

Capability sketches for upcoming protocols

Section titled “Capability sketches for upcoming protocols”

These are not full specs — they are starting notes to show the contract generalizes and to seed future spec PRs. Each will graduate to its own file using the template above.

AspectSketch
Librarysocketcan (Linux SocketCAN)
NaturePush (frames arrive asynchronously) → implements subscribe, subscribe: true.
DeviceA CAN interface, e.g. { interface = "can0", bitrate = 500000 }.
PointA signal within a frame: { can_id = "0x1A0", start_bit, bit_count, byte_order, value_type } (DBC-like).
TypedDecode a signal as int/uint/float from a bit range of an 8-byte frame; endianness ≈ Intel/Motorola byte order.
RawEmit the full 8-byte frame payload as hex.
Writewrite verb sends a CAN frame (for points whose access allows).
NotesA flow can apply the DBC scale/offset; the connector only extracts the bit-field. Subscription model fits the SDK subscribe path directly.
AspectSketch
Librarya Rust BACnet stack (e.g. bacnet-rs) or FFI to a C stack
NaturePolled (ReadProperty) and push (COV subscription) → subscribe: true, features: ["polling","cov"].
Device{ device_instance = 1234, address = "192.168.0.50:47808" }.
PointAn object property: { object_type = "analog-input", instance = 3, property = "present-value" }.
TypedBACnet already carries typed values (Real, Unsigned, Boolean, Enumerated); map to contract datatypes; endianness/word_order largely N/A.
RawEmit the encoded APDU value as hex for unusual types.
Writewrite verb → WriteProperty (respecting priority array could be a future verb extension).
NotesBecause BACnet is self-describing, typed decode is mostly a type map; the flow still does engineering-unit naming and thresholds.

Implemented: see opcua-connector-spec.md for the normative spec (security, certificates, tedge-dot pki). The sketch below is the original outline.

AspectSketch
Libraryopcua crate (client)
NaturePolled (Read) and push (MonitoredItems/Subscriptions) → subscribe: true.
DeviceAn OPC-UA server endpoint + security: { endpoint = "opc.tcp://host:4840", security_policy, auth }.
PointA node: { node_id = "ns=2;s=Boiler.Temp" }.
TypedOPC-UA Variant is already typed (Double, Int32, Boolean, String…); map to contract datatypes; carry the source timestamp into ts; map StatusCode → quality.
RawEmit the encoded variant bytes as hex when the type is exotic.
Writewrite verb → Write service call.
NotesOPC-UA’s StatusCode maps cleanly to the contract’s good/bad/stale quality. Subscriptions are the natural mode; polling is a fallback. The flow handles renaming and any unit conversion.

These four sketches share one config model, one sample envelope, one command protocol, and one conformance harness — which is the entire point of the contract.