Skip to content
Version 6.1.0
Contact Us

Breaking changes from version 5 to version 6

  1. Sections 1 to 6 and the checklist: the C API changes every application needs.
  2. Changes by feature, for each feature you use: data links, Network Port objects, BBMD, MS/TP, BACnet/SC, virtual devices and the rest.
  3. 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.

  1. Object Access Send API
  2. Renamed and removed functions
  3. Return-convention changes
  4. The bool enabled parameter on Set*Enabled
  5. Behavioural conformance changes
  6. Decode output (DecodeAsXML / DecodeAsJSON)
  7. Changes by feature
  8. Additions (non-breaking)
  9. Symptom → fix
  10. Migration checklist

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

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.

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.

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) — flags bit0 = use array index.
  • Write element — 19 bytes + value: the read layout plus dataType(1) priority(1) valueLength(2) value(n) — flags bit0 = 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) — flags bit0 = 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.

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 12
uint8_t e[15] = {0};
e[0] = 0x00; // flags: bit0 = use array index
memcpy(e + 1, &objectType, 2);
memcpy(e + 3, &objectInstance, 4);
memcpy(e + 7, &propertyId, 4);
memcpy(e + 11, &arrayIndex, 4); // ignored unless flags bit0 set
BACnetStack_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 15
const char* val = "42";
uint16_t valLen = 2;
uint8_t e[19 + 2] = {0};
e[0] = 0x00; // flags: bit0 = array index, bit1 = priority
memcpy(e + 1, &objectType, 2);
memcpy(e + 3, &objectInstance, 4);
memcpy(e + 7, &propertyId, 4);
memcpy(e + 11, &arrayIndex, 4);
e[15] = 2; // dataType: UnsignedInteger
e[16] = 0; // priority
memcpy(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 type
const char* nm = "MyName";
uint16_t nmLen = 6;
uint8_t iv[13 + 6] = {0};
iv[0] = 0x00; // element flags: no array index, no priority
memcpy(iv + 1, &propertyId, 4); // propertyIdentifier
// iv[5..8]: propertyArrayIndex, unused (0)
iv[9] = 7; // dataType: CharacterString
iv[10] = 0; // priority, unused
memcpy(iv + 11, &nmLen, 2); // valueLength
memcpy(iv + 13, nm, nmLen); // value: the UTF-8 bytes
BACnetStack_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.

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.

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

BACnetStack_SendUnsubscribedUnconfirmedCOVNotification() changed in four ways, so every existing call has to be rewritten:

  • subscriberProcessIdentifier and timeRemaining are new parameters (after initiatingDeviceInstance and after the monitored object). Both were hardwired to 0; pass 0 for both to keep the old behaviour.
  • A packed property list replaces the single propertyIdentifier. properties, propertyCount and propertiesLength take propertyCount elements of 9 bytes each, little-endian: flags(1) propertyIdentifier(4) propertyArrayIndex(4), with flags bit0 = 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.
  • broadcast is 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 a COVU_Recipients entry configured as a broadcast address could not be served at all. In the C export it sits after networkPortInstance, not at the end; the C++ overload and the Node.js wrapper default it to false. Pass false to keep the v5 behaviour.
  • networkType becomes networkPortInstance, 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 in CASBACnetStackDLL.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_DEFAULT is ambiguous: the send logs an error and returns false.

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.

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_DEFAULT means 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.

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.


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 to true, 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 behaviour
BACnetStack_SetSegmentationEnabled(deviceInstance, segSupported, maxSegAccepted,
apduSegTimeout, proposedWindow, actualWindow,
maxSegTransmittable, /*enabled*/ true);

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 (class property), noSpaceToWriteProperty (class resources), and abortOther (→ Abort-PDU). Every other code was silently replaced with property / writeAccessDenied.
  • v6: any code the callback sets is emitted, with the class clause 18 lists it under: for example deviceBusy with device, unknownObject and busy with object, and noSpaceToWriteProperty with resources. A code that clause 18 lists under more than one class (such as valueOutOfRange or writeAccessDenied) is sent with class property. abortOther still produces an Abort-PDU. A callback that returns false without setting a code still yields property / writeAccessDenied.
  • Action: audit SetProperty* callbacks that set *errorCode outside the old whitelist expecting coercion to writeAccessDenied. 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 BACnetFileAccessMethodParameters is stream = 0, record = 1 — the inverse of the property enum BACnetFileAccessMethod, 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.

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 gets Error(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 in errorCode, with its clause-18 class (for example invalid-file-start-position (11) with services, file-full (128) with object, device-busy with device). If you leave errorCode untouched, or set other or a code that cannot be sent as an Error (abortOther included), the answer is Error(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.

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-case Ack (the PDU suffix stays upper); confirmed service request containers gain a Request suffix; all-caps COV → camelCase Cov; OctString → OctetString; Integer → SignedInteger; maxAdpuLengthAccepted → maxApduLengthAccepted (typo fix); hundrethSeconds → hundredthSeconds; self-named StreamAccess / RecordAccess attributes → context="0".
  • Value shapes: ObjectIdentifier gains value="<objectType>,<objectInstance>"; Boolean value= is "true" / "false" rather than '1' / '0'; maxApduLengthAccepted is the decoded byte count (e.g. 1476), not the raw hex enum; Real / Double always include a decimal point (50 → 50.0); Time text is zero-padded; maxSegmentsAccepted is a decoded label.
  • JSON typing: list containers (ListOf…, NetworkNumbers, PriorityArray) always emit arrays — single-child coercion is gone; Boolean _value is a real JSON boolean; empty elements render as {}, never null.
  • 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.


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.

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() and BACnetStack_RegisterCallbackSendMessageForPort() replace RegisterCallbackReceiveMessage and RegisterCallbackSendMessage. Both carry a networkPortInstance, the instance of a Network Port object, instead of the old networkType byte. The older callbacks without that parameter are gone. See The networkPortInstance.
  • Add a Network Port object for each port. BACnetStack_AddNetworkPortObject() takes seven parameters. networkNumber and networkNumberQuality were added (the older guides showed five); pass 0, 0 (unknown) to reproduce the old number-less behaviour. The new parameters sit before referencePort: 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. A networkType value of 0 still 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.

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_Frames or Network_Number stays inside the stack, reads back as the written value, and sets Changes_Pending to TRUE. Your Set callback is called only when the change is activated, not when it is written.
  • Changes_Pending and Network_Number are 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’ errorCode is uint32_t* instead of unsigned int*.
  • CASBACnetStack_SendRegisterForeignDevice() is now BACnetStack_SendRegisterForeignDevice() and takes the Network Port instance first.
  • The header and the standard name the property Max_Manager (BACnetPropertyIdentifier_maxManager, 64); this manual calls it Max_Master, as the MS/TP pages do.
  • The CASBACnetStackExampleConstants names are gone. Property identifiers are in the BACnetPropertyIdentifier enumeration in CASBACnetStackDLL.h. Other values (object types, error codes, reinitialized states) are plain numbers in this chapter.

Applies to every application that accepts ReinitializeDevice and uses Network Port objects.

The chapter: ReinitializeDevice.

  • Pending changes are applied after your CallbackReinitializeDevice accepts the request, so a request with a wrong password activates nothing. This is the change from the 2020 PDF Network Port Object and ReinitializeDevice.

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_SendReadBroadcastDistributionTable and BACnetStack_SendReadForeignDeviceTable have 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_GetBDTEntry and BACnetStack_GetFDTEntry return uint32_t, the number of address bytes written (6), or 0 on failure. They returned bool.
  • BACnetStack_ClearForeignDeviceTable and BACnetStack_ClearBroadcastDistributionTable are renamed BACnetStack_ResetForeignDeviceTable and BACnetStack_ResetBroadcastDistributionTable.
  • You no longer enable the BBMD properties yourself. BACnetStack_SetBBMD enables 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’s SetPropertyEnabled step 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 the STACK_OPTION_PROFILE_B_BBMD profile, which turns it on. Either needs STACK_OPTION_DATA_LINK_LAYER_IPV4, and turns on the Network Port object type.

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 (the BACnetMSTPCounter_InTokenRing entry of BACnetStack_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.

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_SetFileObjectStreamAccessContents is gone. The stack no longer holds a copy of your certificate bytes. It asks for them on demand through BACnetStack_RegisterCallbackReadFile / RegisterCallbackWriteFile (plus ...ReadFileRecord / ...WriteFileRecord for record-access files, which BACnet/SC certificate files do not use). See §16.2.
  • Certificate File objects are created with BACnetStack_AddFileObject, not BACnetStack_AddObject. AddFileObject is the only call that also stores File_Access_Method and File_Read_Only. Without it, an AtomicWriteFile/AtomicReadFile against 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_FILE command, which reaches the host through BACnetStack_RegisterCallbackNetworkPortCommand (§16.6).
  • SC_Direct_Connect_Initiate_Enable and SC_Direct_Connect_Accept_Enable are writable. A WriteProperty to either sets Changes_Pending = TRUE. The new value takes effect on the data link on ReinitializeDevice(ACTIVATE_CHANGES) or WARMSTART, 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 existing Operational_Certificate_File / Issuer_Certificate_Files / Certificate_Signing_Request_File object, without rebinding to a different File object, used to be invisible to Changes_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, and STACK_OPTION_NM_SCCM_A (or STACK_OPTION_ENABLE_ALL_BIBBS). Without STACK_OPTION_NM_SCCM_A, BACnetStack_AddFileObject returns false and the certificate File objects cannot be created; it also turns on STACK_OPTION_ATOMIC_FILE_IO and 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). SetBACnetSCHubFunctionConfig and SetBACnetSCDirectConnectConfig lost their accept-URI parameters: add accept URIs with BACnetStack_AddBACnetSCAcceptUri (further changed signatures).

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:

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_SendDeviceCommunicatonControl is now BACnetStack_SendDeviceCommunicationControl (the missing i is fixed; there is no alias). See section 2.
  • The client function takes a Network Port instance, not a network type. The networkType argument (0 = BACnet/IP, 1 = MS/TP) became networkPortInstance; pass BACNET_NETWORK_PORT_DEFAULT (4194303) for a single-port device. A trailing passwordEncoding argument 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 DISABLE value itself with Error(services, service-request-denied). Your callback still sees a DISABLE request, so that the password is checked first; when it returns true, the stack then refuses the request and changes nothing. Do not act on DISABLE in the callback.

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 fpRegisterCallbackLogDebugMessage and fpAddDevice, function pointers that their loader filled in. In version 6 call the exports by their full names, BACnetStack_RegisterCallbackLogDebugMessage() and BACnetStack_AddDevice(). The generated adapters use the same names.
  • Behaviour without a callback. In a build with the option, the stack prints every message to stdout until you register a callback.
  • The callback survives BACnetStack_Reset(). It is the only callback that does.

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) and CASBACnetStackExampleConstants came from an example application, not the stack. Call the BACnetStack_* 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_OBJECT and ENABLE_FILE_OBJECT. Version 6 renames them STACK_OPTION_DATA_LINK_LAYER_SC, STACK_OPTION_OBJECT_TYPE_NETWORK_PORT and STACK_OPTION_OBJECT_TYPE_FILE, with no ENABLE_* aliases, so a build script that still sets the ENABLE_* 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 from RegisterCallbackReadFile and accept writes in RegisterCallbackWriteFile.
  • Property identifiers in Appendix A. It gave MAC_Address 332, APDU_Length 388, Network_Number 333 and Network_Number_Quality 339. The values are MAC_Address 423, APDU_Length 399, Network_Number 425, Network_Number_Quality 426 and Network_Type 427. It also listed Object_Name as writable on a Network Port; it is read-only by default, and BACnetStack_SetPropertyWritable makes it writable.
  • Optional flags in Appendix C.1. ENABLE_BACNET_SC_HUB_FUNCTION, ENABLE_BACNET_SC_DIRECT_CONNECT and ENABLE_BACNET_SC_DEBUG_LOGGING do 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 uses STACK_OPTION_API_DEBUG_LOGGING.

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

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)

  1. Object access — replace every Build* / Clear* + Send* sequence with a single SendReadProperty / SendWriteProperty / SendCreateObject; hand-pack propertyArrays little-endian; pass the right serviceType (12/14/15/16); use flags for CreateObject; keep write dataType in 0..12, 27 or 64 and create dataType in 0..12 or 27; encode CreateObject values in binary; drop all *Async calls.
  2. Renames — apply the §2 table. A search-and-replace covers most of it. Then fix the changed signatures: the leading networkPortInstance on the BBMD and foreign-device calls, networkNumber / networkNumberQuality on AddNetworkPortObject, SendDeviceCommunicationControl, SendIAmRouterToNetwork (§2), and the trailing errorCode on every GetProperty* callback (§2). Replace the networkType argument of every send call and hook callback with a Network Port instance or BACNET_NETWORK_PORT_DEFAULT — a widened integer compiles silently, so search for them (§2); move to the …ForPort message callbacks (§2); return CASBACnetTime from the system-time callback (§2); and work through the further changed signatures.
  3. Set*Enabled — in C, append a trailing true to the three renamed functions. C++ callers are source-compatible.
  4. Return values — optionally consume the new bool from RemoveDevice and UpdateValue (and SetBACnetSCWebSocketStatus); GetBDTEntry / GetFDTEntry and ReadTrendLogRecord / ReadTrendLogMultipleRecord now return a byte count (0 = failure).
  5. Decode consumers — update element and attribute matchers; parse maxApduLengthAccepted as an integer byte count; handle "true" / "false" booleans and zero-padded Time; use context=; for JSON, remove single-child list coercions, treat Boolean _value as a real boolean, and replace == null checks with {} checks.
  6. Behavioural audit — review SetProperty* callbacks that set *errorCode (§5.1); check anything branching on Abort-vs-Reject (§5.2); make Notification Class Recipient_List writable if your device claims AE-CRL-B (§5.6); check any decode consumer that matched BACnetStackUnknownAPDU (§5.7).
  7. Removed 5.7.0 functions — if you call SetMaxOutstandingConfirmedRequests or SetAllowIndefiniteCOVLifetime, delete the call and allow for what it did (§2).
  8. 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.
  9. Build flags — version 6 renames the version 5 build flags from ENABLE_* and CASBACNETSTACK_ENABLE_* to STACK_OPTION_* (for example ENABLE_DATA_LINK_LAYER_SC becomes STACK_OPTION_DATA_LINK_LAYER_SC, and ENABLE_NETWORK_PORT_OBJECT becomes STACK_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 in source/CASBACnetStackOptions.h, and see Configuring a build for where the flags go in each build route.

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