Source, flow, sink. Drawn once, run as one process.

You draw the route on a canvas. The same binary polls the sources, runs the nodes and delivers to the sinks. There is no second service to deploy.

  1. source

    Every reading becomes one record.

    Adapters emit CanonicalMachineData: source, timestamp, machine, status, axes, spindle and free-form metadata. Quality travels with it, per record and per value: good, uncertain or bad. A source that goes silent produces an offline record, not a gap.

    modbus 103maps tospindle.speed

    canonical record (telemetry.v1)
    {
      "source": "modbus",
      "timestamp": "2026-08-01T09:30:00Z",
      "machine": { "id": "PLC-01", "name": "Assembly Line PLC" },
      "status": { "state": "running" },
      "axes": [ { "name": "X", "position": 12.5, "unit": "mm" } ],
      "spindle": { "speed": 8000, "load": 41.5 },
      "quality": "good",
      "metadata": { "temp": 61.2 }
    }
  2. flow

    Route, filter and reshape on a canvas.

    Wire nodes together and each source can take its own path to its own sinks. Only the JavaScript transform starts a JavaScript engine; every other node is native Rust.

    • source
    • filter
    • branch
    • map
    • transform
    • http
    • sink
    • DSL for the common case. Filters and field math run against the in-memory record with no JSON round trip.
    • JavaScript when you need loops or regex. Sandboxed: no filesystem, no network, an 8 MB heap and a 100 ms deadline by default.
    • Edit with Node-RED's moves. Quick add on a dropped wire, link nodes across tabs, groups, wire splicing and Ctrl+F.
    • Test before you deploy. Run a node against the last real record from its source and see each step, the diff and what would be delivered. Nothing is sent.
    • Optional script drafting. Describe a rule in plain language and an OpenAI-compatible endpoint that you configure writes the script. The server validates it before it lands.
    The editor with a JavaScript transform node open: the script, a Syntax OK check, and the result of Test with last record, showing five passing steps and a diff that moves the raw payload into temperature and pressure fields.The editor, dark theme, with a JavaScript transform node open: the script, a Syntax OK check, and the result of Test with last record, showing five passing steps and a diff that moves the raw payload into temperature and pressure fields.
    transform.dsl
    # keep only hot or vibrating machines
    when metadata.temp > 80 || metadata.vib > 5.0
    set metadata.alert = true
    set metadata.temp_f = metadata.temp * 9 / 5 + 32
    transform.js
    function transform(record, ctx) {
      if (record.metadata.temp > 95) return null;   // drop
      record.metadata.risk = record.metadata.temp * 0.8
                           + record.metadata.vib * 1.2;
      return record;
    }
  3. sink

    Fan out to every destination, concurrently.

    Each route picks its sinks and a record goes to all of them at once, each under its own deadline (5 s by default). One slow sink does not hold up the others, and the poll loop never waits on the network.

    • pushHMAC-signed POST to OpexMX or any receiver, through the durable queue.
    • natsPublishes to a NATS subject, one subject per machine.
    • mqttPublishes to an MQTT topic.
    • storageSQLite history with retention by days and by size.
    • clickhouseBatched writes to a ClickHouse table.
    • postgresBatched writes to Postgres or a TimescaleDB hypertable.
  4. debug

    Watch every record move.

    The debug panel streams what each node received and what it sent on, as the records flow. Narrow it to the nodes you chose, or leave it on all of them. Live record rates and the average run time sit under every node on the canvas.

    The editor with the Debug panel open and live: rows stream by with a time, in or out, the node name and the start of each record, for sources, the normalize transform, the alarm branch and the hub, push and storage sinks.The editor, dark theme, with the Debug panel open and live: rows stream by with a time, in or out, the node name and the start of each record, for sources, the normalize transform, the alarm branch and the hub, push and storage sinks.

From download to first record in minutes.

Run one binary, connect a PLC, a CNC or a webhook, and watch the records arrive in the editor.

Builds for Linux (x86_64 and ARM64), Windows and macOS.

  • One binary
  • Raspberry Pi to rack server
  • Signed delivery with retries

Sizing questions or an air-gapped install? Email marketing@opexcg.com or read the quick start.