A Node.js-based, configuration-driven Building Management System (BMS) integration agent that acquires telemetry from heterogeneous on-premise BMS deployments and wireless environmental sensor networks, archives the full telemetry in a per-building PostgreSQL warehouse and forwards a curated subset to a central IoT platform (ThingsBoard) over MQTT or HTTP. The agent is administered through a dedicated React-based web UI that exposes per-meter pipeline toggles, metadata edits, asset-hierarchy management and ThingsBoard reconciliation, with per-building access control for site operators.
BMS deployments in the field are typically heterogeneous (different vendor stacks, different protocol variants, different naming conventions across buildings) and their metadata is often poor: human-readable labels are absent, inconsistent or in the local language only; the scope of a meter (which room, floor or system it serves) is frequently missing or wrong; and outright errors carried over from the original commissioning are common. Forwarding such telemetry directly to a central IoT platform propagates the noise into every downstream consumer.
Existing per-building integration scripts solve the acquisition part of this problem but leave operators with no good way to manage which meters are forwarded, which are merely archived, what their human-readable labels and ThingsBoard placements should be, and how to correct the inevitable mistakes in BMS metadata. The component provides an operational web UI, and inserts a metadata-adjustment layer between the raw BMS readings and the data that ultimately reaches the central IoT platform.
The pattern can be applied to any campus, district or multi-site deployment that needs to consolidate telemetry from several independent BMS or sensor networks into a single IoT platform, particularly where the site personnel responsible for each building are not the same as the developers of the integration code.
- Heterogeneous BMS acquisition through a single image. A configuration-driven Node.js agent acquires telemetry from multiple buildings and multiple BMS variants through a small provider abstraction. New BMS variants can be added by implementing the provider interface without touching the orchestrator.
- Per-meter pipeline control. Each meter carries independent archive and pass_to_thingsboard flags. The component honours these flags directly: rows fetched from the BMS are archived to the local warehouse only when archive is set, and forwarded to the central IoT platform only when pass_to_thingsboard is set. This separates long-term traceability from runtime telemetry, and lets operators silence noisy or duplicate meters from the central platform without losing the underlying record.
- Curated metadata and validation layer. Operators edit the human-readable label, units, category, scope, polling interval, valid-value bounds and intended ThingsBoard placement of each meter through the admin UI. Pending name and parent overrides are queued in a dedicated table and reconciled into ThingsBoard by the periodic TB sync routine, so the corrected metadata reaches the central IoT platform without operators needing to touch its UI directly. Many-to-many "measures" assertions correctly model meters that cover several rooms, and bounds-based runtime checks let operators choose whether to retain physically implausible readings in the warehouse while withholding them from the platform.
- Asset-hierarchy and ThingsBoard reconciliation. The admin UI presents the building as a building → floor → room → system tree, mirrors the same hierarchy from the central IoT platform on demand, and lets operators create new assets, re-parent existing ones, and trigger a fresh sync.
- Aranet wireless sensor management. The same admin UI manages the Aranet inventory and per-sensor metadata overrides, removing the need for hand-curated metadata files and applying the same archive/forward toggles as the BMS pipeline.
- Per-building access control on the admin UI. Operators are assigned an explicit per-building access list; a user without an entry for a given building cannot view or edit its meters, sensors or hierarchy.
- Operational visibility. A central error log captures BMS keep-alive failures, fetch errors and per-meter exceptions with their stack traces and source IPs; the log is exposed in the admin UI with a 90-day rolling retention.
- On-demand backfill. A separate backfill worker, sharing the same provider abstraction and metadata model, retrieves historical telemetry beyond the incremental cursor when a building is first onboarded or coverage is extended backwards.
The deployed component consists of three classes of process, all built from the same agent image:
- Per-building incremental load daemon: one instance per building, configured by environment variables and by entries in the building's PostgreSQL warehouse. The daemon loads the configured BMS provider, walks the list of meters that are flagged as either archived, forwarded to ThingsBoard, or both, infers each meter's polling frequency from its recent timestamps, fetches new readings since the last successful scrape, archives them to PostgreSQL when archive is set, and publishes them to ThingsBoard when pass_to_thingsboard is set. A periodic ThingsBoard sync routine reconciles pending operator-driven metadata changes into ThingsBoard via its REST API.
- Per-building backfill worker: an opt-in companion service (run on demand) that uses the same provider abstraction to retrieve historical telemetry beyond the incremental cursor, used when a building is first onboarded or when extending coverage backwards in time.
- Aranet worker: a single worker process that serves all buildings configured to use the Aranet wireless environmental sensor cloud, reading per-building Aranet credentials and curated sensor metadata from the same warehouses and forwarding readings to ThingsBoard through the same MQTT contract as the BMS adapters.
A separate administrative web application is deployed as one further container. The application is a React single-page frontend served by an Express backend; operator credentials and per-building access rights live in a local SQLite database. The backend exposes one HTTP API surface and reaches out to each building's PostgreSQL warehouse on demand, so adding a new building is a configuration change rather than a code change. Authentication is local to the BMS Integration Component.
The per-building schema follows the same shape across every building: a building_meta key/value store, a typed asset hierarchy (building → floor → room → system), a unified meters table holding the raw BMS-side identifiers (path, parent_name, child_id, history_ref) alongside the curated, human-readable representation (label, units, category, what the meter measures, valid-value bounds, intended ThingsBoard placement) and the archive / pass_to_thingsboard pipeline-control flags, a meter_measures many-to-many mapping for meters that cover several rooms, a meter_name_overrides table holding operator-driven renames pending application to ThingsBoard, a building_config key/value store for runtime parameters (BMS endpoint, clock offset, polling pace), an append-only error log, and the partitioned meter_values time-series table.
The BMS Integration Component exists to bridge legacy and newly installed small-scale BMS — and the Aranet-style wireless environmental sensor networks that typically supplement them — to a central IoT platform, in a way that is uniform across vendors, robust to the inconsistent metadata typically encountered in the field, and operable by site personnel rather than only by its developers. Its role is twofold: (1) acquire and forward telemetry on a continuous, incremental basis with per-building isolation; (2) provide the operator-facing controls that turn the raw BMS into a curated, trustworthy data source for downstream applications (digital twins, analytics, dashboards, modelling pipelines).
- Runtime: Node.js (ECMAScript modules), pg (PostgreSQL client), mqtt, node-fetch, dotenv, csv-parse.
- Provider abstraction: small interface (connect, keepAlive, fetchMeterData, onBatchBoundary) with two reference implementations — niagara-iqvision and niagara-trend — covering the IQVision history view and the Trend log view of the Niagara Framework respectively.
- Per-building data warehouse: PostgreSQL with a partitioned meter_values time-series table (six-month range partitions) for predictable insert and query performance over multi-year retention.
- Central IoT platform integration: ThingsBoard via MQTT (gateway-style telemetry payload) for streaming data, plus a REST sync routine that reconciles operator-driven metadata changes (device names, asset hierarchy, Contains / Measures relations) into ThingsBoard.
- Administrative web UI: single-page React frontend served by an Express backend, with operator credentials and per-building access stored in a local SQLite database, deployed as a separate container alongside the agent instances.
- Operational: Docker, Docker Compose / Kubernetes, GitLab CI for image build and rollout. Each building runs as a dedicated containerised agent instance against an isolated database, but all instances share the same image and configuration model.