Skip to content
Version 6.1.0
Contact Us

Network Port objects

Answering ReinitializeDevice, which activates the changes clients write, is in ReinitializeDevice.

A Network Port object describes one data-link port of the device: its network type (BACnet/IP, MS/TP, BACnet/SC, a virtual network), its network number and, for each type, the port’s own settings such as the IP address or the MS/TP Max_Master. A device with two data links has two Network Port objects. (CASBACnetStackDLL.h calls Max_Master maxManager: BACnetPropertyIdentifier_maxManager, 64.)

The object needs the build option STACK_OPTION_OBJECT_TYPE_NETWORK_PORT. Handling ReinitializeDevice needs STACK_OPTION_DM_RD_REINITIALIZE_DEVICE (or STACK_OPTION_DM_BR_BACKUP_AND_RESTORE, or STACK_OPTION_NM_SCCM_A with BACnet/SC; any of them turns on STACK_OPTION_REINITIALIZE_DEVICE). STACK_OPTION_TARGET_FULL turns all of these on.

Each data link takes one Network Port object, with these values in BACnetStack_AddNetworkPortObject():

Data link networkType protocolLevel Connection string in the message callbacks Chapter
BACnet/IPv4 5 (ipv4) 2 (BACnet application) 6 bytes: IPv4 address, then UDP port, big-endian BACnet/IP
BACnet/IPv6 9 (ipv6) 2 (BACnet application) 18 bytes: IPv6 address, then UDP port, big-endian Data links overview
MS/TP 2 (mstp) 2 only 1 byte: the MS/TP MAC address BACnet MS/TP
BACnet/SC 11 (secureConnect) 2 only The WebSocket URI, at most 255 bytes BACnet/SC

Pass 4194303 as referencePort for each of them: they are the lowest protocol layer. You do not add the Network Port objects of a virtual network (network type 7): BACnetStack_AddVirtualNetwork() and BACnetStack_AddDeviceToVirtualNetwork() create them, as Virtual devices and gateways describes. The Data links overview covers the message callbacks these ports share.

Add the device first, then one Network Port object per port:

DllExport bool BACnetStack_AddNetworkPortObject(
const uint32_t deviceInstance,
const uint32_t objectInstance,
const uint8_t networkType,
const uint8_t protocolLevel,
const uint16_t networkNumber,
const uint8_t networkNumberQuality,
const uint32_t referencePort);
Parameter Meaning
deviceInstance The device that owns the port.
objectInstance The Network Port object’s instance, 0 to 4194302. 4194303 is rejected. This is the value your send and receive callbacks use as networkPortInstance.
networkType A BACnetNetworkType value: 0 = Ethernet, 2 = MS/TP, 5 = IPv4, 7 = virtual, 9 = IPv6, 11 = BACnet/SC. ARCNET, PTP, LonTalk, Zigbee and serial are not implemented and the call returns false.
protocolLevel A BACnetProtocolLevel value: 0 = physical, 1 = protocol, 2 = BACnet application, 3 = non-BACnet application. A BACnet data link is 2. MS/TP and BACnet/SC accept only 2, Ethernet only 0.
networkNumber The port’s network number. Used only at protocol level 2; at other levels it is accepted and ignored, and the property does not exist.
networkNumberQuality 0 = unknown, 1 = learned, 2 = learned and configured, 3 = configured. Larger values are rejected. Pass 0, 0 if you do not assign network numbers.
referencePort The instance of the Network Port object this one sits on, or 4194303 if this is the lowest protocol layer.

The call returns false if the device does not exist, an argument is out of range, or the combination of network type and protocol level is not supported.

