<protocol> Connector Spec — Template
Copy this file to
connectors/<protocol>-connector-spec.mdand 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.
| Field | Value |
|---|---|
| Status | Draft / Implementable |
| Protocol id | <protocol> |
| Crate | connector-<protocol> (feature <protocol>) |
| Builds on | <rust crate(s) used>, Connector SDK |
| Implements | OT Connector Contract v0.4.0 |
1. Scope
Section titled “1. Scope”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 kind | Read | Write | Library call |
|---|---|---|---|
<kind> | yes/no | yes/no | <call> |
2. Capability descriptor
Section titled “2. Capability descriptor”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}3. Protocol-specific configuration
Section titled “3. Protocol-specific configuration”Define the shape of the three opaque contract slots and provide a JSON Schema
(<protocol>.schema.json).
3.1 connection
Section titled “3.1 connection”3.2 device.protocol_address
Section titled “3.2 device.protocol_address”3.3 point.address
Section titled “3.3 point.address”For each, give a table of fields (name, required, notes) and a TOML example.
4. Decoding rules (typed mode)
Section titled “4. Decoding rules (typed mode)”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
endiannessandword_orderapply (or not)? - Datatype → read-size table.
- Any allowed refinement (e.g. bit-fields), declared as a
feature.
5. Read flow
Section titled “5. Read flow”Describe read_points (polled) and/or subscribe (push): batching strategy, error → bad
sample behaviour, what goes in addr.
5.1 Reporting policy (report)
Section titled “5.1 Reporting policy (report)”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.
5.2 Value mapping (map)
Section titled “5.2 Value mapping (map)”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.
6. Write flow
Section titled “6. Write flow”Describe execute for verb write (and any extra verbs you declare): typed vs raw encoding,
access checks, read-modify-write if needed.
7. Status
Section titled “7. Status”Describe link-status semantics for this protocol (what counts as connected/degraded).
8. Acceptance test vectors
Section titled “8. Acceptance test vectors”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.
9. Implementation checklist
Section titled “9. Implementation checklist”A numbered list an agent can follow end-to-end.
10. Testing & simulation directory layout
Section titled “10. Testing & simulation directory layout”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-mqttBroker port assignment
Section titled “Broker port assignment”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.
11. Justfile integration
Section titled “11. Justfile integration”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:
just sim <protocol> # start only the simulatorjust sim-down <protocol> # stop itjust e2e-up <protocol> # bring the full stack upjust e2e-down <protocol> # tear it downjust test-e2e <protocol> # up → robot → down (CI entry-point)No justfile changes are required when adding a new protocol. The only preconditions are:
connectors/<protocol>/docker-compose.yamlexists and definesbroker,simulator, andconnectorservices.connectors/<protocol>/tests/contains at least one.robotfile.- 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).
12. New protocol checklist
Section titled “12. New protocol checklist”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,Connectortrait implemented (§5, §6, §7), feature flag added inimpl/rust/Cargo.toml. - Spec — this template filled out and saved as
doc/connectors/<protocol>-connector-spec.md. - Simulator —
connectors/<protocol>/sim/created with a workingDockerfile(§10). - Docker stack —
docker-compose.yaml,Dockerfile.connector,connector.toml,entrypoint.shcreated with correct paths and a unique broker port (§10, §11). - Tests —
connectors/<protocol>/tests/<protocol>_e2e.robotcovers capabilities descriptor, health, link status, at least one typed read, a bad read, and a write round-trip (§8). - Packaging config —
packaging/config/<protocol>.tomlcreated withprotocol,service_name, and sensible defaults, and listed in.goreleaser.yaml(§10). -
just test-e2e <protocol>passes locally. - Port table in
connectors/README.mdupdated.
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.
CAN bus (canbus)
Section titled “CAN bus (canbus)”| Aspect | Sketch |
|---|---|
| Library | socketcan (Linux SocketCAN) |
| Nature | Push (frames arrive asynchronously) → implements subscribe, subscribe: true. |
| Device | A CAN interface, e.g. { interface = "can0", bitrate = 500000 }. |
| Point | A signal within a frame: { can_id = "0x1A0", start_bit, bit_count, byte_order, value_type } (DBC-like). |
| Typed | Decode a signal as int/uint/float from a bit range of an 8-byte frame; endianness ≈ Intel/Motorola byte order. |
| Raw | Emit the full 8-byte frame payload as hex. |
| Write | write verb sends a CAN frame (for points whose access allows). |
| Notes | A flow can apply the DBC scale/offset; the connector only extracts the bit-field. Subscription model fits the SDK subscribe path directly. |
BACnet/IP (bacnet)
Section titled “BACnet/IP (bacnet)”| Aspect | Sketch |
|---|---|
| Library | a Rust BACnet stack (e.g. bacnet-rs) or FFI to a C stack |
| Nature | Polled (ReadProperty) and push (COV subscription) → subscribe: true, features: ["polling","cov"]. |
| Device | { device_instance = 1234, address = "192.168.0.50:47808" }. |
| Point | An object property: { object_type = "analog-input", instance = 3, property = "present-value" }. |
| Typed | BACnet already carries typed values (Real, Unsigned, Boolean, Enumerated); map to contract datatypes; endianness/word_order largely N/A. |
| Raw | Emit the encoded APDU value as hex for unusual types. |
| Write | write verb → WriteProperty (respecting priority array could be a future verb extension). |
| Notes | Because BACnet is self-describing, typed decode is mostly a type map; the flow still does engineering-unit naming and thresholds. |
OPC-UA (opcua)
Section titled “OPC-UA (opcua)”Implemented: see opcua-connector-spec.md for the normative spec (security, certificates,
tedge-dot pki). The sketch below is the original outline.
| Aspect | Sketch |
|---|---|
| Library | opcua crate (client) |
| Nature | Polled (Read) and push (MonitoredItems/Subscriptions) → subscribe: true. |
| Device | An OPC-UA server endpoint + security: { endpoint = "opc.tcp://host:4840", security_policy, auth }. |
| Point | A node: { node_id = "ns=2;s=Boiler.Temp" }. |
| Typed | OPC-UA Variant is already typed (Double, Int32, Boolean, String…); map to contract datatypes; carry the source timestamp into ts; map StatusCode → quality. |
| Raw | Emit the encoded variant bytes as hex when the type is exotic. |
| Write | write verb → Write service call. |
| Notes | OPC-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.