BACnet driver

BACnet/IP runs in the fieldbus container (network_mode: host), which polls devices and publishes readings to central over MQTTS. Central exposes the commissioning UI and REST routes; it never touches the BACnet wire itself.

Configuration

  • config/fieldbus/ — device filters, NIC/BBMD, poll config for the fieldbus container
  • Recipe: standalone runs fieldbus alongside central; edge runs fieldbus only and attaches to a remote central (see Build recipes)
  • OPENFDD_BACNET_MODE — live for field buses

The fieldbus container owns UDP 47808 and the local diagnostic device (599999). Do not run a second BACnet server on the same host.

Key API routes

Method Path Purpose
POST /api/bacnet/whois Discover devices
GET /api/bacnet/driver/tree Driver tree
POST /api/bacnet/read Read present value
GET /api/bacnet/poll/status Poll loop status
POST /api/bacnet/write-dry-run Simulate write
POST /api/bacnet/write Live write — requires explicit approval

Overrides

Scan and export priority-array overrides:

  • GET /api/bacnet/overrides/summary
  • POST /api/bacnet/overrides/scan-once

Durable priority history

The connector capability and inventory surfaces expose a separate read-only history action when the fieldbus has a trusted tenant/building/edge scope. POST /api/connector/priority-history returns bounded, versioned snapshots containing exactly P1–P16 for a selected configured object. The central route POST /api/connectors/{edge_id}/priority-history forwards that same scoped history only after authenticated tenant, building, and edge checks.

Operators and admins can request one bounded visit with the typed trigger POST /api/connectors/{edge_id}/priority-history/trigger; viewers receive 403 before central contacts the edge. The edge trigger is protected by its fieldbus API key and shares the scheduler’s non-overlap guard. The trigger does not bypass the explicit scanner enable flag.

The scheduler is disabled unless OPENFDD_PRIORITY_SCAN_ENABLED=1. When enabled it visits one configured device after a delay following each completed device visit (default 3600 seconds, minimum 300), persists a stable device cursor and bounded records on the edge, and advances the cursor after a complete device visit. It has no catch-up burst and does not discover devices, write values, release priorities, or remediate equipment. A read failure remains a typed error snapshot so the history cannot silently turn a failed read into NULL data.

The edge store appends fsynced JSONL records and cursor entries, periodically compacts them into a bounded checkpoint, and fsyncs the containing directory after replacement. A torn trailing line is truncated during recovery. A corrupt non-tail entry makes history unavailable while the core BACnet polling service continues; no whole-history rewrite is used for each observation. History cursors are bounded to the retained 100,000-record window, and a page probes at most offset + page_size + 1 matching records to determine whether continuation exists.

The cloud-only central and Railway recipes intentionally have no fieldbus scanner or BACnet socket. MQTT history synchronization is not part of this contract; use a separately versioned transport contract if that is added later.

Dashboard

BACnet tab (/bacnet) — scan, add devices, set poll rates, browse points.

BACnet writes affect live equipment. Use dry-run first. Agents must not write without human approval.