Which properties exist depends on the network type and protocol level. The full table, with the callback that serves each property, is in the doc comment of BACnetStack_AddNetworkPortObject() in source/CASBACnetStackDLL.h. In short:

  • The stack serves Network_Type, Protocol_Level, Reference_Port, Network_Number, Network_Number_Quality, Changes_Pending, Command, Command_Validation_Result, APDU_Length, MAC_Address and, on a BACnet/SC port, every SC property. For MAC_Address the stack needs your help on two port types: on an IPv4 port it builds the six octets from your IP_Address and BACnet_IP_UDP_Port, and on an MS/TP port it asks your octet-string Get callback for the one octet.
  • Your callbacks serve the rest, for example Object_Name, IP_Address, IP_Subnet_Mask, BACnet_IP_UDP_Port, FD_BBMD_Address and FD_Subscription_Lifetime on an IPv4 port, and Max_Master and Max_Info_Frames on an MS/TP port.
  • For most properties the stack asks your Get callback first and uses its own value only if your callback does not answer, so you can override a stack-held value. The exceptions are MAC_Address, APDU_Length, Network_Number, Changes_Pending and every BACnet/SC property: the stack always answers those itself, and a value your callback gives for them is not used.

For BACnet/SC ports, Certificates covers the pending changes and the Network Port Command callback, and Operations and troubleshooting lists the SC properties.

The message callbacks name the port by its Network Port object instance:

DllExport void BACnetStack_RegisterCallbackSendMessageForPort(uint16_t(*FPCallbackSendMessageForPort)(const uint8_t* message, const uint16_t messageLength, const uint8_t* connectionString, const uint8_t connectionStringLength, const uint32_t networkPortInstance, bool broadcast));
DllExport void BACnetStack_RegisterCallbackReceiveMessageForPort(uint16_t(*FPCallbackReceiveMessageForPort)(uint8_t* message, const uint16_t maxMessageLength, uint8_t* sourceConnectionString, uint8_t* sourceConnectionStringLength, uint8_t* destinationConnectionString, uint8_t* destinationConnectionStringLength, const uint8_t maxConnectionStringLength, uint32_t* networkPortInstance));
  • In the send callback, use networkPortInstance to pick the socket or serial port to transmit on.
  • In the receive callback, set *networkPortInstance to the instance of the port the frame arrived on.
  • BACNET_NETWORK_PORT_DEFAULT (4194303) means “the default port for this transport” and is unambiguous only when the build contains exactly one data link. A build with more than one (any STACK_OPTION_TARGET_FULL build) must report a real instance; the stack logs an error and drops a frame reported with the default.
  • The callbacks carry no network type: networkPortInstance identifies the port. The networkType you pass to BACnetStack_AddNetworkPortObject() (a BACnetNetworkType, IPv4 = 5) belongs to the Network Port object.

Network Port properties go through the same property callbacks as every other object. Check objectType == 56 (Network Port) and objectInstance to tell your ports apart. The callbacks an IPv4 or MS/TP port uses:

Callback (registration function) Network Port properties
BACnetStack_RegisterCallbackGetPropertyBool Out_Of_Service
BACnetStack_RegisterCallbackGetPropertyEnumerated Reliability, except that the stack reports RENEW_FD_REGISTRATION_FAILURE itself after a failed foreign device renewal, unless the port is Out_Of_Service, until a registration is accepted or the port stops being a foreign device; the host type of FD_BBMD_Address
BACnetStack_RegisterCallbackGetPropertyOctetString IP_Address, IP_Subnet_Mask, IP_Default_Gateway, IP_DNS_Server elements, the IP host of FD_BBMD_Address, the MS/TP MAC_Address
BACnetStack_RegisterCallbackGetPropertyCharacterString Object_Name; a name host of FD_BBMD_Address
BACnetStack_RegisterCallbackGetPropertyUnsignedInteger BACnet_IP_UDP_Port, the port of FD_BBMD_Address, FD_Subscription_Lifetime, Max_Master, Max_Info_Frames, the IP_DNS_Server element count
BACnetStack_RegisterCallbackSetPropertyUnsignedInteger Activation of FD_Subscription_Lifetime, Max_Master, Max_Info_Frames, Network_Number and the port of FD_BBMD_Address
BACnetStack_RegisterCallbackSetPropertyOctetString Activation of an IP host of FD_BBMD_Address, and of an MS/TP MAC_Address assigned by a You-Are
BACnetStack_RegisterCallbackSetPropertyCharacterString, BACnetStack_RegisterCallbackSetPropertyNull Activation of a name host, or no host, of FD_BBMD_Address

