Breaking changes from version 5 to version 6
Upgrade in this order
Section titled “Upgrade in this order”- Sections 1 to 6 and the checklist: the C API changes every application needs.
- Changes by feature, for each feature you use: data links, Network Port objects, BBMD, MS/TP, BACnet/SC, virtual devices and the rest.
- Decoded XML and JSON output, if you parse decoded output.
This document lists everything you need to change in your application to move it from version 5 to version 6 of the CAS BACnet Stack.
Each section tells you what changed, why, and what to do about it. If you would rather work from the failure backwards, start with symptom → fix and follow the links from there.
Your device’s BACnet traffic is unchanged, apart from the standards-conformance corrections in section 5. What changed is the C API you call, the text the decode helpers produce, and a few return types.
Contents
Section titled “Contents”- Object Access Send API
- Renamed and removed functions
- Return-convention changes
- The
bool enabledparameter onSet*Enabled - Behavioural conformance changes
- Decode output (
DecodeAsXML/DecodeAsJSON) - Changes by feature
- Additions (non-breaking)
- Symptom → fix
- Migration checklist
1. Object Access Send API
Section titled “1. Object Access Send API”Version 6 removes the stateful Build* / Clear* / Send* three-step
pattern and the *Async suffix for ReadProperty, WriteProperty, and CreateObject. Each
service is now a single, stateless call — no builder state is held between one call and the
next — taking a hand-packed little-endian propertyArrays byte buffer plus a BACnet
confirmed-service-choice serviceType selector. (“Stateless” here is not a concurrency
guarantee — the stack has no internal synchronization and must be driven from a single
thread; see Lifecycle and threading.)
1.1 Removed functions → replacement
Section titled “1.1 Removed functions → replacement”| Removed (v5) | Replacement (v6) |
|---|---|
BACnetStack_BuildReadProperty |
Pack a 15-byte read element yourself; call BACnetStack_SendReadProperty |
BACnetStack_ClearReadProperty |
No longer needed — no global state |
BACnetStack_SendReadPropertyAsync |
BACnetStack_SendReadProperty(..., serviceType, ...) |
BACnetStack_SendReadPropertySingleAsync |
BACnetStack_SendReadProperty(..., serviceType=12, ...) |
BACnetStack_SendReadPropertyMultipleAsync |
BACnetStack_SendReadProperty(..., serviceType=14, ...) |
BACnetStack_BuildWriteProperty |
Pack a 19-byte + value write element; call BACnetStack_SendWriteProperty |
BACnetStack_ClearWriteProperty |
No longer needed |
BACnetStack_SendWritePropertyAsync |
BACnetStack_SendWriteProperty(..., serviceType, ...) |
BACnetStack_BuildCreateObjectInitialPropertyValues |
Pack into propertyArrays (13-byte + value element, value in binary) |
BACnetStack_ClearCreateObjectInitialPropertyValues |
No longer needed |
BACnetStack_SendCreateObjectByType |
BACnetStack_SendCreateObject(..., flags=0x00, ...) |
BACnetStack_SendCreateObjectByIdentifier |
BACnetStack_SendCreateObject(..., flags=0x01, objectInstance, ...) |
BACnetStack_SendReadProperty and BACnetStack_SendWriteProperty survive by name but
changed signature — they now take serviceType, propertyArraysCount, propertyArrays
and propertyArraysLength (the total byte length of propertyArrays), and their networkType
becomes a networkPortInstance (below).
Existing calls will not compile.
1.2 The serviceType selector
Section titled “1.2 The serviceType selector”serviceType |
Service |
|---|---|
12 |
ReadProperty |
14 |
ReadPropertyMultiple |
15 |
WriteProperty |
16 |
WritePropertyMultiple |
For ReadProperty (12) and WriteProperty (15), propertyArraysCount must be 1. For the
Multiple forms (14 / 16) it is the number of packed elements.
WritePropertyMultiple now works. serviceType=16 builds a real WPM-Request, one
write-access-specification per element. The v5 SendWritePropertyAsync rejected
count > 1.
1.3 propertyArrays wire layouts
Section titled “1.3 propertyArrays wire layouts”All multi-byte fields are little-endian regardless of host byte order.
- Read element — 15 bytes:
flags(1) objectType(2) objectInstance(4) propertyIdentifier(4) propertyArrayIndex(4)—flagsbit0 = use array index. - Write element — 19 bytes + value: the read layout plus
dataType(1) priority(1) valueLength(2) value(n)—flagsbit0 = use array index, bit1 = use priority. - CreateObject initial-value element — 13 bytes + value:
flags(1) propertyIdentifier(4) propertyArrayIndex(4) dataType(1) priority(1) valueLength(2) value(n)—flagsbit0 = use array index, bit1 = use priority; the other bits must be 0.
Write element values are text. In a write element, value is the same string form the v5
Build* functions took: unsigned "42", real "3.14", boolean "1" / "0", octet-string
"x0a1b", date "yyyy-mm-dd (w)", time "HH:MM:SS.hh", object-identifier
"<objectType>,<objectInstance>", DateTime "<date>;<time>". The doc block of
BACnetStack_SendWriteProperty in CASBACnetStackDLL.h lists every form.
CreateObject element values are binary. In a CreateObject element, value is the value’s raw
binary form, not text, with multi-byte numbers little-endian: Boolean 1 byte, Unsigned and
Enumerated 4 bytes, Real 4 bytes (IEEE-754), Double 8 bytes, a CharacterString as its UTF-8 bytes, Date and
Time 4 bytes each, an object identifier as objectType(2) objectInstance(4). A Real 72.5 written to
Present_Value (85), with no array index and no priority, is the 17 bytes
00 55000000 00000000 04 00 0400 00009142. A value left in the v5 text form usually fails to decode and the
send returns false, but not always: a text value whose length happens to equal the binary width of its type
is accepted and misread. Re-encode every CreateObject call. The doc block of BACnetStack_SendCreateObject
lists every form.
Datatype restriction. The dataType byte must be a BACnet primitive application tag
0..12 (Null through BACnetObjectIdentifier), with these additions: a write element also accepts
27 (DateTime) and 64 (DeviceObjectPropertyReference), and a CreateObject element also accepts 27
(DateTime). Any other tag is rejected and the send returns false. The v5 Build* path accepted
other complex tags; those writes are no longer supported through this API.
1.4 Before / after (C)
Section titled “1.4 Before / after (C)”ReadProperty
// ---- v5 ----BACnetStack_BuildReadProperty(objectType, objectInstance, propertyId, /*useIndex*/ false, /*idx*/ 0);BACnetStack_SendReadProperty(&invokeId, conn, connLen, netType, destNet, destAddr, destAddrLen);
// ---- v6 ---- pack one 15-byte read element, serviceType 12uint8_t e[15] = {0};e[0] = 0x00; // flags: bit0 = use array indexmemcpy(e + 1, &objectType, 2);memcpy(e + 3, &objectInstance, 4);memcpy(e + 7, &propertyId, 4);memcpy(e + 11, &arrayIndex, 4); // ignored unless flags bit0 setBACnetStack_SendReadProperty(&invokeId, /*serviceType*/ 12, conn, connLen, portInstance, destNet, destAddr, destAddrLen, /*count*/ 1, e, sizeof(e));portInstance is the Network Port object instance, or BACNET_NETWORK_PORT_DEFAULT on a
single-port device — see
networkType → networkPortInstance.
ReadPropertyMultiple — concatenate N 15-byte elements, pass serviceType = 14,
count = N and propertyArraysLength = 15 × N.
WriteProperty
// ---- v5 ----BACnetStack_BuildWriteProperty(/*datatype*/ 2, "42", 2, objectType, objectInstance, propertyId, false, 0, /*usePriority*/ false, 0);BACnetStack_SendWriteProperty(&invokeId, conn, connLen, netType, destNet, destAddr, destAddrLen);
// ---- v6 ---- pack one 19+value write element, serviceType 15const char* val = "42";uint16_t valLen = 2;uint8_t e[19 + 2] = {0};e[0] = 0x00; // flags: bit0 = array index, bit1 = prioritymemcpy(e + 1, &objectType, 2);memcpy(e + 3, &objectInstance, 4);memcpy(e + 7, &propertyId, 4);memcpy(e + 11, &arrayIndex, 4);e[15] = 2; // dataType: UnsignedIntegere[16] = 0; // prioritymemcpy(e + 17, &valLen, 2);memcpy(e + 19, val, valLen);BACnetStack_SendWriteProperty(&invokeId, /*serviceType*/ 15, conn, connLen, portInstance, destNet, destAddr, destAddrLen, /*count*/ 1, e, sizeof(e));WritePropertyMultiple — concatenate N write elements, pass serviceType = 16,
count = N, and propertyArraysLength = total bytes. There was no working v5 equivalent.
CreateObject
// ---- v5 ----BACnetStack_BuildCreateObjectInitialPropertyValues(propertyId, /*datatype*/ 7, "MyName", 6);BACnetStack_SendCreateObjectByType(&invokeId, objectType, conn, connLen, netType, destNet, destAddr, destAddrLen);
// ---- v6 ---- pack initial values (13 + value each, value in binary);// the memcpy calls assume a little-endian host. flags 0x00 = by typeconst char* nm = "MyName";uint16_t nmLen = 6;uint8_t iv[13 + 6] = {0};iv[0] = 0x00; // element flags: no array index, no prioritymemcpy(iv + 1, &propertyId, 4); // propertyIdentifier // iv[5..8]: propertyArrayIndex, unused (0)iv[9] = 7; // dataType: CharacterStringiv[10] = 0; // priority, unusedmemcpy(iv + 11, &nmLen, 2); // valueLengthmemcpy(iv + 13, nm, nmLen); // value: the UTF-8 bytesBACnetStack_SendCreateObject(&invokeId, objectType, /*flags*/ 0x00, /*objectInstance*/ 0, conn, connLen, portInstance, destNet, destAddr, destAddrLen, /*count*/ 1, iv, sizeof(iv));// By identifier: flags = 0x01 and pass the desired objectInstance.2. Renamed and removed functions
Section titled “2. Renamed and removed functions”A consistency pass over the public DLL surface. Version 6 is a hard break —
there are no deprecation shims. The canonical form is
BACnetStack_<Verb><Noun>[With<Selector>], with verbs drawn from a fixed set
(Add, Remove, Update, Set, Get, Send, Build, Register, Reset).
| v5 | v6 | Why |
|---|---|---|
BACnetStack_SendDeviceCommunicatonControl |
BACnetStack_SendDeviceCommunicationControl |
Spelling typo; the parameters also changed — see below |
BACnetStack_ValueUpdated |
BACnetStack_UpdateValue |
Verb/noun order |
BACnetStack_ClearForeignDeviceTable |
BACnetStack_ResetForeignDeviceTable |
Clear* → Reset* |
BACnetStack_ClearBroadcastDistributionTable |
BACnetStack_ResetBroadcastDistributionTable |
Clear* → Reset* |
BACnetStack_AddNetworkPortObjectWithNetworkNumber |
BACnetStack_AddNetworkPortObject |
Merged: AddNetworkPortObject now always takes networkNumber and networkNumberQuality — see below |
BACnetStack_EnableSegmentation |
BACnetStack_SetSegmentationEnabled |
Enable* → Set*Enabled; see §4 |
BACnetStack_EnableBackupAndRestore |
BACnetStack_SetBackupAndRestoreEnabled |
As above |
BACnetStack_EnableAlarmsAndEventsForObject |
BACnetStack_SetAlarmsAndEventsForObjectEnabled |
As above |
BACnetStack_SendReadRangeByPosition |
BACnetStack_SendReadRangeWithPosition |
Selector vocabulary |
BACnetStack_SendReadRangeBySequenceNumber |
BACnetStack_SendReadRangeWithSequenceNumber |
Selector vocabulary |
BACnetStack_SendReadRangeByTime |
BACnetStack_SendReadRangeWithTime |
Selector vocabulary |
BACnetStack_DecodeAsXML / BACnetStack_DecodeAsJSON |
BACnetStack_DecodeAs |
Merged: the format is a new leading decodeType (BACnetDecodeType_XML / BACnetDecodeType_JSON); the other five parameters keep their order and types, and the last one is still the transport type (IP, MS/TP, BACnet/SC), not a Network Port instance. See the decode-output guide |
BACnetStack_RegisterCallbackReceiveMessage / BACnetStack_RegisterCallbackSendMessage |
BACnetStack_RegisterCallbackReceiveMessageForPort / BACnetStack_RegisterCallbackSendMessageForPort |
The callback’s networkType becomes a Network Port instance — see below |
All 13 RegisterHookProperty* functions are REMOVED
Section titled “All 13 RegisterHookProperty* functions are REMOVED”They are not renamed. There is no replacement symbol to migrate to:
BitString, Bool, CharString, Date, Double, Enum, Int, Null,
ObjectIdentifier, OctString, Real, Time, UInt — and their matching FP… callback
typedefs.
Migration: these were per-datatype observation hooks. Decode the raw inbound frame instead —
BACnetStack_DecodeAs() gives you the whole PDU, including the property values these hooks used to
surface one datatype at a time. If you were using a hook to drive application logic rather than to
observe, the property callbacks (RegisterCallbackGetProperty* / SetProperty*) are the supported
route. The SetProperty* callbacks are unchanged; the GetProperty* callbacks gain a trailing
errorCode — see below.
The eight non-property hooks (RegisterHookIAm, _IHave, _TextMessage, _Error, _Reject,
_Abort, _SimpleAck, _Timeout) remain available. Their one change is that the hook receives a
uint32_t networkPortInstance where it received a uint8_t networkType — see
below.
More removals
Section titled “More removals”| Removed | Replacement |
|---|---|
BACnetStack_SetObjectTypeSupported() |
Removed with no direct replacement — BACnetStack_SetObjectTypeCreatable() now implies supported |
BACnetStack_SetFileObjectStreamAccessContents() |
Removed in 5.3.6, so a 5.7.1 build no longer has it either: the stack no longer holds file contents; serve them through the read callback (§5.3.1) |
BACnetStack_SetMaxOutstandingConfirmedRequests() |
Removed with no replacement. Added in 5.7.0, it capped how many confirmed requests could be outstanding at once, to bound memory use with slow or unresponsive peers. Delete the call; if you relied on the cap, limit outstanding requests in your application |
BACnetStack_SetAllowIndefiniteCOVLifetime() |
Removed with no replacement. Added in 5.7.0, it refused never-expiring COV subscriptions so that every subscription eventually expired. Delete the call; version 6 accepts these subscriptions and has no setting to refuse them |
One changed signature
Section titled “One changed signature”BACnetStack_SendUnsubscribedUnconfirmedCOVNotification() changed in four ways, so every existing call has to
be rewritten:
subscriberProcessIdentifierandtimeRemainingare new parameters (afterinitiatingDeviceInstanceand after the monitored object). Both were hardwired to 0; pass0for both to keep the old behaviour.- A packed property list replaces the single
propertyIdentifier.properties,propertyCountandpropertiesLengthtakepropertyCountelements of 9 bytes each, little-endian:flags(1) propertyIdentifier(4) propertyArrayIndex(4), withflagsbit0 = use array index. All of them name properties of the monitored object, so one notification can carry Present_Value and Status_Flags as clause 13.1 expects. A one-element list keeps the old behaviour. broadcastis new. 135-2024 cl. 13.1 makes broadcast the configured-recipient delivery mode for an unsubscribed COV notification, and the previous signature hardcoded unicast, so aCOVU_Recipientsentry configured as a broadcast address could not be served at all. In the C export it sits afternetworkPortInstance, not at the end; the C++ overload and the Node.js wrapper default it tofalse. Passfalseto keep the v5 behaviour.networkTypebecomesnetworkPortInstance, as on every send function (below).
The full parameter list is in the doc block of the export in CASBACnetStackDLL.h.
BBMD, foreign device, Network Port, routing and DCC signatures
Section titled “BBMD, foreign device, Network Port, routing and DCC signatures”Each of these changed its parameter list, so an existing call will not compile. The common
thread is that version 6 supports more than one Network Port: the functions that act on a
BACnet/IP port’s BBMD tables, or send on a port, now name the port by its Network Port object
instance (a uint32_t networkPortInstance) instead of a uint8_t networkType.
| Function | v5 | v6 |
|---|---|---|
BACnetStack_SendRegisterForeignDevice |
(timeToLive, connectionString, connectionStringLength) |
networkPortInstance added first |
BACnetStack_SendReadBroadcastDistributionTable |
(connectionString, connectionStringLength) |
networkPortInstance added first |
BACnetStack_SendReadForeignDeviceTable |
(connectionString, connectionStringLength) |
networkPortInstance added first |
BACnetStack_GetBDTEntry |
bool (index, ipAddress, ipAddressLength, subnetMask, subnetMaskLength) |
networkPortInstance added first; returns uint32_t — see §3 |
BACnetStack_GetFDTEntry |
bool (index, ipAddress, ipAddressLength, timeToLive, timeRemaining) |
networkPortInstance added first; returns uint32_t — see §3 |
BACnetStack_AddBDTEntry |
(ipAddress, ipAddressLength, subnetMask, subnetMaskLength) |
networkPortInstance added first. Pass exactly 6 address bytes and a 4-byte mask: any other length makes the call return false |
BACnetStack_ClearForeignDeviceTable / _ClearBroadcastDistributionTable |
() |
Renamed Reset* (table above); still no parameters |
BACnetStack_AddNetworkPortObject |
(deviceInstance, uint16_t objectInstance, networkType, protocolLevel, referencePort) |
(deviceInstance, uint32_t objectInstance, networkType, protocolLevel, uint16_t networkNumber, uint8_t networkNumberQuality, referencePort) — the two new parameters sit before referencePort, and objectInstance widens to uint32_t. This is the v5 AddNetworkPortObjectWithNetworkNumber parameter order under the shorter name |
BACnetStack_SendDeviceCommunicatonControl → BACnetStack_SendDeviceCommunicationControl |
…, connectionStringLength, uint8_t networkType, destinationNetwork, …, destinationAddressLength) |
Spelling fixed; networkType → uint32_t networkPortInstance; new trailing uint8_t passwordEncoding (0 = ANSI X3.4/UTF-8) |
BACnetStack_SendIAmRouterToNetwork |
(connectionString, connectionStringLength, uint8_t networkType, broadcast, destinationNetwork, destinationAddress, destinationAddressLength) |
(const uint16_t* networkNumbers, uint32_t networkNumberCount, connectionString, connectionStringLength, uint32_t networkPortInstance, broadcast, destinationNetwork, destinationAddress, destinationAddressLength) — pass NULL, 0 to announce the device’s full routing table, or a list to announce exactly those networks |
Migration: pass the instance of the IPv4 Network Port object you added with
BACnetStack_AddNetworkPortObject to the BBMD and foreign-device functions. Where a
networkPortInstance replaces networkType, pass that instance, or
BACNET_NETWORK_PORT_DEFAULT (4194303) for the default/single port. For
AddNetworkPortObject, pass the network number and quality your v5 code passed to
AddNetworkPortObjectWithNetworkNumber, or the values for your network.
Parameter lists not changed: BACnetStack_SetBBMD already took (deviceInstance, networkPortInstance)
in version 5. BACnetStack_DeleteBDTEntry, BACnetStack_DeleteFDTEntry and the two Reset* table
functions (renamed from Clear*, above) keep their version 5 parameters and take no
networkPortInstance. The two Delete* functions act on the stack’s first BACnet/IP data link (the
one claimed first, not necessarily the first Network Port object you added), as in version 5; the two
Reset* functions clear that table on every BACnet/IP port.
networkType → networkPortInstance on the send functions and hooks
Section titled “networkType → networkPortInstance on the send functions and hooks”In version 5 every function that sends a message, and every hook that reports one, carried a
const uint8_t networkType: a small transport selector (IP = 0, MS/TP = 1, BACnet/SC = 2). In
version 6 that parameter is a const uint32_t networkPortInstance in the same position: the
instance of the Network Port object to send on (for a hook, the port the message arrived on), as
created with BACnetStack_AddNetworkPortObject. The functions keep their names; for most of them
this one argument is the only change.
What to pass:
- A device that adds Network Port objects — the instance of the port to send on.
- A single-port device that never calls
AddNetworkPortObject—BACNET_NETWORK_PORT_DEFAULT(4194303, defined inCASBACnetStackDLL.h), which means “the default port for this transport”. 4194303 is the reserved unknown-instance value, so it can never be a real port’s instance. - A build with more than one data-link transport compiled in (for example BACnet/IP and
BACnet/SC) — a real instance. There
BACNET_NETWORK_PORT_DEFAULTis ambiguous: the send logs an error and returnsfalse.
Search for every call — the compiler will not find them for you. Widening uint8_t to
uint32_t is an implicit conversion, so a v5 call that passed 0 for IP still compiles. It now
names Network Port instance 0, and an instance that matches no Network Port object makes the call
return false with nothing sent.
The hooks receive the instance the same way: the callback you register with each of the eight
RegisterHook* functions listed below takes const uint32_t networkPortInstance where it took
const uint8_t networkType, so its function-pointer type no longer matches until you change it.
The 39 version 5 functions and callbacks whose networkType became networkPortInstance
Found by comparing every DllExport declaration in the 5.x and 6 headers. The first 17 changed
nothing else that a caller has to act on (three of them are renames, noted in the second column); the rest also changed in another way, linked from the
second column.
| v6 function or callback | Other changes |
|---|---|
BACnetStack_SendAtomicWriteFile |
None |
BACnetStack_SendCancelSubscribeCOV |
None |
BACnetStack_SendCancelSubscribeCOVProperty |
None |
BACnetStack_SendDeleteObject |
None |
BACnetStack_SendIAm |
None |
BACnetStack_SendNetworkNumberIs |
None |
BACnetStack_SendReadRange |
None |
BACnetStack_SendReadRangeWithPosition |
Renamed from …ByPosition (table above) |
BACnetStack_SendReadRangeWithSequenceNumber |
Renamed from …BySequenceNumber |
BACnetStack_SendReadRangeWithTime |
Renamed from …ByTime; two parameters renamed only (referenceYear → referenceYearMinus1900, referenceHundrethSecond → referenceHundredthSecond), same types and meaning |
BACnetStack_SendSubscribeCOV |
None |
BACnetStack_SendSubscribeCOVProperty |
None |
BACnetStack_SendTimeSynchronization |
Two parameters renamed only (year → yearMinus1900, hundrethSeconds → hundredthSecond), same types and meaning |
BACnetStack_SendUTCTimeSynchronization |
As SendTimeSynchronization |
BACnetStack_SendWhoHasWithObjectIdentifier |
None |
BACnetStack_SendWhoIs |
None |
BACnetStack_SendWhoIsWithLimits |
None |
BACnetStack_SendAtomicReadFile |
fileStart becomes signed — further changes |
BACnetStack_SendConfirmedTextMessage |
useNumericMessageClass and two encodings added — further changes |
BACnetStack_SendUnconfirmedTextMessage |
As SendConfirmedTextMessage |
BACnetStack_SendReinitializeDevice |
Trailing passwordEncoding added — further changes |
BACnetStack_SendWhoHasWithObjectName |
Trailing objectNameEncoding added — further changes |
BACnetStack_SendReadProperty |
Packed request buffer — §1 |
BACnetStack_SendWriteProperty |
Packed request buffer — §1 |
BACnetStack_SendCreateObject |
Replaces SendCreateObjectByType and …ByIdentifier (two v5 functions) — §1 |
BACnetStack_SendDeviceCommunicationControl |
Spelling and passwordEncoding — table above |
BACnetStack_SendIAmRouterToNetwork |
Network-number list added — table above |
BACnetStack_SendUnsubscribedUnconfirmedCOVNotification |
Property list, broadcast and more — One changed signature |
BACnetStack_RegisterHookAbort, _RegisterHookError, _RegisterHookIAm, _RegisterHookIHave, _RegisterHookReject, _RegisterHookSimpleAck, _RegisterHookTextMessage, _RegisterHookTimeout (8) |
The hook callback’s parameter only |
BACnetStack_RegisterCallbackReceiveMessageForPort / …SendMessageForPort (2) |
Renamed — below |
The other 20 version 5 exports that took a networkType are not in this list: the four *Async
sends were removed (§1), the 13 RegisterHookProperty* functions were
removed (above), DecodeAsXML / DecodeAsJSON
became DecodeAs, which still takes the transport type, and AddNetworkPortObject keeps its
networkType, which is the port’s BACnetNetworkType (ipv4 = 5, …), a different enumeration.
The send and receive message callbacks
Section titled “The send and receive message callbacks”BACnetStack_RegisterCallbackReceiveMessage and BACnetStack_RegisterCallbackSendMessage are
replaced by BACnetStack_RegisterCallbackReceiveMessageForPort and
BACnetStack_RegisterCallbackSendMessageForPort, which take callbacks of type
FPCallbackReceiveMessageForPort and FPCallbackSendMessageForPort. No parameter is added or
removed; in each, the transport selector becomes a Network Port instance:
| Callback | v5 parameter | v6 parameter |
|---|---|---|
| Receive (last parameter, an output) | uint8_t* networkType |
uint32_t* networkPortInstance |
Send (fifth parameter, before broadcast) |
const uint8_t networkType |
const uint32_t networkPortInstance |
The other parameters and the uint16_t return are unchanged.
Migration: rename the two registration calls and change the parameter in both callbacks.
- Receive: write the instance of the Network Port object the frame arrived on. The stack
presets it to
BACNET_NETWORK_PORT_DEFAULT, so a single-transport device that never sets it keeps working. A build with more than one data-link transport must report a real instance: with the default the stack cannot tell the transports apart, so it logs an error and drops the frame. - Send: choose the socket or serial port from the instance, not from an IP/MS/TP/SC value.
BACNET_NETWORK_PORT_DEFAULTmeans the default port for the transport.
Absolute times are CASBACnetTime, not time_t
Section titled “Absolute times are CASBACnetTime, not time_t”Version 6 has no time_t in any exported signature. Absolute times use CASBACnetTime, defined in
CASBACnetStackDLL.h as a fixed int64_t: signed seconds since the POSIX epoch
(1970-01-01T00:00:00Z), the same width on every platform.
| Function or callback | v5 | v6 |
|---|---|---|
BACnetStack_RegisterCallbackGetSystemTime |
callback returns time_t |
callback returns CASBACnetTime (FPCallbackGetSystemTime) |
BACnetStack_InsertTrendLogRecord / BACnetStack_InsertTrendLogMultipleRecord |
const time_t timestamp |
const CASBACnetTime timestamp |
BACnetStack_ReadTrendLogRecord / BACnetStack_ReadTrendLogMultipleRecord |
time_t* timestamp; returns bool |
CASBACnetTime* timestamp; returns uint32_t — see §3 |
What a host must change: declare the system-time callback as returning CASBACnetTime and
return the current time in seconds, for example return (CASBACnetTime)time(NULL);, and use a
CASBACnetTime variable for the trend-log timestamps. On 64-bit Windows and 64-bit Linux time_t
is already 8 bytes, so the value is unchanged and a callback declared as returning time_t may
still compile there; declare CASBACnetTime anyway so that it also builds on a 32-bit target. A
binding in another language (C#, Python, Java, Rust) must bind this type as a 64-bit signed integer.
Further changed signatures
Section titled “Further changed signatures”Found by comparing every DllExport declaration in the 5.x and 6 headers. Each of these changes
the parameter list, so an existing call (or, for a callback, the function-pointer type) will not
match.
| Function | v5 | v6 |
|---|---|---|
BACnetStack_RegisterCallbackGetListElevatorGroupLandingCallStatus (callback) |
…, char* floorText, const uint16_t floorTextMaxLength, uint16_t* floorTextLength, bool* more |
…, char* floorText, uint32_t* floorTextLength, const uint32_t floorTextMaxLength, bool* more — the two floor-text parameters swap places and both widen to 32 bits. The earlier parameters are unchanged |
BACnetStack_SendAtomicReadFile |
const uint32_t fileStart |
const int32_t fileStart, signed, as in SendAtomicWriteFile; plus networkPortInstance |
BACnetStack_SendConfirmedTextMessage / BACnetStack_SendUnconfirmedTextMessage |
(…, useMessageClass, messageClassUnsigned, …, destinationAddressLength) |
const bool useNumericMessageClass added after useMessageClass (true sends messageClassUnsigned, false sends messageClassString); trailing const uint8_t messageClassStringEncoding, const uint8_t messageEncoding (0 = UTF-8); plus networkPortInstance |
BACnetStack_SendReinitializeDevice |
(…, destinationAddressLength) |
Trailing const uint8_t passwordEncoding (0 = ANSI X3.4/UTF-8); plus networkPortInstance |
BACnetStack_SendWhoHasWithObjectName |
(…, destinationAddressLength) |
Trailing const uint8_t objectNameEncoding (0 = ANSI X3.4/UTF-8); plus networkPortInstance |
BACnetStack_AddTrendLogObject |
(…, logPropertyIdentifier, maxBufferSize, …) |
const bool useLogPropertyArrayIndex, const uint32_t logPropertyArrayIndex added after logPropertyIdentifier. Pass false, 0 to log the whole property, as v5 did |
BACnetStack_SetTrendLogTypeToCOV |
(deviceInstance, objectInstance, …) |
const uint16_t objectType added after deviceInstance: the Trend Log or Trend Log Multiple object type |
BACnetStack_UpdateVirtualNetworkNumber |
(oldNetworkNumber, newNetworkNumber) |
const uint32_t deviceInstance added first: the main device’s instance, as passed to AddVirtualNetwork |
BACnetStack_SetBACnetSCHubFunctionConfig |
(deviceInstance, scPortInstance, enabled, acceptUri, acceptUriLength, maxConnections) |
acceptUri and acceptUriLength removed. Add each accept URI with the new BACnetStack_AddBACnetSCAcceptUri(…, connectionRole = 0, …); BACnetStack_ClearBACnetSCAcceptUris(…, connectionRole) empties a list |
BACnetStack_SetBACnetSCDirectConnectConfig |
(deviceInstance, scPortInstance, initiateEnabled, acceptEnabled, acceptUri, acceptUriLength) |
acceptUri and acceptUriLength removed. Use BACnetStack_AddBACnetSCAcceptUri(…, connectionRole = 1, …) |
GetProperty* callbacks gain a trailing errorCode
Section titled “GetProperty* callbacks gain a trailing errorCode”Every BACnetStack_RegisterCallbackGetProperty* callback whose v5 form ended at
propertyArrayIndex now takes a trailing uint32_t* errorCode, as the SetProperty* callbacks
already did: BitString, Bool, CharacterString, Date, Double, Enumerated,
OctetString, Real, SignedInteger, Time and UnsignedInteger. That includes the
callbacks a Network Port object’s properties are read through.
The stack presets *errorCode to success before the call. When a callback returns false
having set a code, that code is sent as the error for a read of the property; when it leaves the
code at success, the stack answers with its stored or default value as before. Migration:
add the parameter to each callback you register; leaving it untouched keeps the v5 behaviour.
Two BACnet/SC certificate callback registrations removed
Section titled “Two BACnet/SC certificate callback registrations removed”| Removed | Replacement |
|---|---|
BACnetStack_RegisterCallbackValidateBACnetSCOperationalCertificate() |
None. |
BACnetStack_RegisterCallbackGenerateBACnetSCCertificateSigningRequest() |
None. |
Neither export was ever actually invoked by the stack in any released version — there is no TLS handshake path inside the stack for a validation callback to run before, and no call site that would ever have triggered CSR generation. A registered security callback that never runs is worse than no callback at all, so both are removed rather than wired up.
No runtime behaviour changes for any existing integration: registering either callback never did anything, so removing them changes nothing for a caller that was actually relying on stack behaviour. Certificate validation and CSR generation remain entirely the host application’s responsibility via its own TLS/PKI library, exactly as before — see §16.3 of the BACnet/SC certificates page and its security note for detail. Migration: delete the two registration calls and the two callback functions you registered with them; there is nothing to replace them with.
Test-tool builds have separate notes; ask Chipkin if you build against the test-tool interface.
Later versions may rename further functions. Use the names and parameter orders in the tables above; the migration notes of a later version will list any rename it makes.
3. Return-convention changes
Section titled “3. Return-convention changes”The rule: functions return bool for success/failure by default; functions
producing a buffer return a length.
| Function | v5 | v6 |
|---|---|---|
BACnetStack_RemoveDevice |
void |
bool — true if the device existed and was removed |
BACnetStack_UpdateValue (was ValueUpdated) |
void |
bool — true if processed; false if the device, object, or property is missing or disabled |
BACnetStack_GetBDTEntry / BACnetStack_GetFDTEntry |
bool |
uint32_t — the number of bytes written to ipAddress (6 on success); 0 if there is no entry at the index or an argument is wrong |
BACnetStack_ReadTrendLogRecord / BACnetStack_ReadTrendLogMultipleRecord |
bool |
uint32_t — the number of bytes written to the datum (datumAsString / dataAsString, the same value written to its length output); 0 on failure. The timestamp output also became CASBACnetTime* (§2) |
BACnetStack_SetBACnetSCWebSocketStatus |
void |
bool — true if a websocket or peer matching the URI was found and updated |
Existing call sites keep compiling (an ignored return value is legal C), so this will not
break your build — but you now have error information you did not have before. For
GetBDTEntry / GetFDTEntry, a v5 test such as if (BACnetStack_GetBDTEntry(…)) still means
“an entry was read”, because 0 is still the failure value; the calls themselves need the new
leading networkPortInstance (§2).
4. The bool enabled parameter on Set*Enabled
Section titled “4. The bool enabled parameter on Set*Enabled”The Enable* → Set*Enabled rename made these read like settable toggles,
so each now takes a trailing bool enabled parameter that turns the feature on or off
at runtime.
| Function | Effect of enabled = false |
|---|---|
BACnetStack_SetSegmentationEnabled(…, maxSegmentsTransmittable, bool enabled) |
Device reports NO_SEGMENTATION; optional segmentation properties disabled |
BACnetStack_SetBackupAndRestoreEnabled(…, restoreCompletionTime, bool enabled) |
Backup-and-Restore device properties disabled; configuration-file list cleared |
BACnetStack_SetAlarmsAndEventsForObjectEnabled(…, enableEventDetection, bool enabled) |
The object’s stack alarm-and-event object is freed and its event properties disabled |
- C++ API (
BACnetInterface,BACnetDatabase,BACnetDBDevice): the parameter is defaulted totrue, so existing C++ call sites are source-compatible. No change needed unless you want to disable a feature. - Flat C DLL (
CASBACnetStackDLL.h): C has no default arguments, so a C caller must pass it. This is an ABI and prototype change.
// ---- v5 ----BACnetStack_EnableSegmentation(deviceInstance, segSupported, maxSegAccepted, apduSegTimeout, proposedWindow, actualWindow, maxSegTransmittable);
// ---- v6 ---- renamed, plus a trailing bool; pass true to keep v5 behaviourBACnetStack_SetSegmentationEnabled(deviceInstance, segSupported, maxSegAccepted, apduSegTimeout, proposedWindow, actualWindow, maxSegTransmittable, /*enabled*/ true);5. Behavioural conformance changes
Section titled “5. Behavioural conformance changes”These are wire-behaviour changes, not API changes. They make the device’s response conform to ASHRAE 135. An integrator who relied on the previous non-conformant response will observe a different PDU. None of them require a code change.
5.1 SetProperty* callbacks now propagate the real error code and class
Section titled “5.1 SetProperty* callbacks now propagate the real error code and class”When your FPCallbackSetProperty* callback returns false with *errorCode set, the
stack now emits that exact error code with the BACnetErrorClass that 135-2024 clause 18
gives it.
- v5: only a fixed whitelist was honoured —
invalidArrayIndex,valueOutOfRange,writeAccessDenied(classproperty),noSpaceToWriteProperty(classresources), andabortOther(→ Abort-PDU). Every other code was silently replaced withproperty/writeAccessDenied. - v6: any code the callback sets is emitted, with the class clause 18 lists it under:
for example
deviceBusywithdevice,unknownObjectandbusywithobject, andnoSpaceToWritePropertywithresources. A code that clause 18 lists under more than one class (such asvalueOutOfRangeorwriteAccessDenied) is sent with classproperty.abortOtherstill produces an Abort-PDU. A callback that returnsfalsewithout setting a code still yieldsproperty/writeAccessDenied. - Action: audit
SetProperty*callbacks that set*errorCodeoutside the old whitelist expecting coercion towriteAccessDenied. The wire now shows the real code.
5.2 Malformed confirmed requests now Reject instead of Abort
Section titled “5.2 Malformed confirmed requests now Reject instead of Abort”A confirmed-service request whose APDU fails to decode is answered with a Reject-PDU
carrying a specific RejectReason (ASHRAE 135 clause 5.4.5) instead of Abort(other).
Unrecognized service → unrecognizedService; missing required parameter →
missingRequiredParameter; malformed or wrong tag → invalidTag; trailing non-padding
bytes → invalidTag. Abort is now reserved for transport and segmentation failures.
Action: none. Clients that branch on Abort-vs-Reject will see Reject for malformed requests. Well-formed requests are unaffected.
5.3 AtomicReadFile / AtomicWriteFile support record access
Section titled “5.3 AtomicReadFile / AtomicWriteFile support record access”v6 honours File_Access_Method = recordAccess; v5 always coerced file I/O to stream
access. AddFileObject stores and reports the per-object access method.
Watch the two inverse enums. The wire/request choice
BACnetFileAccessMethodParametersis stream = 0, record = 1 — the inverse of the property enumBACnetFileAccessMethod, which is record = 0, stream = 1.
Two conformance fixes ship with it: an AtomicReadFile at File-Start-Position 0 on an
empty file now returns End-Of-File instead of an error; a read or write callback that
returns false is now answered with an Error carrying the code it set (and that code’s clause-18
class), not with a success ACK or an Abort. See §5.3.1.
Action: none. An integrator who relied on stream-only coercion will now see record
ACKs for files whose File_Access_Method is record.
5.3.1 Seeding a read-only File object
Section titled “5.3.1 Seeding a read-only File object”BACnetStack_SetFileObjectStreamAccessContents(), which pushed bytes into a File object, is gone in
version 6. The stack no longer holds file contents: your application keeps the bytes and serves them through
the read callback. This is the shape that replaces it, for the common case of a read-only certificate or
configuration file whose contents the application already holds in memory.
AddFileObject creates the object. Like AddTrendLogObject and AddNotificationClassObject, it adds the
object itself. Drop the version 5 pattern of calling AddObject first: that call creates the object and
increases Database_Revision, so a configuration file loses the protection AddFileObject gives it (it does not
increase Database_Revision for a configuration file). AddFileObject takes the property enumeration
(record = 0, stream = 1), the inverse of the value SendAtomicReadFile and SendAtomicWriteFile take
(see §5.3).
/* Application-owned storage. In version 5 these bytes were held by the stack. */static uint8_t certificateBytes[CERT_MAX];static uint32_t certificateLength;
/* 1. Create the File object: stream access, not writable. AddFileObject adds the object itself; do not call BACnetStack_AddObject for it first. */BACnetStack_AddFileObject(deviceInstance, CERT_FILE_INSTANCE, false, /* isWritable: false makes the stack refuse AtomicWriteFile */ false, /* isConfigurationFile */ 1); /* File_Access_Method property value: 1 = stream access */
/* 2. Serve File_Size. The stack reads it on every AtomicReadFile to check the start position; without it every read is refused with Error(services, optional-functionality-not-supported). Put this inside your existing unsigned-integer callback: only one can be registered. Object_Name and File_Type come from the character-string callback the same way. */bool CallbackGetPropertyUnsignedInteger(const uint32_t deviceInstance, const uint16_t objectType, const uint32_t objectInstance, const uint32_t propertyIdentifier, uint32_t* value, const bool useArrayIndex, const uint32_t propertyArrayIndex, uint32_t* errorCode){ if (objectType == 10 /* file */ && objectInstance == CERT_FILE_INSTANCE && propertyIdentifier == 42 /* File_Size */) { *value = certificateLength; return true; } return false;}BACnetStack_RegisterCallbackGetPropertyUnsignedInteger(CallbackGetPropertyUnsignedInteger);
/* 3. Serve the bytes. Called for every AtomicReadFile against an existing stream-access File object. */bool CallbackReadFile(const uint32_t deviceInstance, const uint32_t fileInstance, const uint32_t fileStart, const uint32_t requestedCount, uint8_t* fileData, uint32_t* fileDataLength, const uint32_t maxFileDataLength, bool* endOfFile, uint32_t* errorCode){ if (fileInstance != CERT_FILE_INSTANCE) { return false; } if (fileStart > certificateLength) { *errorCode = 11; /* invalid-file-start-position, answered with class services */ return false; }
uint32_t octetsRemaining = certificateLength - fileStart; uint32_t octetsToCopy = requestedCount < octetsRemaining ? requestedCount : octetsRemaining; if (octetsToCopy > maxFileDataLength) { octetsToCopy = maxFileDataLength; }
memcpy(fileData, certificateBytes + fileStart, octetsToCopy); *fileDataLength = octetsToCopy;
/* Required: the stack presets this to false. */ *endOfFile = (fileStart + octetsToCopy) >= certificateLength; return true;}BACnetStack_RegisterCallbackReadFile(CallbackReadFile);
/* No write callback is needed for a read-only file: see below. */What the stack answers for you.
- A request for a File object that does not exist gets an Error; no callback is called.
- With
isWritable = false, every AtomicWriteFile getsError(services, file-access-denied)before any write callback runs, so a read-only file needs no write callback. - When the read or write callback returns
false, the stack answers with an Error, never an Abort: the code you set inerrorCode, with its clause-18 class (for exampleinvalid-file-start-position(11) withservices,file-full(128) withobject,device-busywithdevice). If you leaveerrorCodeuntouched, or setotheror a code that cannot be sent as an Error (abortOtherincluded), the answer isError(services, file-access-denied).
Set endOfFile on every successful read. The stack starts it as false. If you leave it false on the read that
reaches the end, the client is told more data follows, reads again, and gets an error instead of a clean end of
file.
Properties. The stack stores File_Access_Method and Read_Only (from isWritable) and generates
Property_List; a Get callback for Read_Only, if you register one, takes precedence. Serve everything else
through the property callbacks: Object_Name, File_Type, File_Size (required for reads), Modification_Date,
Archive, and Record_Count for record-access files. For a writable file, your application also updates
Modification_Date and clears Archive after each successful write.
Node. Both enumerations are exported as named constants, BACnetFileAccessMethod and
BACnetFileAccessMethodParameters, so the inversion can be written in code rather than remembered.
5.4 Record-bearing ReadRange-ACK no longer aborts
Section titled “5.4 Record-bearing ReadRange-ACK no longer aborts”A BACnetLogRecord carrying StatusFlags now encodes correctly. Previously the device
aborted on the wire-encode of any record-returning ReadRange-ACK whose records carried
StatusFlags. A device serving a record-bearing ReadRange now returns the records.
Action: none.
5.5 COV notifications are now sent for Loop and Pulse Converter objects
Section titled “5.5 COV notifications are now sent for Loop and Pulse Converter objects”A device with a Loop or Pulse Converter object and an active COV subscriber now sends notifications. Previously the stack accepted the SubscribeCOV, recorded the value change, and then silently dropped the notification.
The List of Values follows ASHRAE 135-2024 Table 13-1 — Loop: Present_Value,
Status_Flags, Setpoint, Controlled_Variable_Value; Pulse Converter: Present_Value,
Status_Flags, Update_Time. Change detection for both now also honours COV_Increment.
Action: none, but a subscriber that relied on never receiving Loop or Pulse Converter notifications will now receive them.
Still not supported: SubscribeCOV on AccessDoor, AccessPoint, and CredentialDataInput continues to be rejected exactly as before.
5.6 Notification Class Recipient_List can now be made writable
Section titled “5.6 Notification Class Recipient_List can now be made writable”A Notification Class object’s Recipient_List (property 102) can now be made writable.
Previously every write to it was rejected. It is still read-only by default: the default
property profile marks it readable but not writable (135-2024 Table 12-24, conformance code R).
To accept writes, which a device claiming BIBB AE-CRL-B needs, turn them on with
BACnetStack_SetPropertyByObjectTypeWritable(deviceInstance, 15 /* notification-class */, 102, true),
or per object with BACnetStack_SetPropertyWritable. Until you do, a remote WriteProperty to
Recipient_List is refused.
Once writable, the write is whole-property — the entire BACnetDestination list is replaced,
and an empty list is a legal clear. Per-array-index element writes are not supported.
Action: if your device claims AE-CRL-B, or a client needs to edit the recipient list, make
Recipient_List writable as above; otherwise nothing changes.
5.7 New decodable abstract-syntax types
Section titled “5.7 New decodable abstract-syntax types”BACnetAbstractSyntaxType::ObjectFactory now reconstructs Destination (72), TimeValue,
and DailySchedule, so these constructed values round-trip on decode. Values that
previously fell through to BACnetStackUnknownAPDU are now typed objects.
Action: audit any decode consumer that pattern-matched the unknown form.
6. Decode output (DecodeAsXML / DecodeAsJSON)
Section titled “6. Decode output (DecodeAsXML / DecodeAsJSON)”Version 6 changes the shape of the decoded XML/JSON text — element and attribute names, value formats, and JSON container typing. Only the textual representation changed; wire decode is identical.
Full detail, including complete rename tables and worked examples, is in the companion guide: Decoded XML and JSON output: migrating from v5 to v6.
Summary of what will break a parser:
- Renames: all-caps
ACK→ title-caseAck(thePDUsuffix stays upper); confirmed service request containers gain aRequestsuffix; all-capsCOV→ camelCaseCov;OctString→OctetString;Integer→SignedInteger;maxAdpuLengthAccepted→maxApduLengthAccepted(typo fix);hundrethSeconds→hundredthSeconds; self-namedStreamAccess/RecordAccessattributes →context="0". - Value shapes:
ObjectIdentifiergainsvalue="<objectType>,<objectInstance>";Booleanvalue=is"true"/"false"rather than'1'/'0';maxApduLengthAcceptedis the decoded byte count (e.g.1476), not the raw hex enum;Real/Doublealways include a decimal point (50→50.0);Timetext is zero-padded;maxSegmentsAcceptedis a decoded label. - JSON typing: list containers (
ListOf…,NetworkNumbers,PriorityArray) always emit arrays — single-child coercion is gone; Boolean_valueis a real JSON boolean; empty elements render as{}, nevernull. - XML structure: an
<?xml version="1.0" encoding="UTF-8"?>declaration is emitted; attributes are double-quoted; tab and newline indentation is applied.
Date.year keeps the BACnet years-since-1900 wire encoding; a new additive yearFull
carries the 4-digit calendar year. Every primitive gains an additive datatypeLabel
attribute.
7. Changes by feature
Section titled “7. Changes by feature”The rest of the manual describes version 6 only. This section collects, feature by feature, what changed since version 5 and the PDF guides that came before the manual. Start with “Data links (all)”: the other data-link sections build on it. Each section opens with one line saying who it applies to, then lists what to change.
Data links (all)
Section titled “Data links (all)”Applies to every application that sends or receives messages on any data link: BACnet/IP, BACnet MS/TP or BACnet/SC.
- Use the per-port message callbacks.
BACnetStack_RegisterCallbackReceiveMessageForPort()andBACnetStack_RegisterCallbackSendMessageForPort()replaceRegisterCallbackReceiveMessageandRegisterCallbackSendMessage. Both carry anetworkPortInstance, the instance of a Network Port object, instead of the oldnetworkTypebyte. The older callbacks without that parameter are gone. See The networkPortInstance. - Add a Network Port object for each port.
BACnetStack_AddNetworkPortObject()takes seven parameters.networkNumberandnetworkNumberQualitywere added (the older guides showed five); pass0, 0(unknown) to reproduce the old number-less behaviour. The new parameters sit beforereferencePort: see the changed signatures for the full order, and Adding a Network Port object. - Send functions take a Network Port instance. Pass the instance of the port to send on, or
BACNET_NETWORK_PORT_DEFAULT(4194303) for a single-port device. AnetworkTypevalue of0still compiles, but it now names instance 0 and sends nothing if no port has that instance. - The stack holds written Network Port values pending. A client’s write to a port’s configuration is checked and held inside the stack, and your Set callback is called only when the change is activated. See “Network Port objects” below.
Network Port objects
Section titled “Network Port objects”Applies to every application that adds a Network Port object, which is every application using the per-port callbacks above.
The chapter: Network Port objects. It replaces the 2020 PDF Network Port
Object and ReinitializeDevice. Do not follow the PDF against a version 6 library: the stack now holds
written values pending and reports Changes_Pending itself, so the PDF’s host-side pending storage would
fight the stack.
- The stack holds written values pending, not your application. A written
FD_BBMD_Address,FD_Subscription_Lifetime,Max_Master,Max_Info_FramesorNetwork_Numberstays inside the stack, reads back as the written value, and setsChanges_Pendingto TRUE. Your Set callback is called only when the change is activated, not when it is written. Changes_PendingandNetwork_Numberare served by the stack. The stack does not ask your Get callbacks for either, so a value your callback would give for them is not used.BACnetStack_ActivateNetworkPortPendingChanges()activates pending changes from your own code, with no ReinitializeDevice and no password.- Every Get property callback gained a trailing
uint32_t* errorCode, and the Set callbacks’errorCodeisuint32_t*instead ofunsigned int*. CASBACnetStack_SendRegisterForeignDevice()is nowBACnetStack_SendRegisterForeignDevice()and takes the Network Port instance first.- The header and the standard name the property
Max_Manager(BACnetPropertyIdentifier_maxManager, 64); this manual calls itMax_Master, as the MS/TP pages do. - The
CASBACnetStackExampleConstantsnames are gone. Property identifiers are in theBACnetPropertyIdentifierenumeration inCASBACnetStackDLL.h. Other values (object types, error codes, reinitialized states) are plain numbers in this chapter.
ReinitializeDevice
Section titled “ReinitializeDevice”Applies to every application that accepts ReinitializeDevice and uses Network Port objects.
The chapter: ReinitializeDevice.
- Pending changes are applied after your
CallbackReinitializeDeviceaccepts the request, so a request with a wrong password activates nothing. This is the change from the 2020 PDF Network Port Object and ReinitializeDevice.
BBMD and foreign devices
Section titled “BBMD and foreign devices”Applies to every application that runs a BBMD or registers as a foreign device on BACnet/IP.
The chapter: BBMD and foreign devices.
- Every table and registration function takes the Network Port instance.
BACnetStack_AddBDTEntry,BACnetStack_GetBDTEntry,BACnetStack_GetFDTEntry,BACnetStack_SendRegisterForeignDevice,BACnetStack_SendReadBroadcastDistributionTableandBACnetStack_SendReadForeignDeviceTablehave a new first parameter,networkPortInstance: the IPv4 Network Port object whose BBMD the call acts on. A device with more than one BACnet/IP port has one BBMD, one BDT and one FDT per port. BACnetStack_GetBDTEntryandBACnetStack_GetFDTEntryreturnuint32_t, the number of address bytes written (6), or 0 on failure. They returnedbool.BACnetStack_ClearForeignDeviceTableandBACnetStack_ClearBroadcastDistributionTableare renamedBACnetStack_ResetForeignDeviceTableandBACnetStack_ResetBroadcastDistributionTable.- You no longer enable the BBMD properties yourself.
BACnetStack_SetBBMDenables BBMD_Broadcast_Distribution_Table, BBMD_Accept_FD_Registrations and BBMD_Foreign_Device_Table on the port, and sets its BACnet_IP_Mode to BBMD. The 2021 guide’sSetPropertyEnabledstep can be dropped. - The broadcast distribution mask comes from the BDT entry. The BBMD forwards using its own BDT entry’s mask; there is no separate host mask. The port’s IP_Subnet_Mask, read when the Network Port object is added, is used only to recognise senders on the BBMD’s own subnet. The BDT subnet mask is 4 bytes, not the 6 that the older guides gave.
- The build option is
STACK_OPTION_NM_BBMDC, or theSTACK_OPTION_PROFILE_B_BBMDprofile, which turns it on. Either needsSTACK_OPTION_DATA_LINK_LAYER_IPV4, and turns on the Network Port object type.
BACnet MS/TP
Section titled “BACnet MS/TP”Applies to every application that runs a BACnet MS/TP port. MS/TP frames go through the per-port callbacks in “Data links (all)”, with the instance of the MS/TP Network Port object, or, in byte mode, through the byte-level callbacks below.
The chapter: BACnet MS/TP.
MSTP.c is gone: register the byte callbacks and call BACnetStack_LoopMSTP, or stay in packet mode with your own framing.
source/MSTP.c and source/MSTP.h (the C MS/TP engine, and every MSTP_* function and mstp_* callback type in them) are removed, and the
stack runs the same state machines itself, one engine per port. This breaks anything that compiled MSTP.c. The call-by-call
mapping, and where MSTP_MAX_PACKET_OCTETS (501), MSTP_MAX_NPDU_OCTETS (1497) and MSTP_INPUT_BUFFER_SIZE (1508) went, is in
Moving from MSTP.c. In short: delete the engine initialisation, the two frame queues, the timer
pair and the sleep callback; register CallbackMSTPReceiveBytes, CallbackMSTPSendBytes and CallbackMSTPMicroseconds; serve
MAC_Address, Max_Master, Max_Info_Frames and Link_Speed from the Network Port’s property callbacks; and call BACnetStack_LoopMSTP
(on its own thread, if BACnetStack_Tick can take milliseconds).
Compared with the 2023 BACnet MSTP Manual PDF (stack version 4.x and later), what changed in the MS/TP engine,
in the terms of the stack’s current interface (the PDF’s C functions, such as MSTP_Init(), no longer exist):
- Received frames carry a destination. The PDF’s APDU callback (
mstp_APDUCallBack) had no destination; the engine now gives the stack the source and the destination of every received NPDU (255 for a broadcast) itself, so you supply neither. In packet mode, your receive-message callback must still return both connection strings. - Two things the engine offers that the PDF’s API did not. A driver-enable switch
(
BACnetStack_RegisterCallbackMSTPTransmitEnable(), optional) and an in-the-token-ring indicator (theBACnetMSTPCounter_InTokenRingentry ofBACnetStack_GetMSTPCounters()): whether this node has found another master to pass the token to. - Master nodes only. The engine does not start for a station (MAC_Address) above 127. The PDF’s 128–254 slave range does not apply to this engine.
- No settings to reset or order. The engine takes its station, Max_Master, Max_Info_Frames and baud rate from the Network Port object’s properties, read when it starts and again after pending Network Port changes are activated; every timer has its default. There is no init call to configure after.
- The silence-timeout recovery keeps your settings. After a long gap between calls of
BACnetStack_LoopMSTP()the engine restarts its state machines but no longer reverts the settings or drops queued frames. The gap is Tno_token + 254 × Tslot, 3,040 ms with the defaults. The PDF said 1 second and a full reset. - 480-octet APDUs on MS/TP. The stack limits responses on MS/TP to 480 octets. Confirmed requests and device-originated confirmed notifications sent on MS/TP advertise 480 as Max_APDU_Length_Accepted. The Network Port’s APDU_Length reads 480.
- Max_Master and Max_Info_Frames are Network Port properties held pending. A client’s write is checked and held until ReinitializeDevice (ACTIVATE_CHANGES or WARMSTART), then handed to your application. See Max_Master and Max_Info_Frames.
The PDF’s C functions (MSTP_Init, MSTP_Loop, MSTP_SetBaudRate, MSTP_SetMaxMaster, MSTP_InsertFrameToSend and the byte,
sleep and timer callbacks) are all removed; the mapping to the stack’s interface is in
Moving from MSTP.c.
BACnet/SC
Section titled “BACnet/SC”Applies to every application that has a working v5.3.5 BACnet/SC integration, or follows the v5.3.5 BACnet SC manual.
The chapter: BACnet/SC. It replaces the v5.3.5 BACnet SC manual. If you still ship against a 5.x stack, keep using the v5.3.5 manual from your 5.x release package: do not follow the chapter’s instructions against a 5.x library, and do not follow the v5.3.5 manual’s certificate-loading instructions against a version 6 library.
This is the short version of what moved. Details and worked examples are in the referenced sections.
- Certificate loading is now application-owned, not stack-owned.
BACnetStack_SetFileObjectStreamAccessContentsis gone. The stack no longer holds a copy of your certificate bytes. It asks for them on demand throughBACnetStack_RegisterCallbackReadFile/RegisterCallbackWriteFile(plus...ReadFileRecord/...WriteFileRecordfor record-access files, which BACnet/SC certificate files do not use). See §16.2. - Certificate File objects are created with
BACnetStack_AddFileObject, notBACnetStack_AddObject.AddFileObjectis the only call that also storesFile_Access_MethodandFile_Read_Only. Without it, anAtomicWriteFile/AtomicReadFileagainst the object cannot be served, and the stack answers Error(services, optional-functionality-not-supported). See §16.1. - The certificate-validation and CSR-generation registration callbacks are gone. Neither
was ever actually invoked by the stack in any released version: there is no TLS handshake path inside
the stack for a validation callback to run before. Certificate validation is entirely the host
application’s responsibility, via its own TLS/PKI library (see the security note in
§16.3). A CSR is now requested through the Network Port
GENERATE_CSR_FILEcommand, which reaches the host throughBACnetStack_RegisterCallbackNetworkPortCommand(§16.6). SC_Direct_Connect_Initiate_EnableandSC_Direct_Connect_Accept_Enableare writable. AWritePropertyto either setsChanges_Pending = TRUE. The new value takes effect on the data link onReinitializeDevice(ACTIVATE_CHANGES)orWARMSTART, the same activation model as every other pending BACnet/SC configuration change. See §16.5 and §22.- Rewriting the bytes of an already-bound certificate File object raises
Changes_Pending. Rotating a certificate by writing new PEM bytes into the existingOperational_Certificate_File/Issuer_Certificate_Files/Certificate_Signing_Request_Fileobject, without rebinding to a different File object, used to be invisible toChanges_Pending. It is not anymore. See §16.5. - Compile-time flag names. The gating flags are
STACK_OPTION_DATA_LINK_LAYER_SC,STACK_OPTION_OBJECT_TYPE_NETWORK_PORT,STACK_OPTION_OBJECT_TYPE_FILE,STACK_OPTION_DATA_LINK_LAYER_IPV4, andSTACK_OPTION_NM_SCCM_A(orSTACK_OPTION_ENABLE_ALL_BIBBS). WithoutSTACK_OPTION_NM_SCCM_A,BACnetStack_AddFileObjectreturns false and the certificate File objects cannot be created; it also turns onSTACK_OPTION_ATOMIC_FILE_IOand ReinitializeDevice. The v5.3.5 manual used other names: see Corrections to older documents. - Unchanged from v5.3.5: the hub/node model, the connection state machine, the transport callback
contract, and the SC configuration exports
(
BACnetStack_SetBACnetSCUuid/SetBACnetSCHubConnector/SetBACnetSCSettings/SetBACnetSCCertificateFileObjects).SetBACnetSCHubFunctionConfigandSetBACnetSCDirectConnectConfiglost their accept-URI parameters: add accept URIs withBACnetStack_AddBACnetSCAcceptUri(further changed signatures).
Virtual devices and gateways
Section titled “Virtual devices and gateways”Applies to every application that hosts more than one device or a virtual network.
The chapter: Virtual devices and gateways. Compared with
CAS BACnet Stack - Virtual Devices.pdf (2024-04, stack 4.3.5) and the version 4 example project
BACnetVirtualDevicesServerExampleCPP:
- Function names carry the
BACnetStack_prefix. The PDF’sfpAddDevice,fpAddVirtualNetworkand so on were function pointers loaded from the library. The exports are nowBACnetStack_AddDevice(),BACnetStack_AddVirtualNetwork(),BACnetStack_AddDeviceToVirtualNetwork()and so on. - Send functions take a Network Port instance.
BACnetStack_SendIAm()andBACnetStack_SendIAmRouterToNetwork()takenetworkPortInstancewhere the PDF passedNETWORK_TYPE_IP. Pass the instance of your BACnet/IP Network Port object, orBACNET_NETWORK_PORT_DEFAULT(4194303) when the build has only one data link. BACnetStack_SendIAmRouterToNetwork()takes a list of network numbers. It now starts withnetworkNumbersandnetworkNumberCount. PassNULL, 0to announce every virtual network, as the PDF’s call did.- Every get-property callback ends in
uint32_t* errorCode. The PDF’s callbacks stop atpropertyArrayIndex. - BBMD calls changed. See “BBMD and foreign devices” above.
- A second
BACnetStack_AddDevice()call is rejected. Every device after the first must be added withBACnetStack_AddDeviceToVirtualNetwork(). - New functions:
BACnetStack_UpdateVirtualNetworkNumber(),BACnetStack_UpdateDeviceInstance()andBACnetStack_RemoveDevice().
DeviceCommunicationControl
Section titled “DeviceCommunicationControl”Applies to every application that accepts or sends DeviceCommunicationControl.
The chapter: DeviceCommunicationControl. It replaces the 2021 PDF CAS BACnet Stack - Device Communication Control (minimum stack version 3.25.0). Do not follow that document against a version 6 DLL: the client function was renamed and gained parameters, and the stack’s handling of an unregistered callback and of callback error codes changed.
BACnetStack_SendDeviceCommunicatonControlis nowBACnetStack_SendDeviceCommunicationControl(the missingiis fixed; there is no alias). See section 2.- The client function takes a Network Port instance, not a network type. The
networkTypeargument (0 = BACnet/IP, 1 = MS/TP) becamenetworkPortInstance; passBACNET_NETWORK_PORT_DEFAULT(4194303) for a single-port device. A trailingpasswordEncodingargument was added. The C export has no default arguments, so every argument must be passed. - An unregistered callback now refuses every request. Without
BACnetStack_RegisterCallbackDeviceCommunicationControl, the stack answers every DeviceCommunicationControl with Error(security, password-failure) instead of applying it unauthenticated. - Only three error codes reach the wire from the callback. password-failure (26), optional-functionality-not-supported (45) and service-request-denied (29) are sent; any other value, or none, is sent as password-failure. The PDF’s advice to return missing-required-parameter (16) for an absent password no longer applies: return password-failure.
- The stack refuses the deprecated
DISABLEvalue itself with Error(services, service-request-denied). Your callback still sees aDISABLErequest, so that the password is checked first; when it returns true, the stack then refuses the request and changes nothing. Do not act onDISABLEin the callback.
Debug logging
Section titled “Debug logging”Applies to every application that registers a debug log callback or builds with debug logging.
The chapter: Debug logging.
- The build option. Debug logging is controlled by
STACK_OPTION_API_DEBUG_LOGGING, not by building the stack with_DEBUG. A release build can log, and a debug build without the option does not. - Function names. The old examples called
fpRegisterCallbackLogDebugMessageandfpAddDevice, function pointers that their loader filled in. In version 6 call the exports by their full names,BACnetStack_RegisterCallbackLogDebugMessage()andBACnetStack_AddDevice(). The generated adapters use the same names. - Behaviour without a callback. In a build with the option, the stack prints every message to
stdoutuntil you register a callback. - The callback survives
BACnetStack_Reset(). It is the only callback that does.
Corrections to older documents
Section titled “Corrections to older documents”Applies to anyone who still has the older PDF guides or the v5.3.5 BACnet SC manual open. These documents contain errors that version 6 code would expose.
BBMD guide (2021 PDF).
- The guide had the one-hop and two-hop masks the wrong way round. See Distribution masks.
- Its function-pointer names (
fpAddBDTEntry,fpSetBBMD) andCASBACnetStackExampleConstantscame from an example application, not the stack. Call theBACnetStack_*exports directly.
BACnet SC manual (v5.3.5). Four things in it do not apply to version 6:
- Build flag names. It used the version 5 names
ENABLE_DATA_LINK_LAYER_SC,ENABLE_NETWORK_PORT_OBJECTandENABLE_FILE_OBJECT. Version 6 renames themSTACK_OPTION_DATA_LINK_LAYER_SC,STACK_OPTION_OBJECT_TYPE_NETWORK_PORTandSTACK_OPTION_OBJECT_TYPE_FILE, with noENABLE_*aliases, so a build script that still sets theENABLE_*names changes nothing. Every other build flag is renamed the same way; see the migration checklist. - Certificate bytes. It loaded certificates with
BACnetStack_SetFileObjectStreamAccessContents, which is removed. The stack keeps no copy of the certificate bytes; serve them fromRegisterCallbackReadFileand accept writes inRegisterCallbackWriteFile. - Property identifiers in Appendix A. It gave
MAC_Address332,APDU_Length388,Network_Number333 andNetwork_Number_Quality339. The values areMAC_Address423,APDU_Length399,Network_Number425,Network_Number_Quality426 andNetwork_Type427. It also listedObject_Nameas writable on a Network Port; it is read-only by default, andBACnetStack_SetPropertyWritablemakes it writable. - Optional flags in Appendix C.1.
ENABLE_BACNET_SC_HUB_FUNCTION,ENABLE_BACNET_SC_DIRECT_CONNECTandENABLE_BACNET_SC_DEBUG_LOGGINGdo not exist. The hub function and direct connect are configured at runtime (Appendix C.2 of the BACnet/SC reference), and BACnet/SC debug output usesSTACK_OPTION_API_DEBUG_LOGGING.
8. Additions (non-breaking)
Section titled “8. Additions (non-breaking)”No migration action required. Listed so they are not mistaken for breaking changes.
| Addition | Purpose |
|---|---|
BACnetStack_GetBACnetProtocolRevision() |
Returns the BACnet protocol revision the stack was compiled against |
BACnetStack_GetInfo capability discovery |
The <info> XML now also reports a <capabilities> list plus a raw <macros> list of enabled compile-time options, so a customer linking a prebuilt DLL can discover what it supports at runtime |
BACnetStack_SendLifeSafetyOperation() + BACnetStack_RegisterCallbackLifeSafetyOperation() |
LifeSafetyOperation confirmed service (cl. 13.13, confirmed-service-choice 27). On the customer DLL surface, but behaviour is gated by the build option STACK_OPTION_DM_LSO_LIFE_SAFETY_OPERATION |
BACnetStack_SendWhoHasWithObjectIdentifierAndLimits() / …WithObjectNameAndLimits() |
Device-instance-range-restricted Who-Has. The pre-existing non-AndLimits variants always sent the full range |
BACnetStack_SendWhoIsRouterToNetwork() |
Router-discovery query |
BACnetStack_AddEventLogObject() |
Add an Event Log object |
BACnetStack_RegisterCallbackReadFileRecord() / …WriteFileRecord() |
Record-access file I/O callbacks (see §5.3) |
BACnetStack_RegisterCallbackWriteGroupInhibitDelay() |
Optional WriteGroup inhibit-delay callback (cl. 15.11.1.1.4) |
BACnetStack_RegisterCallbackGetPropertyLightingCommand() / …SetPropertyLightingCommand() |
Lighting Command property access |
BACnetStack_RegisterCallbackGetPropertyAuthenticationFactorFormat() |
Authentication Factor Format property access |
BACnetStack_SetIntrinsic*Algorithm() (19 functions, 17 of them new) |
Intrinsic event algorithm configuration, one function per algorithm: BufferReady, ChangeOfBitstring (plus a 64-bit variant, BACnetStack_SetIntrinsicChangeOfBitstringAlgorithm64), ChangeOfCharacterstring, ChangeOfDiscreteValue, ChangeOfLifeSafety, ChangeOfState (Bool, Enum, Unsigned), ChangeOfStatusFlags, ChangeOfTimer, ChangeOfValue, CommandFailure, FloatingLimit, OutOfRange (plain, Signed, Unsigned, Double) and UnsignedRange. The OutOfRange and ChangeOfState Bool functions already existed in 5.7.1 |
BACnetStack_SetFaultLifeSafetyAlgorithm() / …SetFaultStatusFlagsAlgorithm() |
Fault algorithm configuration |
BACnetStack_SetBACnetStackSetting exported setting IDs, and a new leading deviceInstance on both it and BACnetStack_GetBACnetStackSetting |
The setting-ID enum is now available to you in a public header, so you no longer need to hard-code the numeric values. Additional settings are also added. Settings are also per-device: both exports take deviceInstance first. To keep the old process-wide behaviour exactly, pass BACNET_STACK_SETTING_ALL_DEVICES (4194303); pass a real device instance to override the default for that device alone |
BACnetStack_AddRouterRoute() |
Configure a physical-router route with an operator-supplied next-hop MAC. Required before B-RTR can be claimed |
BACnetStack_RemoveGroupMember() |
The companion to BACnetStack_AddGroupMember(), which shipped without one. Matches by object identifier; removing an absent member fails rather than being idempotent |
BACnetStack_RegisterCallbackEventNotificationReceived() |
Receives the contents of every valid Confirmed/UnconfirmedEventNotification a local device receives — the event object, time stamp, message text, Event Values (as their CHOICE tag and encoded) and the routed sender’s network and address — for AE-N-A / AE-VN-A (an operator display, B-OD) without decoding frames with BACnetStack_DecodeAs(). Built with STACK_OPTION_ALARMS_AND_EVENTS; enable the services with BACnetStack_SetServiceEnabled bits 2 and 29 |
STACK_OPTION_DM_LM_LIST_MANIPULATION build option |
Opt-in receive-side AddListElement (cl. 15.1) and RemoveListElement (cl. 15.2) for the Group object’s List_Of_Group_Members. The default property profile stays read-only (conformance code R); make it writable at runtime via BACnetStack_SetPropertyWritable to accept these services |
9. Symptom → fix
Section titled “9. Symptom → fix”These are derived from the interface changes above, not from a recorded compile of a v5 application against v6. Exact compiler wording will vary by toolchain.
| Symptom | Cause | Fix |
|---|---|---|
Undefined symbol BACnetStack_BuildReadProperty / _BuildWriteProperty / _ClearReadProperty / _ClearWriteProperty |
Stateful object-access pattern removed | Pack a propertyArrays element and call the single-step send (§1) |
Undefined symbol …Async (SendReadPropertyAsync, SendWritePropertyAsync, SendReadPropertyMultipleAsync, SendReadPropertySingleAsync) |
*Async suffix removed |
Call SendReadProperty / SendWriteProperty with the right serviceType (§1.2) |
SendReadProperty / SendWriteProperty — “too few arguments” |
Signature gained serviceType, propertyArraysCount, propertyArrays, propertyArraysLength |
Rewrite the call (§1.4) |
Undefined symbol BACnetStack_SendCreateObjectByType / …ByIdentifier |
Merged into one function | SendCreateObject with flags 0x00 (by type) or 0x01 (by identifier) |
Undefined symbol BACnetStack_SendDeviceCommunicatonControl |
Spelling typo fixed | Add the missing i: …CommunicationControl |
Undefined symbol BACnetStack_ValueUpdated |
Renamed | BACnetStack_UpdateValue |
Undefined symbol BACnetStack_EnableSegmentation / _EnableBackupAndRestore / _EnableAlarmsAndEventsForObject |
Renamed to Set*Enabled |
Rename and add the trailing bool enabled in C (§4) |
SetSegmentationEnabled — “too few arguments” (C only) |
Trailing bool enabled added; C has no default arguments |
Append true to preserve v5 behaviour |
Undefined symbol BACnetStack_ClearForeignDeviceTable / _ClearBroadcastDistributionTable |
Clear* → Reset* |
ResetForeignDeviceTable / ResetBroadcastDistributionTable |
SendRegisterForeignDevice / SendReadBroadcastDistributionTable / SendReadForeignDeviceTable / GetBDTEntry / GetFDTEntry / AddBDTEntry — “too few arguments” |
Leading networkPortInstance added |
Pass the IPv4 Network Port instance first (§2) |
AddNetworkPortObject — “too few arguments”; undefined symbol BACnetStack_AddNetworkPortObjectWithNetworkNumber |
The two merged; networkNumber and networkNumberQuality are always passed |
Call AddNetworkPortObject with both values before referencePort (§2) |
SendDeviceCommunicationControl — “too few arguments” after fixing the spelling |
networkType → networkPortInstance; trailing passwordEncoding added |
Pass the port instance (or 4194303) and 0 for UTF-8 (§2) |
SendIAmRouterToNetwork — argument mismatch |
Leading networkNumbers / networkNumberCount added; networkType → networkPortInstance |
Pass NULL, 0 to announce the full routing table (§2) |
Incompatible function-pointer type when registering a GetProperty* callback |
Trailing uint32_t* errorCode added |
Add the parameter (§2) |
A send returns false and nothing goes out, though the call compiled unchanged |
The networkType value (0, 1 or 2) is now read as a Network Port instance, and no port has that instance |
Pass the port’s instance, or BACNET_NETWORK_PORT_DEFAULT on a single-transport device (§2) |
Incompatible function-pointer type when registering a RegisterHook* callback |
The hook’s networkType became uint32_t networkPortInstance |
Change the parameter (§2) |
Undefined symbol BACnetStack_RegisterCallbackReceiveMessage / _RegisterCallbackSendMessage |
Replaced by the …ForPort registrations |
Rename, and change the callbacks’ networkType to networkPortInstance (§2) |
Undefined symbol BACnetStack_DecodeAsXML / _DecodeAsJSON |
Merged into BACnetStack_DecodeAs |
Pass BACnetDecodeType_XML or BACnetDecodeType_JSON first (§2) |
Incompatible function-pointer type when registering the GetSystemTime callback |
The callback returns CASBACnetTime, not time_t |
Return CASBACnetTime (§2) |
Incompatible function-pointer type when registering the GetListElevatorGroupLandingCallStatus callback |
floorTextLength and floorTextMaxLength swapped and widened |
Reorder and widen them (§2) |
AddTrendLogObject / SetTrendLogTypeToCOV / UpdateVirtualNetworkNumber / SendConfirmedTextMessage / SendReinitializeDevice / SendWhoHasWithObjectName / SetBACnetSCHubFunctionConfig — argument mismatch |
Parameters added or removed | See further changed signatures |
Undefined symbol BACnetStack_SendReadRangeByPosition / _BySequenceNumber / _ByTime |
By* → With* |
SendReadRangeWithPosition / WithSequenceNumber / WithTime |
Undefined symbol BACnetStack_RegisterHookProperty* (any of the 13) |
Removed, not renamed — there is no replacement symbol | Decode the raw inbound frame with BACnetStack_DecodeAs(), or use the property callbacks (§2) |
Undefined symbol BACnetStack_SetFileObjectStreamAccessContents |
Removed: the stack no longer holds file contents | Serve them through the read callback (§5.3.1) |
Undefined symbol BACnetStack_SetMaxOutstandingConfirmedRequests / _SetAllowIndefiniteCOVLifetime |
Removed: introduced in 5.7.0, not in version 6 | Delete the call (§2) |
SendWriteProperty returns false for a value that worked in v5 |
A dataType other than 0..12, 27 or 64 is rejected |
Other complex/constructed datatypes are not supported through this API; contact Chipkin |
| XPath / JSONPath matchers stop matching after upgrade | Decode element and attribute renames | Update matchers (§6) |
| JSON consumer breaks on a list that used to be an object | Single-child list coercion removed — containers always emit arrays | Always treat ListOf… as an array |
JSON == null check for an empty element stops firing |
Empty elements render as {} |
Test for an empty object |
| Client sees Reject-PDU where it used to see Abort | Conformance fix | Expected (§5.2) |
Wire shows an error code you thought was coerced to writeAccessDenied |
Real error code now propagated | Expected (§5.1) |
| Unexpected COV notifications from a Loop or Pulse Converter object | Previously dropped silently | Expected (§5.5) |
10. Migration checklist
Section titled “10. Migration checklist”- Object access — replace every
Build*/Clear*+Send*sequence with a singleSendReadProperty/SendWriteProperty/SendCreateObject; hand-packpropertyArrayslittle-endian; pass the rightserviceType(12/14/15/16); useflagsfor CreateObject; keep writedataTypein 0..12, 27 or 64 and createdataTypein 0..12 or 27; encode CreateObject values in binary; drop all*Asynccalls. - Renames — apply the §2 table. A
search-and-replace covers most of it. Then fix the changed signatures: the leading
networkPortInstanceon the BBMD and foreign-device calls,networkNumber/networkNumberQualityonAddNetworkPortObject,SendDeviceCommunicationControl,SendIAmRouterToNetwork(§2), and the trailingerrorCodeon everyGetProperty*callback (§2). Replace thenetworkTypeargument of every send call and hook callback with a Network Port instance orBACNET_NETWORK_PORT_DEFAULT— a widened integer compiles silently, so search for them (§2); move to the…ForPortmessage callbacks (§2); returnCASBACnetTimefrom the system-time callback (§2); and work through the further changed signatures. Set*Enabled— in C, append a trailingtrueto the three renamed functions. C++ callers are source-compatible.- Return values — optionally consume the new
boolfromRemoveDeviceandUpdateValue(andSetBACnetSCWebSocketStatus);GetBDTEntry/GetFDTEntryandReadTrendLogRecord/ReadTrendLogMultipleRecordnow return a byte count (0 = failure). - Decode consumers — update element and attribute matchers; parse
maxApduLengthAcceptedas an integer byte count; handle"true"/"false"booleans and zero-paddedTime; usecontext=; for JSON, remove single-child list coercions, treat Boolean_valueas a real boolean, and replace== nullchecks with{}checks. - Behavioural audit — review
SetProperty*callbacks that set*errorCode(§5.1); check anything branching on Abort-vs-Reject (§5.2); make Notification ClassRecipient_Listwritable if your device claims AE-CRL-B (§5.6); check any decode consumer that matchedBACnetStackUnknownAPDU(§5.7). - Removed 5.7.0 functions — if you call
SetMaxOutstandingConfirmedRequestsorSetAllowIndefiniteCOVLifetime, delete the call and allow for what it did (§2). - Data links — add the work in section 7 for the links you use: all data links, Network Port objects, BBMD and foreign devices, BACnet MS/TP, BACnet/SC and virtual devices.
- Build flags — version 6 renames the version 5 build flags from
ENABLE_*andCASBACNETSTACK_ENABLE_*toSTACK_OPTION_*(for exampleENABLE_DATA_LINK_LAYER_SCbecomesSTACK_OPTION_DATA_LINK_LAYER_SC, andENABLE_NETWORK_PORT_OBJECTbecomesSTACK_OPTION_OBJECT_TYPE_NETWORK_PORT). There are no aliases: a build that still defines the old names does not get those features. Find each new name insource/CASBACnetStackOptions.h, and see Configuring a build for where the flags go in each build route.
Major version release dates
Section titled “Major version release dates”Note on version numbering. There is no 4.0.x, no 5.0.0, and Version 2 was skipped entirely. The 4.x line begins at 4.1.0 and the 5.x line begins at 5.0.1.
| Major version | First release | Notes |
|---|---|---|
| Version 1 | 2016 (last 1.x release 1.57.7, 2017) | Original CAS BACnet API |
| Version 2 | — | Skipped; never released |
| Version 3 | 2018 (3.0.1) | |
| Version 4 | 2022 (4.1.0) | No 4.0.x was released |
| Version 5 | 2024 (5.0.1) | No 5.0.0 was released. Latest 5.x is 5.7.1 (2026-Jul-31) |
| Version 6 | 6.1.0 | No 6.0.x was released |