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.

Proposal: A thin-edge.io OT Connector Framework

Status: Draft for discussion · Target: thin-edge.io community · Date: 2026-05-30

This directory contains a design proposal to evolve the thin-edge.io modbus-plugin into a general-purpose, community-extensible OT Connector framework written in Rust.

The proposal is intentionally split into small, self-contained documents so that each piece can be reviewed, ratified, and implemented independently — including by AI coding agents working from the machine-readable specifications.


  • Make the protocol driver dumb. A connector should only do one thing well: move bytes between an OT protocol (Modbus, CAN, BACnet, OPC-UA, …) and MQTT. It performs transport and optional decoding — nothing else.
  • Move transformation into thin-edge.io flows. Scaling, renaming, units, thin-edge JSON shaping, threshold alarms, aggregation, and registration are all expressed as small JavaScript flows that the community can read, test (tedge flows test), and share — no recompilation required.
  • Define one neutral contract for all OT protocols. A single OT Connector Contract (config model + MQTT envelopes + command protocol + capability model) lets every protocol connector look the same to the rest of the system.
  • Rust + a shared SDK. One binary, tedge-dot, with protocol modules behind cargo feature flags, all built on a shared tedge-dot-sdk runtime. Modbus is the reference implementation on tokio-modbus.
  • Built for a community and for AI agents. Machine-readable schemas, a connector template, a conformance test suite, a connector registry, and an RFC process make it realistic for third parties — human or AI — to add a new protocol and have it “just work.”

The current plugin (tedge_modbus) is a capable Python application, but it concentrates a large amount of responsibility inside one codebase:

