Skip to content
Version 6.1.0
Contact Us

BACnet/SC

  • Specification reference: ANSI/ASHRAE 135-2024, Annex AB (BACnet/SC), Protocol Revision 30.

BACnet Secure Connect (BACnet/SC) carries BACnet messages over TLS-encrypted WebSocket connections instead of UDP. This page explains what it changes and how it works, and what the CAS BACnet Stack provides. Adding it to a product is covered on these pages:

  • Integration guide: the ten steps from loading the stack to running the main loop.
  • Certificates: the certificate File objects, certificate changes and CSR requests, and test certificates for the lab.
  • Transport callbacks: the WebSocket callbacks your application implements.
  • Operations and troubleshooting: the Network Port properties, heartbeats, reconnect and failover, common problems and questions.
  • Moving from BACnet/IP: adding BACnet/SC to a product that already runs BACnet/IP.
  • Reference: the property reference, compile flags and sizes, the API summary, a glossary and the conformance statement.

The problem with BACnet/IP in modern buildings

Section titled “The problem with BACnet/IP in modern buildings”

BACnet/IP has served the building automation industry well for over two decades. But it was designed for a different era, one where building controllers lived on isolated, air-gapped networks, safely separated from the enterprise and the internet.

That world no longer exists.

Today, HVAC controllers, energy meters, lighting systems and access control panels are connected to corporate networks, monitored from cloud dashboards and managed remotely. The attack surface has expanded dramatically, and the security assumptions built into BACnet/IP have not kept up.

BACnet/IP has no encryption, no authentication and no access control.

Any device on the same network segment can read and write BACnet objects. Any attacker who gains network access can impersonate a BACnet controller, inject false sensor readings or issue commands to physical equipment.

IT security teams know this. That is why BACnet/IP traffic is often blocked at the corporate firewall, why UDP port-forwarding requests from facilities teams are rejected, and why compliance audits flag unencrypted building automation traffic as a finding.

BACnet/SC (Secure Connect) is the ASHRAE standard’s answer to these problems. Defined in Annex AB of ANSI/ASHRAE 135-2024, it replaces the BACnet/IP transport while leaving the BACnet application layer unchanged.

Problem BACnet/IP BACnet/SC
Traffic is plaintext All messages readable by anyone on the network TLS 1.3 encrypts every connection
No device authentication Any device can send traffic and be trusted Every device presents an X.509 certificate; both ends verify each other
Broadcasts and BBMDs UDP broadcast needs BBMD configuration across subnets Hub-and-spoke topology: no UDP broadcast, no BBMD
Firewall and NAT hostile UDP port forwarding required, often blocked by IT Outbound TCP (secure WebSocket), which passes firewalls the way HTTPS does
No access control No concept of who may read or write Certificate-based identity makes policy enforcement possible
IT integration friction “BACnet is a security risk” The same kind of infrastructure as HTTPS, which IT teams understand

The key point: your BACnet services, objects and properties do not change. ReadProperty, WriteProperty, Who-Is, I-Am, COV subscriptions, alarms and schedules all work exactly as before. BACnet/SC changes only the transport underneath.

  • NIST SP 800-82 (the NIST guide to operational technology security) recommends protecting OT network traffic.
  • NERC CIP standards set security requirements for the electric sector’s OT systems.
  • Government and defence programmes such as FedRAMP and CMMC can bring the building systems of their facilities into scope.
  • Corporate security baselines at large enterprises (finance, healthcare, data centres) flag unencrypted building automation protocols.

Building automation manufacturers who cannot offer BACnet/SC are losing bids to competitors who can.

The biggest non-technical benefit of BACnet/SC is less friction with IT. WebSocket over TLS, the BACnet/SC transport, is the same protocol stack as HTTPS. IT departments that will not open a UDP port will approve an outbound HTTPS-style connection much more readily. BACnet/SC moves a product from “blocked at the firewall” to “approved by IT” without changing its building automation functionality.

