Lightweight BACnet/IP + JSON-RPC edge microservice for Docker-based deployments, including optional Modbus TCP client features.
This app uses bacpypes3 CLI-style arguments to configure a BACnet server (--name, --instance, --address), similar to running commands directly in the bacpypes3 shell.
This is a mini tutorial for bacpypes3, which is great for troubleshooting and serves as a useful step for testing deployments before moving on to the DIY BACnet server application.
# Create virtual environment
python -m venv env
# Activate (Linux / macOS)
. env/bin/activate
# Install dependencies
pip install bacpypes3 ifaddrThe bacpypes3 library supports a shell mode out of the box, as shown directly below. Further down, we’ll cover setup for the full-featured DIY BACnet server, which includes a web app powered by FastAPI and supports easy Docker deployments.
python -m bacpypes3 \
--name BensRawBacpypes3Test \
--address 192.168.204.12/24 \
--instance 123456 \
--debugThis will start a basic BACnet device on your network for testing discovery (whois) and communication.
When running the bacpypes3 module interactively:
> help
commands: config, exit, help, iam, ihave, irt, rbdt, read, rfdt, rpm, wbdt, whohas, whois, wirtn, writeNOTE: If the step below does not work, the entire web application framework will not function. This step is critical. The
whoiscommand also accepts a range of BACnet instance IDs. For example:> whois 1 100.
> whoisExample output (trimmed):
3456788 192.168.204.16
3456789 192.168.204.13
3456790 192.168.204.14
# Discover devices in a range
whois 1000 3456799
# Read a point
read 192.168.204.13 analog-input,1 present-value
# Write a value (priority 9)
write 192.168.204.14 analog-output,1 present-value 999.8 9
# Release a command (null write)
write 192.168.204.14 analog-output,1 present-value null 9The app supports the same flags as bacpypes3:
--name→ Device name--instance→ Device instance ID--address→ IP/subnet/port (optional)--public→ Enable LAN HTTP access +/docs
Tip: Omit
--addresswhen a single NIC is sufficient.
This example sets up the diy-bacnet-server server locally and generates a Bearer token via a .env file.
git clone https://github.com/bbartling/diy-bacnet-server.git
cd diy-bacnet-server
python3 -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"
# Create API key
printf 'BACNET_RPC_API_KEY=%s\n' "$(openssl rand -hex 32)" > .env
# Load environment variables
set -a && . ./.env && set +a
# Run server
python -m bacpypes_server.main \
--name my-device \
--instance 123456 \
--address 192.168.204.18/24:47808 \
--public \
--debugWith --public enabled:
-
Open API docs:
http://127.0.0.1:8080/docs(or use the host’s LAN IP from another machine)
POST /server_hello→ No auth required- All other JSON-RPC endpoints → Require Bearer token (if API key is set)
- Ensure UDP port 47808 is open for BACnet discovery (Who-Is / I-Am).
- The
.envfile is optional for local, unsecured loopback testing. - Behavior should match standard bacpypes3 discovery and communication patterns.
The most common issue is firewall configuration.
You may already have UDP 47808 open for BACnet, but the HTTP API (JSON-RPC / Swagger) runs on TCP 8080. This can cause a confusing situation where:
curlor browser works locally on the server- but fails from another machine on the LAN
Allow port 8080:
sudo ufw allow in on enp3s0 to any port 8080 proto tcp comment 'diy-bacnet HTTP'Or more broadly:
sudo ufw allow 8080/tcpVerify:
sudo ufw status numberedcurl -sS -o /dev/null -w '%{http_code}\n' http://192.168.204.18:8080/docsExpected result:
200
If you don’t see 200, it’s almost always a firewall or network interface binding issue.
--network host is required for BACnet/IP so UDP broadcasts (port 47808) work correctly on the LAN.
git clone https://github.com/bbartling/diy-bacnet-server.git
cd diy-bacnet-server
printf 'BACNET_RPC_API_KEY=%s\n' "$(openssl rand -hex 32)" > .env
docker build -t diy-bacnet-server .
docker run -d \
--network host \
--restart unless-stopped \
--env-file .env \
--name diy-bacnet-gateway \
diy-bacnet-server \
python3 -u -m bacpypes_server.main \
--name asdf \
--instance 123456 \
--address 192.168.204.18/24:47808 \
--publicSwagger Authorize uses the same BACNET_RPC_API_KEY from .env.
Logs
docker logs -f diy-bacnet-gatewayStop
docker stop diy-bacnet-gatewayStart
docker start diy-bacnet-gatewayRestart
docker restart diy-bacnet-gatewayRemove (fresh rebuild)
docker rm -f diy-bacnet-gatewaySwagger Authorize uses the same BACNET_RPC_API_KEY value as in that file.
This application is part of a broader ecosystem that together forms the Open FDD AFDD Stack, enabling a fully orchestrated, edge-deployable analytics and optimization platform for building automation systems.
-
🔗 DIY BACnet Server Lightweight BACnet server with JSON-RPC and MQTT support for IoT integrations. Documentation · GitHub
-
📖 Open FDD AFDD Stack Full AFDD framework with Docker bootstrap, API services, drivers, and React web UI. Documentation · GitHub
-
📘 Open FDD Fault Detection Engine Core rules engine with
RuleRunner, YAML-based fault logic, and pandas workflows. Documentation · GitHub · PyPI -
⚙️ easy-aso Framework Lightweight framework for Automated Supervisory Optimization (ASO) algorithms at the IoT edge. Documentation · GitHub · PyPI
- Python 3.12+
bacpypes3ifaddrfastapi-jsonrpcuvicornrequestshttpxaiomqttpyModbusTCP>=0.2.0pip+ virtual environment tooling (python3 -m venv)- Docker (for container runs; use
--network hostfor BACnet/IP behavior) - OpenSSL (optional, used in examples to generate
BACNET_RPC_API_KEY)
MIT