Skip to content
Version 6.1.0
Contact Us

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”

While initiation is disabled, a device still sends replies to requests, I-Am in answer to Who-Is, I-Have in answer to Who-Has and network layer messages, but holds back COV notifications, event notifications, unsolicited I-Am and requests your application sends. Communication is enabled again when the duration ends, on DeviceCommunicationControl ENABLE, or on ReinitializeDevice WARMSTART or COLDSTART.

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:

  1. The duration in the request elapses. The stack checks the deadline on every BACnetStack_Tick(), so no host action is needed.
  2. The device accepts a DeviceCommunicationControl with ENABLE.
  3. The device accepts a ReinitializeDevice with WARMSTART or COLDSTART. 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.

Four steps.

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.

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

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.

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

#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)());
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);
}
  • 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 ENABLE or a warm or cold start.
  • A disabled device still answers. DISABLE_INITIATION stops 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 with BACnetStack_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 timed DISABLE_INITIATION that expires, and a warm start that lifts it.
  • ReinitializeDevice is described in ReinitializeDevice.