A lightweight, containerized BACnet/IP server, built as a fully asynchronous asyncio-based application using bacpypes3, FastAPI, and JSON-RPC. It's designed for rapid development, prototyping, and seamless integration into modern microservice environments via Dockerβideal for IoT edge deployments.
The app reads a CSV configuration file at startup to define BACnet points and provides a JSON-RPC API (instead of REST) for interacting with the BACnet/IP server.
JSON-RPC compliant interface with built-in OpenRPC schema support and Swagger-compatible
/docs
endpoint based on the fastapi-jsonrpc library.
Place a CSV file in the csv_file
directory with the following format. It is parsed at startup to define the exposed BACnet points:
Name,PointType,Units,Commandable
chillerEnable,BV,Status,Y
chwSetPoint,AV,degrees celsius,Y
chillerDemandLimit,AV,,N
evapDP,AV,kpa pressure units,N
evapFlow,AV,,N
- Name: Required. BACnet
objectName
. - PointType: Required. Only
AV
(Analog Value) orBV
(Binary Value) supported. - Units: Optional. BACnet
EngineeringUnits
(defaults tonoUnits
if omitted). - Commandable:
Y
for writable priority-array points,N
for read-only.
By default security reasons, the BACnet RPC API is hardcoded to run on localhost
(127.0.0.1), meaning it is not accessible externally by default. However, when developing remotely (e.g., via SSH into a Linux server or Raspberry Pi), you can still view the Swagger UI and test endpoints securely using VS Code's built-in SSH tunneling. If a public-facing RPC server is desired (not recommended), you can pass the --public
flag as a command-line argument when starting the app. See the β‘ Test App section below for the example command to start the app.
-
Connect to the remote host using VS Code Remote - SSH.
-
Run the app:
python3 bacpypes_server/main.py --name BensServer --instance 123456
-
When the app starts, it will log:
JSON-RPC API ready at http://localhost:8080/docs
-
VS Code will prompt you to forward port
8080
. Click "Forward Port" when prompted. -
Open your browser on your local machine and go to:
http://localhost:8080/docs
Youβll now see the full Swagger UI for the JSON-RPC API.
{
"jsonrpc": "2.0",
"method": "client_read_property",
"params": {
"device_instance": 123456,
"object_identifier": "analog-output,1",
"property_identifier": "present-value"
},
"id": "0"
}
{
"jsonrpc": "2.0",
"result": {
"chwSetPoint": 0.0
},
"id": "0"
}
jsonrpc
: Always"2.0"
(protocol version).result
: The return value.id
: Echoes the request ID for correlation.
Returns a welcome message.
Updates values via a dictionary payload for the BACnet server.
Discoverable BACnet server objects (sometimes called "points") are defined in the CSV configuration file.
Reads only commandable BACnet server point values.
These are points that can be written to by an external control system, such as a BAS. This method retrieves the latest values that were written to the server over BACnet for commandable points defined in the CSV file.
Reads the present values of all BACnet server points defined in the configuration, regardless of whether they are writable or read-only.
A BACnet client feature used to read any BACnet property from a discovered remote device.
Supports single-property reads using object identifier and property name.
A BACnet client feature used to write a value to a BACnet property on a remote device.
Supports priority-based override logic if a priority level is specified.
A BACnet client feature that uses the ReadPropertyMultiple (RPM) service to fetch multiple properties from a single remote BACnet device in one request.
Sends a Who-Is broadcast across a specified range of BACnet device instance IDs to discover devices on the network.
Returns all I-Am responses along with metadata like vendor ID and description.
For a given
device_instance
, performs a discovery of all objects and their human-readable names.
Useful for populating a list of points available for polling, command, or analytics.
Scans all objects for a single device, identifies those that support the priority-array property, and returns the active override values (if any).
This simulates supervisory control logic that checks if a point is under manual or automatic control.
Reads the full priority array (1β16 levels) for a single point.
Returns a list of priority levels and their current values including nulls. This is useful for understanding overrides in effect on a writable BACnet object.
Discovers BACnet routers on the network that advertise routing capabilities to other BACnet networks. This includes BACnet/IP-to-MSTP gateways, but may also include other router types depending on the system architecture.
python3 bacpypes_server/main.py --name BensServer --instance 123456 --debug
You can also expose the server publicly (bind to 0.0.0.0
) if needed:
python3 bacpypes_server/main.py --name BensServer --instance 123456 --debug --public
By default, the server binds to 127.0.0.1
(localhost only) for security β i.e., for microservice access only, such as Docker-to-Docker communication on the same machine.
If you specify the --public flag, the server binds to 0.0.0.0
, making it accessible from other machines outside of the host operating system's firewall.
If you'd rather run this in a container, check out the Docker Setup Guide. π
diy-bacnet-server
is released under the MIT License, ensuring it remains free and accessible for all.
γMIT Licenseγ
Copyright 2025 Ben Bartling
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.