Skip to content

About

CAS BACnet Stack profile example: B-RTR (Router) — C++ (planned)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

BACnet B-RTR (Router) - C++ example

A minimal, copy-paste-friendly example showing how to implement the BACnet B-RTR (Router) device profile in C++ using the CAS BACnet Stack. It is a stand-alone BACnet router: it owns two BACnet/IP Network Port objects, one per network it joins, and forwards BACnet messages between them - a ReadProperty sent from network 1 to a device on network 2 crosses the router, and the answer comes back. It also answers ReadProperty / WriteProperty itself, edits a Group's member list with AddListElement / RemoveListElement, and is discoverable via Who-Is / I-Am. It is the series' canonical example for F-ROUTER and F-MULTIPORT (a device owning more than one Network Port).

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 @ 64b2cfac), at Protocol_Revision 30, with the vendored common/ helper at v3.4.0. Running the example prints all three - if what it prints disagrees with this line, trust the program and check CHANGELOG.md.

What is proven on the wire, and what is not yet. tests/ starts this router and a Python-simulated Peer device behind its second port, and checks: a ReadProperty from network 1 reaches the device on network 2 and the ComplexACK comes back with SNET = 2; a message for an unknown network gets Reject-Message-To-Network, after searching for it on the other port; Who-Is-Router-To-Network is answered on both ports; the start-up announcements are right for each port; a Who-Is from network 1 (DNET = 2 or DNET = 0xFFFF) is re-broadcast on network 2 and the device's I-Am comes back, so discovery works across the router; each Network Port's Routing_Table lists only the networks reached through a router beyond that port; AddListElement / RemoveListElement edit the Group. Not conformant yet: the answer to Who-Is-Router-To-Network is sent unicast (stack #3931); TODO.md says where it is fixed.

The device this example creates

Device 389018  "Chipkin Example B-RTR"   (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)
    +-- Group 1               "Olive"        List_Of_Group_Members  [Analog Input 1]  (AddListElement / RemoveListElement)
    +-- Network Port 1        "Vermilion"    BACnet/IP, network 1     (port A of the router)
    +-- Network Port 2        "Vermilion 2"  BACnet/IP, network 2     (port B of the router)

The three input objects and three output objects are the shared minimum/commandable pattern every example in this series carries. The Group gives AddListElement / RemoveListElement (DM-LM-B) a list to edit. The two Network Ports - each with its own network number - are this example's own contribution - see TUTORIAL.md for how main.cpp wires them up as F-MULTIPORT and configures the router (F-ROUTER).

What this example supports

The example implements exactly the capabilities below - and nothing more, which is the point of a profile example.

BIBBs (BACnet Interoperability Building Blocks)

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-LM-B Device Management - List Manipulation - B (AddListElement/RemoveListElement) ✅ on Group 1
NM-RC-B Network Management - Router Configuration - B ✅ forwards between networks 1 and 2 - see the notice above for what is not working yet

These are the B-RTR BIBBs of ANSI/ASHRAE 135-2024 Annex L.7 plus DM-LM-B. This example does not implement DeviceCommunicationControl (DM-DCC-B), alarming, scheduling, or trending; docs/PICS.md section 3 explains the DM-DCC-B decision.

Services (executed / B-side)

Service Notes
ReadProperty (12) Responds to property reads on both networks (DS-RP-B).
WriteProperty (15) Accepts writes to the commandable outputs' Present_Value (DS-WP-B).
AddListElement (8) / RemoveListElement (9) Edit Group 1's List_Of_Group_Members (DM-LM-B).
Who-Is / I-Am Answers Who-Is with I-Am; broadcasts I-Am on start-up on BOTH networks (DM-DDB-B).
Who-Has / I-Have Answers Who-Has with I-Have (DM-DOB-B).
Forwarding A message for network 2 arriving on network 1 (and back) is relayed, with SNET/SADR added (NM-RC-B).
Who-Is-Router-To-Network Answered with I-Am-Router-To-Network listing the networks reachable through the router, excluding those behind the port it was asked on (NM-RC-B).
Reject-Message-To-Network Sent to the originator of a message for a network the router cannot reach.
I-Am-Router-To-Network / Network-Number-Is Broadcast on both ports at start-up (and I-Am-Router-To-Network on the r key), each port with its own list and number (NM-RC-B).

Object types

Object type Instance Name Access
Device 389018 Chipkin Example B-RTR -
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)
Group 1 Olive List_Of_Group_Members editable with AddListElement / RemoveListElement
Network Port 1 Vermilion network 1
Network Port 2 Vermilion 2 network 2

Every required property of every object, and who answers it, is in docs/PICS.md.

Requires the CAS BACnet Stack (licensed product)

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.

What's in this repository

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 that prove the router forwards, rejects, announces and answers (see its README), and wiretest/ - the series' shared Python harness they are written against (vendored, like common/).
  • 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.

Prerequisites

  • A C++17 compiler (MSVC, GCC, or Clang).
  • CMake >= 3.15.
  • Git (to fetch the stack submodule).

Windows

Linux / macOS

  • Debian/Ubuntu: sudo apt install build-essential cmake git
  • macOS: xcode-select --install and brew install cmake

Build

CMake only, and the same two commands on every platform:

git clone --recursive https://github.com/chipkin/BACnetProfileExample-B-RTR-CPP.git
cd BACnetProfileExample-B-RTR-CPP

cmake -B build -S .
cmake --build build --config Release

Already 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.

Run

# Linux / macOS
./build/BACnetExampleBRTR --port 47808 --port2 47809

# Windows
.\build\Release\BACnetExampleBRTR.exe --port 47808 --port2 47809

Expected output:

