TL;DR:
- BACnet/IP is the dominant protocol in commercial building automation — HVAC, lighting, access control, elevators — and it’s not going anywhere soon
- Bridging BACnet to MQTT at the edge unlocks cloud analytics, AI-driven optimisation, and integration with modern IoT platforms without replacing the BMS
- Purpose-built gateway hardware (Loytec, Toradex, Advantech) or open-source brokers running on local edge nodes (YABE, bacnet4j, the open62541-based stacks) handle the translation layer
Building automation systems control some of the most energy-intensive equipment in commercial real estate: HVAC, lighting, elevators, fire suppression, and access control. The dominant protocol at the control layer is BACnet, standardised as ASHRAE 135 and ISO 16484-5, with BACnet/IP variants handling Ethernet-connected devices in most buildings built or retrofitted since the early 2000s.
BACnet works. It was designed for reliability and interoperability in building environments, and it delivers. The problem is that BACnet was not designed for cloud integration, real-time analytics, or the kind of data pipelines that modern energy optimisation and predictive maintenance applications require.
MQTT was. Bridging between the two protocols at the edge — reading BACnet objects and publishing them as MQTT topics — is now a standard pattern in smart building retrofits.
Understanding BACnet Data Model
BACnet represents device data as objects with properties. A temperature sensor is an Analog Input object. A setpoint is an Analog Value. A binary output (on/off) is a Binary Output. Each object has a set of standard properties: Present_Value, Status_Flags, Description, Units.
The BACnet device and its objects can be discovered via Who-Is and Read-Property-Multiple services on the network. This is how commissioning tools and gateway software discover what data is available without prior configuration — you broadcast a Who-Is, collect I-Am responses, then walk each device’s object list.
Understanding this model matters for your MQTT bridge design: you’ll need to decide which objects to subscribe to, how frequently, and how to represent BACnet’s rich metadata (units, engineering ranges, status flags) in the flatter MQTT topic structure.
The Bridge Architecture
The standard pattern has three components:
-
BACnet polling agent — software running on an edge node that reads BACnet objects from the local network via BACnet/IP. It can use COV (Change of Value) subscriptions for low-latency updates, or periodic polling where COV isn’t supported.
-
Translation layer — converts BACnet objects and properties to MQTT topics, handling data type mapping (BACnet enumerated values, priority arrays, timestamp alignment) and normalisation.
-
MQTT broker — publishes the translated data, either locally (Mosquitto on the edge node) or to a cloud broker (AWS IoT Core, Azure IoT Hub, HiveMQ Cloud).
Topic Structure
A clean MQTT topic hierarchy for a BACnet bridge:
building/{site_id}/device/{device_id}/object/{object_type}/{object_instance}/present_value
building/{site_id}/device/{device_id}/object/{object_type}/{object_instance}/status_flags
For example:
building/hq-london/device/1001/object/analog-input/3/present_value → 21.4
building/hq-london/device/1001/object/analog-input/3/status_flags → ["fault": false, "in_alarm": false]
Including engineering units and metadata in a separate meta topic per object avoids bloating each telemetry message:
building/hq-london/device/1001/object/analog-input/3/meta → {"units": "degreesCelsius", "description": "Supply Air Temp", "device_name": "AHU-03"}
Software Options
Open Source
bacnet4j (Java): Mature BACnet stack with full client support. Basis for several commercial products. Requires Java runtime on the edge node.
bacpypes3 (Python): Modern async Python BACnet stack. Good for custom gateway scripts and integration with Python-based data pipelines. Works well with the aiomqtt library for async MQTT publishing.
YABE (Yet Another BACnet Explorer, .NET): Useful for discovery and debugging during setup; not typically used for production polling.
node-bacnet: Node.js BACnet client, good for lightweight edge deployments where Node.js is already present.
Commercial Gateways
For production deployments, purpose-built gateway hardware handles the protocol translation in firmware with better reliability than a general-purpose edge node running open-source code:
- Loytec L-Gate 900: BACnet/IP to MQTT gateway with web-based configuration, supports COV subscriptions, cloud broker integration. Common in European commercial building deployments.
- Advantech WISE-4000 series: Industrial IoT gateway with BACnet/IP support, designed for DIN rail mounting in electrical panels.
- Toradex BACnet gateway modules: ARM-based modules running Linux with BACnet-to-MQTT bridge software, designed for OEM integration.
- Chipkin BACnet to MQTT Gateway: Purpose-built product with a visual configuration tool; handles object discovery and mapping without code.
A Simple Python Bridge
For a quick prototype or small deployment, bacpypes3 and aiomqtt handle the basics:
import asyncio
from bacpypes3.app import Application
from bacpypes3.local.device import DeviceObject
from bacpypes3.primitivedata import ObjectIdentifier
from bacpypes3.basetypes import PropertyReference
import aiomqtt
BROKER = "mqtt://your-broker:1883"
SITE_ID = "hq-london"
async def poll_and_publish(bacnet_app, mqtt_client, device_address, objects):
for obj_id in objects:
obj_type, obj_instance = obj_id
try:
value = await bacnet_app.read_property(
device_address, obj_id, "present-value"
)
topic = f"building/{SITE_ID}/device/{device_address}/object/{obj_type}/{obj_instance}/present_value"
await mqtt_client.publish(topic, payload=str(value))
except Exception as e:
print(f"Read failed for {obj_id}: {e}")
async def main():
# Initialise BACnet application and MQTT client
# Discovery, object mapping, and polling loop omitted for brevity
pass
Real implementations need error handling, COV subscription management, offline buffering (store-and-forward for when the MQTT broker is unreachable), and reconnection logic.
Common Challenges
Network segmentation: BACnet/IP uses UDP broadcast for discovery. If your BMS controllers and edge node are on different VLANs, Who-Is broadcasts won’t reach across the boundary. Use BACnet Broadcast Management Devices (BBMDs) or configure directed broadcasts to cross VLAN boundaries.
COV subscription limits: Many BACnet devices support a limited number of simultaneous COV subscriptions (often 10–20). For buildings with hundreds of objects, polling is unavoidable. A 30-second poll interval is typical for HVAC data; critical alarms should use COV.
Object priority arrays: BACnet writable objects (setpoints, outputs) use a 16-level priority array. When writing via MQTT control messages, you must specify a priority level. Incorrect priority can result in writes silently being overridden by higher-priority BMS commands.
Clock synchronisation: BACnet has its own time-synchronisation service. For accurate event timestamping, ensure your edge node and BACnet devices are NTP-synchronised.
Bridging BACnet to MQTT doesn’t require replacing any existing BMS equipment — it sits alongside, reads the existing data, and opens it to the modern tooling ecosystem. That makes it one of the most practical smart building upgrades available for commercial properties with existing BACnet infrastructure.