FD_BBMD_Address is assembled from several calls, all with propertyIdentifier 418 (BACnetPropertyIdentifier_fdBbmdAddress). BACnet_IP_Global_Address, once you enable it with BACnetStack_SetPropertyEnabled, is assembled from the same calls with propertyIdentifier 407 (BACnetPropertyIdentifier_bacnetIpGlobalAddress), and it reads as host none, port 0 while you serve nothing. The stack does not work out a global address itself: if your application does, make the property read-only with BACnetStack_SetPropertyWritable.

  1. CallbackGetPropertyEnumerated with useArrayIndex false returns the host type: 0 = none, 1 = IP address, 2 = name.
  2. CallbackGetPropertyUnsignedInteger with array index 2 returns the UDP port.
  3. Array index 1 returns the host: 4 octets through CallbackGetPropertyOctetString for an IP address, or a string through CallbackGetPropertyCharacterString for a name.

The Set callbacks receive the same array indexes at activation. If your application does not answer the host type query, the stack answers FD_BBMD_Address from its own foreign-device registration when it has one. If it does not answer FD_Subscription_Lifetime, the stack answers with the time-to-live of its own registration, or 0.

The full signatures are in source/CASBACnetStackDLL.h; each Get callback ends with uint32_t* errorCode. Return false without touching errorCode when a property is not yours to answer.

A client writes Max_Info_Frames; the stack holds it pending and Changes_Pending reads TRUE. ReinitializeDevice or BACnetStack_ActivateNetworkPortPendingChanges() then calls your Set callback: if it returns true the value is active and Changes_Pending is FALSE; if it returns false every value is rolled back and Changes_Pending stays TRUE.

These Network Port properties are writable, and a successful write holds the value pending instead of applying it:

Property Identifier Port Range checked by the stack
FD_BBMD_Address 418 IPv4 —
BACnet_IP_Global_Address 407 IPv4, once you enable it an IP host must be 4 octets
FD_Subscription_Lifetime 419 IPv4 0 to 65535
Max_Master 64 MS/TP 0 to 127
Max_Info_Frames 63 MS/TP 1 to 255
Network_Number 425 any, at protocol level 2 0 to 65534; writable when the device is a router
BBMD_Accept_FD_Registrations 413 IPv4, only while the port is a BBMD —
IP_Address, IP_Subnet_Mask, IP_Default_Gateway 400, 411, 401 IPv4, once you make it writable; read-only while IP_DHCP_Enable reads TRUE 4 octets
IP_DHCP_Enable 402 IPv4, once you make it writable —
BACnet_IP_UDP_Port 412 IPv4, once you make it writable 0 to 65535
IP_DNS_Server 406 IPv4, once you make it writable; read-only while IP_DHCP_Enable reads TRUE 4 octets an element; the whole array at its length, or one element by index 1 to its length; the length does not change
Link_Speed 420 any port that has it, once you make it writable; read-only while Link_Speed_Autonegotiate reads TRUE a finite rate of 0 or more bits per second; when Link_Speeds (421) is enabled, one of the rates it serves
Link_Speed_Autonegotiate 422 any port that has it, once you make it writable —

While one of these is pending, a client’s read returns the written value, but the stack’s own uses of the port’s address keep the address in use. MAC_Address follows the new address once the change is activated. The BACnet/IP data link’s own address and a BBMD’s own BDT entry are read again when you add the port again or register the Get callbacks again; see When the stack reads the port’s own address.

The BACnet/SC properties have their own pending set; see the BACnet/SC chapter.