BACnet B-RTR (Router) Example - C++ v1.1.0
CAS BACnet Stack version: 6.0.23.0
Common helper (common/) version: 3.4.0
FYI: Listening for BACnet/IP on UDP port 47808 (Network Port 1).
FYI: Listening for BACnet/IP on UDP port 47809 (Network Port 2).
TX 21 bytes to 192.168.3.255:47808 (broadcast) (Network Port 1) - Unconfirmed: I-Am
TX 21 bytes to 192.168.3.255:47809 (broadcast) (Network Port 2) - Unconfirmed: I-Am
TX 10 bytes to 192.168.3.255:47808 (broadcast) (Network Port 1) - Network-Layer-Message: Network-Number-Is
TX 10 bytes to 192.168.3.255:47809 (broadcast) (Network Port 2) - Network-Layer-Message: Network-Number-Is
TX 7 bytes to 192.168.3.255:47808 (broadcast) (Network Port 1) - Network-Layer-Message: Who-Is-Router-To-Network
TX 7 bytes to 192.168.3.255:47809 (broadcast) (Network Port 2) - Network-Layer-Message: Who-Is-Router-To-Network
FYI: Device 389018 ("Chipkin Example B-RTR") ready. Vendor ID 389. Routing network 1 (Vermilion, UDP 47808) <-> network 2 (Vermilion 2, UDP 47809). Press 'h' for help.
TX 11 bytes to 192.168.3.255:47808 (broadcast) (Network Port 1) - Network-Layer-Message: I-Am-Router-To-Network
TX 9 bytes to 192.168.3.255:47809 (broadcast) (Network Port 2) - Network-Layer-Message: I-Am-Router-To-Network

The two I-Am-Router-To-Network lines after ready are the stack's own start-up announcement, sent on the first BACnetStack_Tick() - networks 2 and 3 on Network Port 1, network 1 on Network Port 2.

The device listens on UDP 47808 and 47809 by default. Allow both ports through your firewall.

A few red Error: lines at start-up are expected and are not your bug - both ports hearing their own broadcasts, and a one-time BACnet/SC UUID notice. TUTORIAL.md explains them.

Command-line options

Option Default Meaning
--port <n> 47808 UDP port for Network Port 1 (Vermilion).
--port2 <n> --port + 1 UDP port for Network Port 2 (Vermilion 2). Must differ from --port.
--deviceID <n> 389018 The device's BACnet instance number.
--help, -h - Show usage and exit.
--version - Print the example, stack, and common/ helper versions, then exit.

Interactive commands

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.
r Re-send I-Am-Router-To-Network on both ports now.

Verify

Automated, on the wire - tests/ (Python 3 standard library only; Linux/macOS - on Windows only discovery_test.py, routing_table_test.py and list_element_test.py run, the others capture broadcasts). One command runs every Wire Test against the router you built; the device behind port 2 is simulated in Python by the tests:

python3 tests/run_wire_tests.py --exe build/BACnetExampleBRTR

They check discovery the way a client does it (Who-Is, then every property of every one of the router's ten objects reads as a value), forwarding (ReadProperty network 1 -> 2, ComplexACK back with SNET = 2), Reject-Message-To-Network for an unknown network after a Who-Is-Router-To-Network search on the other port, device discovery across the router (Who-Is with DNET = 2 and DNET = 0xFFFF re-broadcast on network 2, the device's I-Am back to network 1), Who-Is-Router-To-Network on both ports, the start-up I-Am-Router-To-Network / Network-Number-Is on each port, both ports' Network_Number, each port's Routing_Table, Network-Number-Is attributed to the port it arrived on, and AddListElement / RemoveListElement on the Group. Each check has been shown to fail with its feature removed (the table at the end of tests/README.md). [KNOWN GAP] lines are the items in TODO.md.

By hand - with a BACnet client such as the CAS BACnet Explorer:

  1. ReadProperty of Device Object_Name directly against each port (47808, 47809) returns "Chipkin Example B-RTR".
  2. Network Port 1 Network_Number = 1, Network Port 2 = 2, both Network_Number_Quality = configured.
  3. WriteProperty Analog Output 1 Present_Value = 25.5 at priority 8; re-read confirms 25.5.
  4. Run any BACnet/IP device on its own UDP port (another example from the series will do), and from network 1 read its Object_Name addressed DNET = 2, DADR = its IP address and UDP port. A client that discovers devices by a Who-Is for network 2 (or a global Who-Is) finds it through the router: the router re-broadcasts the Who-Is on network 2. The device must hear network 2's broadcasts - on one host, that means sharing Network Port 2's UDP port, which a real BACnet/IP device on its own host simply does.

For a property-by-property review against the conformance statement, see TUTORIAL.md.

The BACnet profile example series

The CAS BACnet Stack supports every standardized device profile in ASHRAE 135-2024 Annex L, and there is one example repository per profile. 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.

Controllers (Annex L.4)

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

Life safety controllers (Annex L.5)

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

Access control controllers (Annex L.6)

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

Lighting controllers (Annex L.11)

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

Elevator controllers (Annex L.13)

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

Authentication and authorization (Annex L.14)

Profile C++ Node.js C# Rust Python Go
B-AS Authorization Server B-AS-CPP Ask Ask Ask Ask Ask

Miscellaneous (Annex L.7, combinable with any one family)

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 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 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) — — — — —

Operator interfaces and workstations (Annex L.1–L.3, L.9–L.10, L.12)

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. "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.

References

See also TUTORIAL.md, docs/PICS.md, TODO.md, CHANGELOG.md, and AGENTS.md.

About

CAS BACnet Stack profile example: B-RTR (Router) — C++ (planned)

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages