I Built a BACnet Chiller Plant Simulator

While working on a security audit for a building management system, chillers, pumps, and cooling towers all running on BACnet, I ran into a practical problem: testing anything needs a device to talk to, and a live plant isn’t something you can safely poke at. Vendor simulators are tied to their own ecosystems, and the open examples only model a single room. I needed a full plant, so I built one and released it as open source.
BACnet in 30 Seconds
BACnet (Building Automation and Control Network) is the language most commercial buildings speak: HVAC, chillers, lighting, access control. It was first published as ANSI/ASHRAE Standard 135 in 1995 and became an ISO standard (ISO 16484-5) in 2004. Every device exposes objects, and each object has properties. A sensor is an Analog Input with a presentValue. A setpoint is an Analog Value you can write to. Devices announce themselves with a Who-Is broadcast, and you read or change values with ReadProperty and WriteProperty.
That’s the whole model. Objects, properties, and a few services to read, write, and discover.
Why It Isn’t Secure
Classic BACnet, the version in the overwhelming majority of buildings, has no real security:
- No authentication. Devices don’t check who is asking. Reach the network and you can enumerate everything on it.
- No encryption. Traffic is plaintext. Anyone on the wire reads every setpoint and sensor value.
- Trusted writes. WriteProperty needs no credentials. If a point is writable, whoever reaches it can change it.
- Broadcast discovery. One Who-Is maps the whole network. Recon is a single packet.
These are not just theoretical: CISA ICS advisories on building-automation products regularly cite cleartext transmission and unauthenticated BACnet traffic as the underlying weakness, pointing to BACnet Secure Connect as the fix (example advisory). BACnet/SC, added in the 2020 edition of the standard, wraps communication in TLS and certificates, but it’s barely deployed. Buildings last decades and nobody rips out a working control system, so the reality stays flat and open. Soft target for an attacker, under-examined domain for defenders.
The Simulator
Mine stands up a BACnet/IP device with the full point map of a chiller plant: supply and return water temps, flow rate, plant power and efficiency, individual chillers with load and COP, pumps, cooling towers, and the aggregate metrics a dashboard pulls. A physics loop drives the values so they move like a real plant across a day instead of sitting frozen. It answers Who-Is, serves ReadProperty, and accepts WriteProperty.
It’s built on BAC0 (on top of bacpypes), so the plant is plain data: points with names, units, and starting values, and a loop nudges them over time.
One command brings a plant online:
python chiller_simulator.py -a 192.168.10.11/24 -p 47808 -id 101
Point a scanner at it and it looks like a building.
Multiple Plants on One VM
Testing against one device only goes so far. For a small campus of plants, give a single VM multiple network cards and bind one simulator to each. Different NIC, different subnet, different device ID, so from the network they look like separate buildings.

- Add adapters to the VM. Two or three, coming up as
eth0,eth1,eth2on a Linux guest. - Assign an address per NIC, e.g.
192.168.10.11/24,192.168.20.11/24,192.168.30.11/24. - Launch one simulator per NIC with a unique device ID:
python chiller_simulator.py -a 192.168.10.11/24 -id 101 &
python chiller_simulator.py -a 192.168.20.11/24 -id 102 &
python chiller_simulator.py -a 192.168.30.11/24 -id 103 &
A discovery scan now sees three distinct plants, each with a full point set, each on its own network. A building automation lab on a laptop, for nothing.
Get It
git clone https://github.com/prashsiv-eng/bacnet-chiller-simulator.git
cd bacnet-chiller-simulator
pip install -r requirements.txt
python chiller_simulator.py -a 127.0.0.1/24
If you work in OT or ICS security, or you’re just curious how the building around you runs, it’s a low-stakes way to get hands on the protocol. Break it, script against it, extend the plant model.