What happens on a write:

  1. The stack checks the value’s type and range and refuses a bad value with an error. A refused write leaves Changes_Pending unchanged.
  2. The stack checks that the value could ever be activated: CallbackReinitializeDevice must be registered, and so must the Set callback that will take the value (CallbackSetPropertyUnsignedInteger, CallbackSetPropertyBool for IP_DHCP_Enable and Link_Speed_Autonegotiate, CallbackSetPropertyReal for Link_Speed, CallbackSetPropertyOctetString for the addresses (called once per written IP_DNS_Server element, with its array index), or the FD_BBMD_Address callbacks for its host type). If one is missing the write is answered with Abort(other).
  3. The stack stores the value as pending. Your Set callback is not called.
  4. From now on a read of that property returns the pending value without calling your Get callback, and Changes_Pending reads TRUE.

Your Get callbacks keep serving the value in use. Your application needs no pending storage and no Changes_Pending flag of its own.

A client can drop the pending values of one port by writing DISCARD_CHANGES to its Command property, or ask for a check without activating by writing VALIDATE_CHANGES. To take part in those, register BACnetStack_RegisterCallbackNetworkPortCommand() and BACnetStack_RegisterCallbackValidateNetworkPortChanges(); both are optional for IPv4 and MS/TP ports.

Pending values become active in one of two ways:

Any other state, COLDSTART included, leaves the values pending.

Activation is all or nothing. The stack calls your Set callback once for each pending value. Treat that call as “this value is now in use”: apply it to the port and store it in non-volatile memory. If your Set callback returns false for one value, the stack hands back every value you accepted in the same pass by calling the Set callback again with the value that was in use before (for the host-served properties, the value your Get callback reported just before the pass), nothing is activated, and Changes_Pending stays TRUE.

BACnetStack_ActivateNetworkPortPendingChanges() runs the same checks and the same activation without calling CallbackReinitializeDevice, so no password is checked:

DllExport bool BACnetStack_ActivateNetworkPortPendingChanges(const uint32_t deviceInstance, const uint32_t networkPortInstance);

Pass a Network Port instance to activate that port only, or 4194303 to activate every port of the device. Call it from the thread that calls BACnetStack_Tick(), never from inside a stack callback. Call it once your start-up configuration is done, so the device does not boot reporting Changes_Pending for configuration nobody wrote over the network.

The stack does not re-register as a foreign device by itself. When an activated FD_BBMD_Address or FD_Subscription_Lifetime changes the registration, call BACnetStack_SendRegisterForeignDevice() with the new values after BACnetStack_Tick() returns:

DllExport bool BACnetStack_SendRegisterForeignDevice(const uint32_t networkPortInstance, const uint16_t timeToLive, const uint8_t* connectionString, const uint8_t connectionStringLength);

The connection string is the BBMD’s 4-octet IP address followed by its UDP port, big-endian. The call needs BACnetStack_RegisterCallbackGetSystemTime() registered. The BBMD and foreign devices chapter covers registration in detail.

The ReinitializeDevice chapter links a complete example on GitHub: a device whose pending Network Port changes are activated by its ReinitializeDevice handler.

  • Register the Set callbacks before clients can write. Without CallbackSetPropertyUnsignedInteger, or without CallbackReinitializeDevice, writes to the pending properties are answered with Abort(other), because the value could never be activated.
  • Changes_Pending and Network_Number are not asked of your Get callbacks. The stack serves both, so there is nothing to answer for them.
  • Expect a Set call with the old value. When activation is rolled back, the stack calls your Set callback again with the value that was in use before the pass.
  • On an MS/TP port in stack-owned byte mode, the stack adds Subordinate_Proxy_Enable, Manual_Subordinate_Address_Binding, Auto_Subordinate_Discovery and Subordinate_Address_Binding at the first BACnetStack_Tick() in that mode, and serves them itself. A packet-mode MS/TP port has none of them. See BACnet MS/TP.