Blocked Example - blocked on chipkin/cas-bacnet-stack#2032. A B-DAP is "a BACnet network router that provides device address proxying for one or more directly connected networks" (Annex L.7.7): it answers Who-Is with I-Am on behalf of the devices on a proxied network, from its Device_Address_Proxy_Table (clause 19.9). The CAS BACnet Stack has never implemented that proxying (stack CHANGELOG, #2346: it "never answers Who-Is from a Device_Address_Proxy_Table"), so this example cannot perform the B-DAP Profile Function. What it does today is what a B-SA does: a single, non-routing BACnet/IP device with three sensor inputs and three commandable outputs. It does not route, and it proxies nothing.
A minimal, copy-paste-friendly example showing how to implement the BACnet B-DAP (BACnet Device Address Proxy) device profile in C++ using the CAS BACnet Stack. It listens on BACnet/IP (UDP 47808), answers ReadProperty requests, accepts WriteProperty to its commandable outputs, and is discoverable via Who-Is / I-Am and Who-Has / I-Have.
Download a prebuilt binary (Windows and Linux x64) - or build it yourself, see Build below.
- TUTORIAL.md - how to extend this example and how to review it for conformance. Read it when you start turning this into your own device.
- docs/PICS.md - the Protocol Implementation Conformance Statement: every object, every property, and who answers it.
Versions: this document describes example v1.1.0, built and verified against CAS BACnet Stack 6.0.23 (
6.x@69b6249f), at Protocol_Revision 30, with the vendoredcommon/helper at v3.4.1. Running the example prints all three - if what it prints disagrees with this line, trust the program and checkCHANGELOG.md.
B-DAP's required BIBB set - DS-RP-B, DS-WP-B, DM-DDB-B, DM-DOB-B - is identical to B-SA (Smart Actuator)'s, so the object model and service wiring below are the same: three read-only sensor inputs plus three commandable outputs a client drives with WriteProperty. B-DAP additionally requires DM-DAP-B (Device Address Proxying, Annex K.5.37 - answering Who-Is with I-Am on behalf of proxied devices), which itself requires NM-RC-B: a B-DAP is a router. This example does neither - see TODO.md for why.
A BACnet output object's Present_Value is not written directly. Instead it is
commandable: it is resolved from a 16-slot Priority_Array plus a
Relinquish_Default.
- A
WriteProperty(Present_Value, value, priority)storesvaluein slotpriority(1 = highest .. 16 = lowest). If the client omits the priority, BACnet uses 16. - A
WriteProperty(Present_Value, NULL, priority)relinquishes that slot. - The stack reports the value in the highest-priority non-null slot as the
effective
Present_Value; if every slot is null, it reportsRelinquish_Default.
This lets several controllers command the same output at different priorities
(e.g. a manual override at priority 8 beats a normal command at 16) and cleanly
hand control back by writing NULL. The application just stores the array (see the
Commandable struct in main.cpp); the stack does the priority resolution.
Device 389021 "Chipkin Example B-DAP" (Vendor 389 - Chipkin Automation Systems)
│
├── Analog Input 1 "Bronze" Present_Value 21.5 (REAL, degrees Celsius; read-only)
├── Binary Input 1 "Emerald" Present_Value inactive (0 = inactive / 1 = active; read-only)
├── Multi-State Input 1 "Hot Pink" Present_Value 1 (state, 1..3; read-only)
├── Analog Output 1 "Chartreuse" Present_Value 20.0 (REAL setpoint; WRITABLE, commandable)
├── Binary Output 1 "Fuchsia" Present_Value inactive (0/1; WRITABLE, commandable)
├── Multi-State Output 1 "Indigo" Present_Value 1 (state, 1..3; WRITABLE, commandable)
└── Network Port 1 "Vermilion" the BACnet/IP port (required on every device)
The three input objects (Bronze, Emerald, Hot Pink) are the shared minimum
every example in this series carries. The three output objects (Chartreuse,
Fuchsia, Indigo) demonstrate DS-WP-B; their Present_Value starts at the
Relinquish_Default (no slots commanded yet). Object names follow this series'
colour-naming convention (Device is always "Chipkin Example B-DAP").
The example implements exactly the capabilities below - and nothing more, which is the point of a profile example. These capabilities satisfy four of the five BIBBs the B-DAP (BACnet Device Address Proxy) profile requires; DM-DAP-B is not implemented (see TODO.md). Because DS-RP-B/DM-DDB-B/DM-DOB-B are a superset of the B-GENERAL baseline, a conformant device necessarily satisfies B-GENERAL too. That is subsumption, not a second claim: this repository still claims exactly one profile.
| BIBB | Description | Supported |
|---|---|---|
| DS-RP-B | Data Sharing - ReadProperty - B | ✅ |
| DS-WP-B | Data Sharing - WriteProperty - B | ✅ |
| DM-DDB-B | Device Management - Dynamic Device Binding - B | ✅ |
| DM-DOB-B | Device Management - Dynamic Object Binding - B | ✅ |
| DM-DAP-B | Device Management - Device Address Proxying - B | ⏳ not implemented — see TODO.md |
| Service | Notes |
|---|---|
| ReadProperty | Responds to property reads (DS-RP-B). |
| WriteProperty | Accepts writes to the commandable outputs' Present_Value (DS-WP-B). |
| Who-Is / I-Am | Answers Who-Is with I-Am, and broadcasts an I-Am on start-up (DM-DDB-B). |
| Who-Has / I-Have | Answers Who-Has with I-Have (DM-DOB-B). |
| Object type | Instance | Name | Access |
|---|---|---|---|
| Device | 389021 | Chipkin Example B-DAP | - |
| Analog Input | 1 | Bronze | read-only |
| Binary Input | 1 | Emerald | read-only |
| Multi-State Input | 1 | Hot Pink | read-only |
| Analog Output | 1 | Chartreuse | writable (commandable) |
| Binary Output | 1 | Fuchsia | writable (commandable) |
| Multi-State Output | 1 | Indigo | writable (commandable) |
| Network Port | 1 | Vermilion | - |
Every required property of every object, and who answers it, is in docs/PICS.md.
This example builds against the CAS BACnet Stack, which is a commercial Chipkin
product - it is not free or open source, and there is no public/trial build.
The stack is referenced here as the private git submodule
submodules/cas-bacnet-stack; you can only fetch and build it once you have a CAS
BACnet Stack license and access to that repository.
To get the CAS BACnet Stack (and access to build this example), contact Chipkin: https://store.chipkin.com/services/stacks/bacnet-stack or sales@chipkin.com.
You do not need a stack licence to read this example, or to run a prebuilt release binary. The licence is what lets you build it - that is the part the stack submodule gates.
This is a self-contained project. It ships:
main.cpp- the example device.common/- the shared helper (UDP, callbacks, CLI, keyboard) vendored in.CMakeLists.txt- the build, the same on Windows, Linux, and macOS.docs/PICS.md- the conformance statement.tests/- the Wire Tests (see Wire Tests), andwiretest/, the series' shared Python harness they are written against (vendored, likecommon/).submodules/cas-bacnet-stack/- the CAS BACnet Stack as a git submodule (private; requires a license - see above). Its sources are compiled into the executable, so there is no library or DLL to build, ship, or install.
- A C++17 compiler (MSVC, GCC, or Clang).
- CMake >= 3.15.
- Git (to fetch the stack submodule).
- C++ compiler - install Visual Studio Community (free) and select the "Desktop development with C++" workload.
- CMake - from https://cmake.org/download/, or
winget install Kitware.CMake.
- Debian/Ubuntu:
sudo apt install build-essential cmake git - macOS:
xcode-select --installandbrew install cmake
CMake only, and the same two commands on every platform:
git clone --recursive https://github.com/chipkin/BACnetProfileExample-B-DAP-CPP.git
cd BACnetProfileExample-B-DAP-CPP
cmake -B build -S .
cmake --build build --config ReleaseAlready cloned without --recursive? Run git submodule update --init --recursive
first - the build needs the stack submodule.
The first build takes a few minutes - it compiles the entire CAS BACnet Stack (~600 source files) into the executable. Rebuilds after that are incremental and take seconds.
If your CAS BACnet Stack lives somewhere other than the bundled submodule, point
CMake at it: cmake -B build -S . -D CAS_STACK_DIR=/path/to/cas-bacnet-stack.
# Linux / macOS
./build/BACnetExampleBDAP
# Windows
.\build\Release\BACnetExampleBDAP.exeExpected output:
BACnet B-DAP (Device Address Proxy) Example - C++ v1.1.0
CAS BACnet Stack version: 6.0.23.0
Common helper (common/) version: 3.4.1
FYI: Listening for BACnet/IP on UDP port 47808 (Network Port 1).
TX 21 bytes to 192.168.3.255:47808 (broadcast) (Network Port 1) - Unconfirmed: I-Am
FYI: Device 389021 ("Chipkin Example B-DAP") ready. Vendor ID 389. Press 'h' for help.
The TX line is the start-up I-Am the device broadcasts to announce itself. It
goes to the local subnet broadcast address (here 192.168.3.255, computed
from the Network Port's interface), not the global 255.255.255.255. As clients
talk to the device you'll see RX ... bytes from ... and TX ... bytes to ...
lines showing the traffic; a WriteProperty to an output also prints a line such
as WriteProperty: Analog Output 1 (Chartreuse) <- 42.50 @ priority 8.
The device listens on UDP 47808 (BACnet/IP). Allow that port through your
firewall. To use a different port, pass --port (see below).
A wall of red
Error:lines at start-up is expected and is not your bug - it is the stack's own debug logging (the device hearing its own broadcast I-Am, and a one-time BACnet/SC UUID notice). TUTORIAL.md explains both.
| Option | Default | Meaning |
|---|---|---|
--port <n> |
47808 |
UDP port to listen on (BACnet/IP). |
--deviceID <n> |
389021 |
The device's BACnet instance number (BACnet requires this to be configurable). |
--help, -h |
- | Show usage and exit. |
--version |
- | Print the example, stack, and common/ helper versions, then exit. |
While the example runs, these keys are available:
| Key | Action |
|---|---|
h |
Show the version information and this command list. |
q |
Quit. |
| up arrow | Increase Analog Input 1 (Bronze) by 1.1. |
| down arrow | Decrease Analog Input 1 (Bronze) by 1.1. |
The up/down keys change the live Present_Value of the analog input, so a client
re-reading it sees the new value.
Use a BACnet client such as the CAS BACnet Explorer:
- Discover - send a Who-Is. The device replies with I-Am from instance 389021 (vendor 389). It also broadcasts an I-Am at start-up.
- Browse the object model - the device shows eight objects: the Device
(
Chipkin Example B-DAP), three inputs, three outputs, and the Network Port (Vermilion). Reading the Device'sObject_Listreturns all eight. - Read the Device - ReadProperty
389021->Object_Namereturns"Chipkin Example B-DAP";Protocol_Revisionreturns30;Descriptionreturns the profile description string. - Read a sensor - ReadProperty Analog Input
1->Present_Valuereturns21.5;Unitsreturnsdegrees-Celsius;Object_Namereturns"Bronze". - Command an output (the DS-WP-B test) - ReadProperty Analog Output
1->Present_Valuereturns20.0(itsRelinquish_Default, nothing commanded yet). Now WriteProperty Analog Output1Present_Value=42.5at priority8. Re-readPresent_Value: it returns42.5, andPriority_Array[8]shows42.5. WritePropertyPresent_Value=NULLat priority8to relinquish;Present_Valuereturns to20.0. Repeat for Binary Output1("Fuchsia", active/inactive) and Multi-State Output1("Indigo", state 1..3). - Confirm the profile boundary - a WriteProperty to a read-only input (e.g. Analog Input 1) is rejected. That is correct: inputs are sensors.
- Discover by object (DM-DOB-B) - send a Who-Has naming Analog Input 1
(
"Bronze"). The device replies with I-Have.Protocol_Services_Supportedread from the Device confirmswho-hasandi-haveare both enabled. - DM-DAP-B is not exercised here - this example does not answer Who-Is on behalf of any other device; see TODO.md for why.
For a property-by-property review against the conformance statement, see TUTORIAL.md.
Part of the checks above, automated: a Python BACnet client drives the running
example over BACnet/IP from outside it, the way a client discovers a device -
Who-Is, the Device's Object_List, then for every object its Property_List and
a ReadProperty of every property it lists. Every read must come back as a value;
an Error, Reject or Abort fails the run. Python 3 standard library only:
python3 tests/run_wire_tests.py --exe build/BACnetExampleBDAP # Linux/macOS
python tests\run_wire_tests.py --exe build\Release\BACnetExampleBDAP.exe # WindowsIt ends with ALL WIRE TESTS PASSED (exit 0). CI runs it on Linux after the
smoke test. tests/README.md says what each check proves. It
does not test the Profile Function: the device proxies nothing (see the Blocked
Example note at the top).
There is one example repository per standardized device profile in ASHRAE 135-2024 Annex L. An example marked ⛔ is blocked: the stack cannot yet perform that profile's function through its customer interface, and the example's README says what it does today. Pick the profile your device claims, then the language you build in. "Ask" means the example hasn't been built yet for that language - contact Chipkin if you need one.
| Profile | C++ | Node.js | C# | Rust | Python | Go |
|---|---|---|---|---|---|---|
| B-SS Smart Sensor | B-SS-CPP | B-SS-Node | B-SS-CS | B-SS-Rust | B-SS-Python | B-SS-Go |
| B-SA Smart Actuator | B-SA-CPP | Ask | Ask | Ask | Ask | Ask |
| B-ASC Application Specific Controller | B-ASC-CPP | B-ASC-Node | Ask | Ask | Ask | Ask |
| B-AAC Advanced Application Controller | B-AAC-CPP | Ask | Ask | Ask | Ask | Ask |
| B-BC Building Controller | B-BC-CPP | Ask | Ask | Ask | Ask | Ask |
| Profile | C++ | Node.js | C# | Rust | Python | Go |
|---|---|---|---|---|---|---|
| B-LSC Life Safety Controller | B-LSC-CPP 🚧 | Ask | Ask | Ask | Ask | Ask |
| B-ALSC Advanced Life Safety Controller | B-ALSC-CPP | Ask | Ask | Ask | Ask | Ask |
| Profile | C++ | Node.js | C# | Rust | Python | Go |
|---|---|---|---|---|---|---|
| B-ACC Access Control Controller | B-ACC-CPP | Ask | Ask | Ask | Ask | Ask |
| B-AACC Advanced Access Control Controller | B-AACC-CPP | Ask | Ask | Ask | Ask | Ask |
| Profile | C++ | Node.js | C# | Rust | Python | Go |
|---|---|---|---|---|---|---|
| B-LD Lighting Device | B-LD-CPP | Ask | Ask | Ask | Ask | Ask |
| B-LS Lighting Supervisor | B-LS-CPP | Ask | Ask | Ask | Ask | Ask |
| Profile | C++ | Node.js | C# | Rust | Python | Go |
|---|---|---|---|---|---|---|
| B-EM Elevator Monitor | B-EM-CPP | Ask | Ask | Ask | Ask | Ask |
| B-EC Elevator Controller | B-EC-CPP | Ask | Ask | Ask | Ask | Ask |
| B-AEC Advanced Elevator Controller | B-AEC-CPP | Ask | Ask | Ask | Ask | Ask |
| Profile | C++ | Node.js | C# | Rust | Python | Go |
|---|---|---|---|---|---|---|
| B-AS Authorization Server | B-AS-CPP | Ask | Ask | Ask | Ask | Ask |
| Profile | C++ | Node.js | C# | Rust | Python | Go |
|---|---|---|---|---|---|---|
| B-BBMD Broadcast Management Device | B-BBMD-CPP | Ask | Ask | Ask | Ask | Ask |
| B-ACDC Access Control Door Controller | B-ACDC-CPP | Ask | Ask | Ask | Ask | Ask |
| B-ACCR Access Control Credential Reader | B-ACCR-CPP ⛔ blocked on stack #3934 | Ask | Ask | Ask | Ask | Ask |
| B-RTR Router | B-RTR-CPP | Ask | Ask | Ask | Ask | Ask |
| B-GW Gateway | B-GW-CPP | Ask | Ask | Ask | Ask | Ask |
| B-DAP Device Address Proxy | B-DAP-CPP ⛔ blocked on stack #2032 | Ask | Ask | Ask | Ask | Ask |
| B-SCHUB BACnet/SC Hub | B-SCHUB-CPP | Ask | Ask | Ask | Ask | Ask |
| B-GENERAL General device (Annex L.8) | (satisfied by every example above) | — | — | — | — | — |
Client-side profiles.
| Profile | C++ | Node.js | C# | Rust | Python | Go |
|---|---|---|---|---|---|---|
| B-OD Operator Display | B-OD-CPP | Ask | Ask | Ask | Ask | Ask |
| B-OWS Operator Workstation | planned | — | — | — | — | — |
| B-AWS Advanced Operator Workstation | planned | — | — | — | — | — |
| B-XAWS Extended Advanced Operator Workstation | planned | — | — | — | — | — |
| B-LSAP Life Safety Annunciator Panel | planned | — | — | — | — | — |
| B-LSWS Life Safety Workstation | planned | — | — | — | — | — |
| B-ALSWS Advanced Life Safety Workstation | planned | — | — | — | — | — |
| B-ACSD Access Control Security Display | planned | — | — | — | — | — |
| B-ACWS Access Control Workstation | planned | — | — | — | — | — |
| B-AACWS Advanced Access Control Workstation | planned | — | — | — | — | — |
| B-LOD Lighting Operator Display | planned | — | — | — | — | — |
| B-ALWS Advanced Lighting Workstation | planned | — | — | — | — | — |
| B-LCS Lighting Control Station | planned | — | — | — | — | — |
| B-ALCS Advanced Lighting Control Station | planned | — | — | — | — | — |
| B-ED Elevator Display | planned | — | — | — | — | — |
| B-EWS Elevator Workstation | planned | — | — | — | — | — |
| B-AEWS Advanced Elevator Workstation | planned | — | — | — | — | — |
🚧 = in progress. ⛔ = Blocked Example (blocked on the named stack gap). "Ask" = not yet built for that language; contact Chipkin if you need it. Profile definitions: ANSI/ASHRAE 135-2024 Annex L. BIBB definitions: Annex K. Get the stack: https://store.chipkin.com/services/stacks/bacnet-stack.
- ANSI/ASHRAE Standard 135 (BACnet) - the protocol standard. Object model (Clause 12), services (Clause 15), BACnet/IP (Annex J), device profiles (Annex L). Purchase / preview via the ASHRAE store.
- What is BACnet? - Chipkin's introduction: https://docs.chipkin.com/protocols/bacnet/.
- CAS BACnet Stack - product page and documentation: https://store.chipkin.com/services/stacks/bacnet-stack.
- CAS BACnet Explorer - client for testing this device: https://store.chipkin.com/products/tools/cas-bacnet-explorer.
- B-SA (Smart Actuator) example - the profile this one's object model and service wiring mirror: https://github.com/chipkin/BACnetProfileExample-B-SA-CPP.
- Shared helper used by this example -
common/README.md.
See also TUTORIAL.md, docs/PICS.md, TODO.md, CHANGELOG.md, and AGENTS.md.