ConcernWhere it lives todayProblem
Modbus transport (TCP/RTU)reader/reader.pyTightly coupled to mapping logic
Bit extraction & datatypesreader/mapper.pyHard-coded; per-protocol reinvention
Endianness (byte + word order)reader/mapper.pySubtle, hard to test in isolation
Scaling (raw*m*10^d/div)+offsetreader/mapper.pyRequires a code change to adjust
Alarms / events / change detectionreader/mapper.pyBusiness logic baked into the driver
thin-edge JSON shapingreader/mapper.pyCouples driver to cloud data model
Cumulocity operationsoperations/*.pyC8y-specific glue inside the driver

Adding a second OT protocol (CAN, BACnet, OPC-UA) means re-implementing most of this stack again. There is no shared contract, no shared test harness, and no obvious place for a contributor to plug in. The transformation logic — the part domain experts most want to tweak — is the least accessible part of the system.

thin-edge.io already ships a purpose-built answer for the transformation half of this problem: flows. Flows are sandboxed JavaScript steps that run inside a mapper, are hot-reloaded without a restart, are testable offline, and are packaged and distributed through normal software management. This proposal leans into flows for everything that is business logic and reserves Rust for everything that is protocol plumbing.


        ┌────────────────────────────────────────────────────────────────────────┐
        │                         tedge-dot                                      │
        │  (single Rust binary, protocol modules behind cargo feature flags)     │
        │                                                                        │
        │   ┌────────────┐     ┌──────────────────┐      ┌────────────────────┐  │
   OT ──┼──▶│ Transport  │────▶│ Decode (optional)│─────▶│ Publish: canonical │  │
 device │   │ (protocol) │     │ raw │ typed      │      │ "sample" envelope  │  │
        │   └────────────┘     └──────────────────┘      └─────────┬──────────┘  │
        │   ▲ write_point            capability model              │             │
        └───┼──────────────────────────────────────────────────────┼─────────────┘
            │                                                      │ MQTT
   command  │                                                      ▼
   results  │                                        ┌────────────────────────────┐
            │                                        │  thin-edge.io flows (JS)   │
            └────────────────────────────────────────│  scale · rename · units ·  │
                       cmd/write request             │  alarms · events · shape · │
                                                     │  aggregate · register      │
                                                     └─────────────┬──────────────┘
                                                                   ▼
                                                       te/device/<d>///m|e|a/...
                                                       (standard thin-edge data)

The connector publishes a small, protocol-neutral “sample” envelope for every point it reads. Flows turn those raw/typed samples into the thin-edge.io data model the cloud mappers already understand. Writes flow the other way: a cmd/write request on MQTT is validated by the SDK and handed to the protocol module’s write_point.


Read them roughly in this order:

#DocumentWhat it covers
1rfc/0001-ot-connector-architecture.mdThe core architectural decision: dumb driver + flows, single pluggable binary, rationale, alternatives, risks.
2contract/ot-connector-contract.mdNormative. The OT Connector Contract: config model, sample envelope, status/health, command protocol, capability model, topic conventions, quality & timestamp rules.
3contract/schemas/Machine-readable JSON Schemas for config, point libraries, and sample, command and status messages.
4contract/asyncapi.yamlAsyncAPI 3.0 description of every MQTT topic and message.
5sdk/connector-sdk.mdThe Rust SDK and the Connector trait every protocol module implements; what the runtime provides for free.
6connectors/modbus-connector-spec.mdAI-implementable reference spec for the Modbus connector, including decode rules and acceptance test vectors.
6aconnectors/snmp-connector-spec.mdSpec for the SNMP trap receiver (v1/v2c traps and informs): device matching by source address, trap/varbind points, the BER decoding rules and the golden vectors both implementations are held to.
6bconnectors/opcua-connector-spec.mdSpec for the OPC UA connector’s sessions and security: policies and modes, the application certificate, the PKI directory and trust rules (pinned, CA chain, CRLs), user identities, the link-status reasons and tedge-dot pki.
7connectors/_template-connector-spec.mdA blank protocol spec template, plus capability sketches for CAN, BACnet and OPC-UA.
8flows/Example flow packages that move transformation out of the driver (scaling, alarms, registration).
9conformance/conformance-suite.mdThe acceptance/conformance suite every connector must pass, and the simulator harness.
10community/community-model.mdConnector registry, contribution guide, RFC process, project template, governance.
11migration/migration-guide.mdHow today’s devices.toml / modbus.toml / C8y operations map onto the new world.
12roadmap.mdA phased delivery plan from SDK to a community of connectors.
13testing.mdThe layered testing strategy: unit, property-based (proptest), fuzzing (cargo-fuzz), integration, simulator e2e, flow and cloud tests.
14rfc/0002-cloud-fieldbus-integration.mdProposal: Cumulocity Cloud Fieldbus device types translated into define-device commands; the device stays config-file driven.
15rfc/0003-parameter-writes.mdWriting to devices from the cloud as device parameters: write-batch in the SDK, writable points published as twin fragments by one flow, Cumulocity c8y_ParameterUpdate bridged by the command flows, DTM definitions rendered from the config; why not the CLI or the parameter plugin.
16rfc/0004-point-libraries.mdPoint libraries: a device type’s point list as its own packageable file, referenced by points_from so an instance declares only its address — resolution and override rules, why the persisted config keeps the reference, and the discovery hook that follows.
16areducing-data-volume.mdUser guide to the reporting policy (report): publish on change, deadband, rate limit, heartbeat for Cumulocity availability, debounce, per-connector notes, and moving from the ot-measurement settings.
16bmapping-values.mdUser guide to value mapping (map): state codes to labels with ranges and a catch-all, numeric text to measurements, enumerated device parameters written as labels, and how it interacts with transform, report and the flows.
17rfc/0005-device-types-and-parameter-sets.mdDevice types: naming what a device is (on the device, or once in its point library) and deriving parameter set names from it, so two device types on one protocol stop colliding on a tenant-wide Cumulocity identifier; the group/set split, why the type travels in samples and link status, and the entity type that follows.
18rfc/0006-opcua-namespace-table.mdOPC UA namespace tables: numeric node ids plus the namespace array they were written against, translated to the server’s indexes on every connect, so ids survive a renumbering and one library fits every instance; what OPC UA guarantees about the namespace array, and why not URI addressing.

  1. One job per layer. The driver moves bytes; flows make meaning. Neither reaches into the other’s domain.
  2. Protocol-neutral by default. Everything above the transport layer speaks the same contract, so tooling, tests, and flows are written once and reused everywhere.
  3. Configurable, not hard-coded. Each point can be emitted raw or typed; decoding is declared in config, never compiled in.
  4. Testable offline. Connectors ship golden acceptance vectors; flows are validated with tedge flows test. A contributor can prove correctness without hardware.
  5. Boringly extensible. Adding a protocol means implementing one trait and passing one conformance suite — a task small enough to specify for an AI agent.
  6. Community-owned. A registry, a template, an RFC process, and a contribution guide make the framework a place people can build on, not just consume.

In scope: the architecture, the normative contract, machine-readable schemas, the SDK and trait design, the Modbus reference spec with acceptance vectors, illustrative flow packages, the conformance suite design, the community model, and a migration path.

Out of scope (follow-up work): the actual Rust crates, production flow packages, packaging/CI, and any dynamic-plugin runtime. Those are described well enough to be implemented — by humans or AI agents — but are deliberately not built here.