Device Communication Control
- Specification reference: ANSI/ASHRAE 135-2024, clause 16.1 (DeviceCommunicationControl Service).
What DeviceCommunicationControl does to your device
Section titled “What DeviceCommunicationControl does to your device”A client sends DeviceCommunicationControl with an enableDisable value, an optional duration in minutes and an
optional password. The stack applies the result per device: in a gateway with several devices, only
the device addressed by the request changes state.
enableDisable |
Value | Effect on the device |
|---|---|---|
ENABLE |
0 | Normal communication. Any duration in the request is ignored. |
DISABLE |
1 | Deprecated. Always refused with Error(services, service-request-denied), after your callback has checked the password. |
DISABLE_INITIATION |
2 | The device keeps answering requests but stops initiating messages. |
While a device is in DISABLE_INITIATION, the stack drops every message the device would initiate:
COV and event notifications, unsolicited I-Am and I-Have, and confirmed or unconfirmed requests started by
your own BACnetStack_Send... calls. The stack still sends:
- replies to incoming requests (Simple-ACK, Complex-ACK, Error, Reject, Abort);
- I-Am in answer to Who-Is, and I-Have in answer to Who-Has;
- audit notifications;
- network layer messages.
A dropped message is logged as “Device tried to initiate a request when device communication control has disabled initiation” when debug logging is on.
Communication is restored when any of these happens:
- The duration in the request elapses. The stack checks the deadline on every
BACnetStack_Tick(), so no host action is needed. - The device accepts a DeviceCommunicationControl with
ENABLE. - The device accepts a ReinitializeDevice with
WARMSTARTorCOLDSTART. The stack re-enables communication only after your ReinitializeDevice callback has returned true, so a request with a wrong password does not lift the disable.
What the stack handles and what you provide
Section titled “What the stack handles and what you provide”| Concern | Who | Notes |
|---|---|---|
| Decoding the request, rejecting a malformed one | Stack | A present password must be 1 to 20 characters; a longer or empty one, or one that is not well-formed UTF-8, is rejected before your callback runs. |
Refusing DISABLE |
Stack | Error(services, service-request-denied), after your callback has accepted the password. |
| Refusing a duration on a device with no clock | Stack | If neither CallbackGetSystemTime nor CallbackGetSystemTimeMs is registered, a request other than ENABLE that carries a duration gets Error(services, optional-functionality-not-supported), before your callback runs. |
| Validating the password | You | In CallbackDeviceCommunicationControl. The stack performs no password check of its own. |
Entering and leaving DISABLE_INITIATION, the duration timer, holding back outgoing messages, the Simple-ACK |
Stack | After your callback returns true. |
| Persisting the state across a power cycle | Not done | The state is held in memory; a restart of your application starts with communication enabled. |
The callback runs inside BACnetStack_Tick(), after the decoding and clock checks above have passed. It also sees
a deprecated DISABLE, which the stack refuses once the callback returns, so do not act on every request the
callback accepts. It is a good place for logging and for password-attempt counters.
Server (DM-DCC-B)
Section titled “Server (DM-DCC-B)”Four steps.
1. Build with the option
Section titled “1. Build with the option”DeviceCommunicationControl is compiled in by STACK_OPTION_DM_DCC_DEVICE_COMMUNICATION_CONTROL. Define it in
source/CIBuildSettings.h, or pass it in CASBACNET_EXTRA_OPTIONS to CMake. STACK_OPTION_ENABLE_ALL_BIBBS,
STACK_OPTION_TARGET_FULL and several of the presets define it for you. See
build presets.
2. Enable the service on the device
Section titled “2. Enable the service on the device”Call BACnetStack_SetServiceEnabled with service 17 (DeviceCommunicationControl in
BACnetServicesSupported) for each device that should execute the service. Until you do, the device does
not advertise or execute it.
DllExport bool BACnetStack_SetServiceEnabled(const uint32_t deviceInstance, const uint32_t service, const bool enabled);3. Implement the callback
Section titled “3. Implement the callback”The signature, from source/BACnetCallbackInterface.h:
typedef bool(*FPCallbackDeviceCommunicationControl)(const uint32_t deviceInstance, const uint8_t enableDisable, const char* password, const uint8_t passwordLength, const bool useTimeDuration, const uint16_t timeDuration, uint32_t* errorCode);| Parameter | Meaning |
|---|---|
deviceInstance |
The device the request is addressed to. |
enableDisable |
0 = ENABLE, 1 = DISABLE or 2 = DISABLE_INITIATION. DISABLE reaches the callback only so that the password is checked: when you return true for it, the stack still refuses the request with Error(services, service-request-denied) and changes nothing. A callback that acts on the request itself should ignore DISABLE. |
password, passwordLength |
The password from the request, or NULL and 0 when the request carried none. Compare exactly passwordLength bytes; the bytes are in the character set the client declared. |
useTimeDuration, timeDuration |
Whether a duration was given, and its length in minutes. |
errorCode |
Write a BACnetErrorCode here when you return false. |
Return true to accept the request: the stack applies it and sends a Simple-ACK. Return false to refuse it:
*errorCode |
Sent as |
|---|---|
| 26 password-failure | Error(security, password-failure) |
| 45 optional-functionality-not-supported | Error(services, optional-functionality-not-supported) |
| 29 service-request-denied | Error(services, service-request-denied) |
| anything else, or left unset | Error(security, password-failure) |
Use password-failure both for a wrong password and for a missing one when your device requires a password.
4. Register the callback
Section titled “4. Register the callback”DllExport void BACnetStack_RegisterCallbackDeviceCommunicationControl(bool(*FPCallbackDeviceCommunicationControl)(const uint32_t deviceInstance, const uint8_t enableDisable, const char* password, const uint8_t passwordLength, const bool useTimeDuration, const uint16_t timeDuration, uint32_t* errorCode));Register it on every product that enables the service, including one with no password: a device that should accept any request registers a callback that returns true. Without it, the stack refuses every DeviceCommunicationControl with Error(security, password-failure).
Example
Section titled “Example”#include <stdint.h>#include <string.h>#include <stdbool.h>#include "CASBACnetStackDLL.h"
#define SERVICE_DEVICE_COMMUNICATION_CONTROL 17 /* BACnetServicesSupported */#define ERROR_CODE_PASSWORD_FAILURE 26 /* BACnetErrorCode */
static const char DEVICE_PASSWORD[] = "12345"; /* at most 20 characters */
bool CallbackDeviceCommunicationControl(const uint32_t deviceInstance, const uint8_t enableDisable, const char* password, const uint8_t passwordLength, const bool useTimeDuration, const uint16_t timeDuration, uint32_t* errorCode){ const size_t expectedLength = strlen(DEVICE_PASSWORD);
/* Absent or wrong password: refuse. password is NULL with length 0 when none was sent. */ if (password == NULL || passwordLength != expectedLength || memcmp(password, DEVICE_PASSWORD, expectedLength) != 0) { *errorCode = ERROR_CODE_PASSWORD_FAILURE; return false; }
/* Accepted. Log deviceInstance, enableDisable, useTimeDuration and timeDuration here if wanted. */ return true;}
bool SetUpDeviceCommunicationControl(const uint32_t deviceInstance){ BACnetStack_RegisterCallbackDeviceCommunicationControl(CallbackDeviceCommunicationControl); return BACnetStack_SetServiceEnabled(deviceInstance, SERVICE_DEVICE_COMMUNICATION_CONTROL, true);}To accept requests with a duration, also register a clock with BACnetStack_RegisterCallbackGetSystemTime
(you need it for other time-based features in any case):
DllExport void BACnetStack_RegisterCallbackGetSystemTime(CASBACnetTime(*FPCallbackGetSystemTime)());Client (DM-DCC-A)
Section titled “Client (DM-DCC-A)”DllExport bool BACnetStack_SendDeviceCommunicationControl(uint8_t* sentInvokeId, const uint8_t enableDisable, const bool useTimeDuration, const uint16_t timeDuration, const char* password, const uint8_t passwordLength, const uint8_t* connectionString, const uint8_t connectionStringLength, const uint32_t networkPortInstance, const uint16_t destinationNetwork, const uint8_t* destinationAddress, const uint8_t destinationAddressLength, const uint8_t passwordEncoding);| Parameter | Meaning |
|---|---|
sentInvokeId |
Out: the invoke ID of the request, valid only when the function returns true. |
enableDisable |
0 = ENABLE, 1 = DISABLE (deprecated; current devices refuse it), 2 = DISABLE_INITIATION. |
useTimeDuration, timeDuration |
true and a number of minutes for a timed disable; false for an indefinite one. |
password, passwordLength |
The target’s password, 1 to 20 characters, or NULL and 0 for none. A longer password, or one that is not well-formed UTF-8, makes the call return false. |
connectionString, connectionStringLength |
The datalink address to send to. For BACnet/IP, 6 bytes: the IPv4 address then the port, big-endian (0xC0, 0xA8, 0x01, 0x64, 0xBA, 0xC0 is 192.168.1.100:47808). |
networkPortInstance |
The instance of the Network Port object to send on, the one your application added. BACNET_NETWORK_PORT_DEFAULT (4194303) is accepted only in a build with exactly one data link compiled in; in any other build, including the prebuilt libraries, the call returns false. |
destinationNetwork, destinationAddress, destinationAddressLength |
0, NULL, 0 for a device on the local network; otherwise the remote network number and its address. |
passwordEncoding |
The character set of password: 0 = UTF-8 (ANSI X3.4). Other values change how the target compares the bytes. |
The function returns true when the request was sent. It returns false (with a debug log message) when the
DM-DCC option is not compiled in, no CallbackSendMessageForPort is registered, the connection string is
empty, or the password is longer than 20.
The target’s answer arrives later through the client hooks, matched by invoke ID:
BACnetStack_RegisterHookSimpleAck (with serverAckChoice 17) on success, and BACnetStack_RegisterHookError,
BACnetStack_RegisterHookReject, BACnetStack_RegisterHookAbort or BACnetStack_RegisterHookTimeout otherwise.
#define ENABLE_DISABLE_DISABLE_INITIATION 2 /* BACnetEnableDisable */#define PASSWORD_ENCODING_UTF8 0#define NETWORK_PORT_INSTANCE 1 /* the Network Port object this application added */
bool SilenceDeviceForFiveMinutes(void){ const uint8_t targetAddress[6] = { 0xC0, 0xA8, 0x01, 0x64, 0xBA, 0xC0 }; /* 192.168.1.100:47808 */ const char password[] = "12345"; uint8_t sentInvokeId = 0;
return BACnetStack_SendDeviceCommunicationControl(&sentInvokeId, ENABLE_DISABLE_DISABLE_INITIATION, true, 5, /* useTimeDuration, timeDuration (minutes) */ password, (uint8_t)(sizeof(password) - 1), targetAddress, sizeof(targetAddress), NETWORK_PORT_INSTANCE, 0, NULL, 0, /* local network */ PASSWORD_ENCODING_UTF8);}Practical notes
Section titled “Practical notes”- Ship with a password. With a callback that accepts everything, any device on the network can silence
yours until the duration elapses or someone sends
ENABLEor a warm or cold start. - A disabled device still answers.
DISABLE_INITIATIONstops notifications and outgoing requests, not ReadProperty or WriteProperty. A COV subscriber sees no notifications until the device is re-enabled; the stack does not replay the changes it held back. - Your own client calls are held back too. While your device is in
DISABLE_INITIATION, requests you send withBACnetStack_Send...are dropped, including a DeviceCommunicationControl to another device. - Testing. Any BACnet client that sends DeviceCommunicationControl will do, for example VTS. Check a wrong
password (password-failure),
DISABLE(service-request-denied), a timedDISABLE_INITIATIONthat expires, and a warm start that lifts it. - ReinitializeDevice is described in ReinitializeDevice.