BACnet/SC is part of the current standard, Annex AB of ANSI/ASHRAE 135-2024, which covers the BACnet/SC data link, the hub function, direct connections and certificate management. The CAS BACnet Stack implements it at Protocol Revision 30. Products built for BACnet/SC today interoperate with the building automation platforms, gateways and supervisory systems that adopt it.

BACnet/SC is the right choice if your customers ask questions like these:

  • “Can your BACnet traffic be encrypted?”
  • “How do we keep unauthorised devices off the BACnet network?”
  • “Our IT department will not open UDP ports. What are our options?”
  • “Does your product support BACnet Secure Connect?”
  • “We need to bring the building automation system into our enterprise security monitoring.”

If any of those applies, BACnet/SC is the path forward.

The CAS BACnet Stack includes a BACnet/SC implementation:

Capability Detail
Hub connector (node) Primary and failover hub, automatic reconnect
Hub function Accepts and forwards node connections
Direct connections Peer-to-peer, both initiating and accepting
Certificate management Certificate files and CSR requests over the network (File objects, Network Port commands); keys and TLS stay in your TLS library
Transport independence Works with the WebSocket and TLS library of your choice
Standard ANSI/ASHRAE 135-2024, Annex AB, Protocol Revision 30
Test coverage Unit tests for the ANSI/ASHRAE 135.1-2023 clause 12.5 BACnet/SC test cases

BACnet/SC can run alongside BACnet/IP on the same device, a migration path that adds secure connectivity while keeping compatibility with existing BACnet/IP installations.

This manual describes how to integrate BACnet Secure Connect (BACnet/SC) into a server-class device - a BACnet/SC node that connects outward to a hub, exposes BACnet objects (analog inputs, binary values, schedules, etc.), and serves read/write requests over the SC transport.

In scope:

  • Conceptual primer on BACnet/SC for developers familiar with BACnet/IP or MS/TP.
  • Step-by-step C/C++ integration of the CAS BACnet Stack BACnet/SC features.
  • The transport callback contract (WebSocket bridge) the application must implement.
  • The certificate model: operational certificate and issuer (CA) certificates - including the application-owned file-storage model.
  • A worked example creating a single Analog Input on an SC node.
  • A self-signed certificate appendix for lab testing.

Out of scope:

  • Implementing a BACnet/SC hub function (server that accepts node connections). The hub function is supported by the stack but is not the focus of this manual.
  • Direct connect (peer-to-peer) initiator/acceptor. Briefly mentioned where it interacts with this guide.
  • BACnet/SC routing between SC and BACnet/IP or MS/TP.
  • Choosing or implementing a specific WebSocket / TLS library. The stack is library-agnostic.
  • Production certificate authority (CA) administration. The CSR API is described; CA operations are vendor-specific.
  • Migrating an existing BACnet/IP device to add BACnet/SC alongside it. See Moving from BACnet/IP to BACnet/SC for that scenario.

BACnet/SC (Secure Connect) is a datalink layer for BACnet, defined in ANSI/ASHRAE 135-2024 Annex AB. It replaces the UDP-based BACnet/IP transport with WebSocket (RFC 6455) over TLS 1.3, providing:

  • Encryption of all BACnet traffic (TLS 1.3, RFC 8446).
  • Mutual authentication using X.509 v3 certificates.
  • Firewall- and NAT-friendly transport using TCP (typically port 443 or 4443).
  • Hub-and-spoke topology - no UDP broadcast, no BBMDs.

The BACnet application layer is unchanged. Every BACnet service (ReadProperty, WriteProperty, Who-Is, I-Am, SubscribeCOV, alarms, ReadRange, etc.) continues to work the same way. Only the bytes underneath the NPDU change.

Mental model: BACnet/SC is to BACnet/IP what HTTPS is to HTTP - same upper protocol, secured transport beneath.

BACnet/IP was designed for trusted, isolated networks; BACnet/SC was designed to run on ordinary IT infrastructure. What BACnet/SC solves, above, compares the two.

Hub and spoke: six devices each connect over TLS to a primary hub, with dashed backup connections to a failover hub. Two devices also have a direct connection to each other.

+------------------+
| Primary Hub |
| (Hub Function) |
+--+----+----+----+
/ | \
/ | \
+---v--+ +--v---+ +-v----+
| Node | | Node | | Node |
| A | | B | | C |
+------+ +------+ +------+
Optional: direct-connect peering between nodes
Node A <==========================> Node C

Every node opens an outbound wss:// connection to the hub. The hub forwards messages between connected nodes. Two nodes that need higher throughput or lower latency can optionally establish a peer-to-peer direct connect WebSocket, bypassing the hub.

2.4 Identity: Device UUID, VMAC, Device Instance

Section titled “2.4 Identity: Device UUID, VMAC, Device Instance”

A BACnet/SC node has three identifiers that are easy to confuse:

Identifier Size Lifetime Used for
Device UUID 16 bytes (RFC 4122) Permanent per physical device TLS / Connect-Request handshake; Device_UUID (507) on the Device object
VMAC (Virtual MAC) 6 bytes Per-network address Hub addressing; MAC_Address on the Network Port
Device Instance 22-bit (0–4 194 302) Per BACnet network Standard BACnet object identifier

The UUID is the persistent identity of the box. Generate it once at manufacturing time (or first boot) and store it in non-volatile memory. The VMAC addresses the device on a given SC network. Each BACnet/SC port has one VMAC, used on its hub connection, by its hub function and on its direct connections. If you do not configure one, it is derived from the UUID (its last six octets, in the Random-48 form), so it is the same on every boot. The device instance is the same concept you already know from BACnet/IP.

WebSocket states: unknown 0 moves to connecting 1 when the stack calls the initiate callback. From connecting, you report 2 to reach connected, or 4 to reach error. From connected, you report 3 to reach disconnected. From disconnected or error, the stack asks again, with backoff. If you never report connected, nothing starts.

+------------------+
| Idle / cold |
+--------+---------+
| application configured + Tick() running
v
+--------+---------+ TLS handshake fails
| TLS dial +---------+
+--------+---------+ |
| TLS OK |
v v
+--------+---------+ +-----+-----+
| WS Upgrade | | Error / |
+--------+---------+ | Reconnect |
| WS open | delay |
v +-----+-----+
+--------+---------+ |
| Connect-Request |<--------+
+--------+---------+
| Connect-Accept
v
+--------+---------+
| OPERATIONAL |
| (Encap-NPDU, |
| Heartbeats) |
+--------+---------+
| error / Disconnect-Request
v
reconnect with backoff

The CAS BACnet Stack drives this state machine internally. Your application is responsible only for the WebSocket transport (open / send / receive / close) and for telling the stack the WebSocket connect/disconnect events through notification calls.

BACnet/SC defines its own BVLC (BACnet Virtual Link Control) message set. Each BVLC message is sent as a single WebSocket binary frame.

Function Hex Direction Purpose
BVLC-Result 0x00 both ACK / NAK for control messages
Encapsulated-NPDU 0x01 both Carries the BACnet NPDU
Address-Resolution 0x02 node→node (relayed by the hub), or node→hub Resolve VMAC to direct-connect URI
Address-Resolution-ACK 0x03 resolved node→requester Resolution response, sent by the node whose VMAC was resolved (the hub answers only for itself)
Advertisement 0x04 both Announces the sender’s capabilities (sent unsolicited, or in answer to a solicitation)
Advertisement-Solicitation 0x05 both Asks the peer for its Advertisement
Connect-Request 0x06 initiator→peer Begin SC session (node to hub, or node to node for a direct connection)
Connect-Accept 0x07 peer→initiator Confirm SC session
Disconnect-Request 0x08 both Begin graceful close
Disconnect-ACK 0x09 both Confirm close
Heartbeat-Request 0x0A both Liveness check
Heartbeat-ACK 0x0B both Liveness response
Proprietary-Message 0x0C both Vendor-specific

You do not encode/decode BVLC by hand. The stack builds and parses every BVLC-SC message, control messages (Connect-Request, heartbeats, Advertisements and the rest) as well as Encapsulated-NPDU. Your code moves whole BVLC-SC messages, as opaque bytes, between the WebSocket and the existing RegisterCallbackSendMessageForPort / RegisterCallbackReceiveMessageForPort callbacks (one message per WebSocket binary frame), plus a small set of SC-specific transport callbacks. It neither parses nor builds NPDUs.

BACnet/SC uses TLS 1.3 with mutual authentication. Each device needs:

  1. An operational certificate - the device’s X.509 v3 leaf certificate.
  2. Issuer certificates - the CA chain (typically root + intermediate, exactly two).
  3. A private key - never exposed as a BACnet property.

The CAS BACnet Stack does not generate or store private keys, and it does not perform TLS itself - that lives in your TLS/WebSocket library. The stack also does not store the certificate bytes: it tracks which File objects hold the operational, issuer, and CSR certificates and exposes those references through the SC Network Port properties (Operational_Certificate_File, Issuer_Certificate_Files, Certificate_Signing_Request_File), but the actual PEM bytes live in your application and are served on demand through RegisterCallbackReadFile / RegisterCallbackWriteFile. It also handles the BACnet requests for cert updates: AtomicWriteFile to deliver the new bytes, and ReinitializeDevice to activate them. A CSR is requested through the Network Port command callback (BACnetStack_RegisterCallbackNetworkPortCommand, §16.6); the stack has no certificate-validation callback.

3. Roles, Topology, and What This Manual Covers

Section titled “3. Roles, Topology, and What This Manual Covers”

The BACnet/SC standard defines four roles. This manual covers role 1 (the node) in depth.

Role Description Required for a server device?
1. Hub Connector (node) Outbound wss:// client to the hub. Carries this device’s BACnet traffic. Yes - minimum requirement.
2. Hub Function Listens for inbound wss:// connections from nodes; forwards traffic. No - only if the device is the project hub.
3. Direct Connect Initiator Opens a peer-to-peer wss:// to another node. Optional.
4. Direct Connect Acceptor Listens for peer-to-peer wss:// connections. Optional.

A typical BACnet/SC server device implements role 1 only.

4. Architecture: Where the Stack Ends and Your Code Begins

Section titled “4. Architecture: Where the Stack Ends and Your Code Begins”

The CAS BACnet Stack does not open WebSockets, perform TLS, or handle TCP sockets. The stack is transport-agnostic by design. It also does not hold certificate bytes in its own storage - see §2.7. The application owns:

  • The WebSocket library (e.g., libwebsockets, Boost.Beast, uWebSockets, mongoose).
  • The TLS library (e.g., OpenSSL, mbedTLS, wolfSSL, BoringSSL).
  • Certificate bytes and file I/O on disk / flash.
  • Threading, sleep, polling.

The contract between your application and the stack is a small set of callbacks and notification functions:

+-----------------------+ +----------------------------------+
| Application | | CAS BACnet Stack (DLL/static) |
| (your code) | | |
| | | - Device & object database |
| WebSocket library | <-----> | - BACnet APDU/NPDU encode/dec |
| TLS library | CBs | - BACnet/SC BVLC + state mach. |
| Cert bytes on disk | | - Hub connector state machine |
| Main loop / Tick | | - Heartbeat + reconnect timing |
+-----------------------+ +----------------------------------+
Direction of calls:
Stack -> App : Initiate/Disconnect WebSocket, Send raw bytes,
Read property values, Read/Write File bytes,
Network Port command (DISCARD_CHANGES, GENERATE_CSR_FILE)
App -> Stack : Tick(), SetBACnetSCWebSocketStatus(Connected/Disconnected/Error),
Receive raw bytes (via callback return)

Two consequences flow from this design:

  1. The stack will tell you to open a connection, but you decide how. Your handler reads a URI string and calls into your WebSocket library. The actual TLS negotiation, certificate selection, and SNI handling are all yours.
  2. The stack does not “know” a connection succeeded until you tell it via BACnetStack_SetBACnetSCWebSocketStatus. If you forget this notification, the SC handshake will never start and the node will appear hung.

Chipkin has more than 25 years of BACnet experience and has helped hundreds of companies integrate BACnet into their products.