Skip to content
Version 6.1.0
Contact Us

Serving property values

When a client reads or writes a property on your device, the stack either answers from its own stored state or calls one of your callbacks. This guide explains how that split works, how your Get and Set property callbacks are called, and what their return values and error codes mean. It assumes you have already configured the stack as described in Stack lifecycle. For complete, working programs, see the example projects on Chipkin’s GitHub.

Each object type has a page under Objects and properties that says how to add it, which callbacks serve its values and which properties the stack holds itself. For an object created with BACnetStack_AddObject(), the stack stores and serves only:

  • ObjectIdentifier, ObjectType and PropertyList;
  • StatusFlags, which the stack assembles by reading EventState, Reliability and OutOfService back through your callbacks;
  • PriorityArray and CurrentCommandPriority of a commandable object, which the stack also assembles from your callbacks (see Commandable objects below).

Your callbacks serve everything else. ObjectName is the one every object needs: if no BACnetStack_RegisterCallbackGetPropertyCharacterString() callback supplies it, the stack answers Object <type>,<instance> with the numeric object type, for example Object 2,1 for Analog Value 1.

Object types that have their own constructor serve more from the stack. Their documentation has its own table of which properties come from where. See, for example, BACnetStack_AddNetworkPortObject() and BACnetStack_AddChannelObject().

If a callback is never registered, or is registered as NULL, the stack does not call it and falls back to its own handling. Some properties are then served from the stack’s stored state. Others have no source in the stack, and the request fails. If your application owns a property, register the matching callback.

Sequence: a BACnet client sends ReadProperty for Analog Input 1 Present_Value; the stack calls your GetPropertyReal callback, which returns true and 21.5; the stack answers ReadProperty-ACK 21.5. If the callback returns false with an errorCode, the stack answers with an Error.

The stack calls a Get callback, such as FPCallbackGetPropertyReal, when it needs the value of a property your application serves. That happens in response to a ReadProperty or ReadPropertyMultiple request. It can also happen while the stack builds an outgoing message: BACnetStack_SendIAm() is one function that calls Get callbacks. Do not assume a callback can only arrive during BACnetStack_Tick().

The first four arguments, deviceInstance, objectType, objectInstance and propertyIdentifier, identify what is being read. If your application models several devices, use deviceInstance to tell them apart; callbacks carry no user-data pointer.

Write the value through the out-parameter and return true. Return false when you are not supplying a value. What the stack does next depends on errorCode (see The errorCode out-parameter below):

  • If you set errorCode, the stack answers the read with that error straight away.
  • If you leave it unset, the stack falls back to the value it stores for the property, or to its default. The exceptions are properties whose value the stack must not make up, including PresentValue, NumberOfStates, RelinquishDefault, LocalDate and LocalTime, the car position, direction, door and passenger alarm properties of Lift and Escalator objects, and a File object’s File_Type, File_Size, Modification_Date, Archive, Read_Only and Record_Count. For those, a declined read is answered with read-access-denied rather than a made-up value. (If no callback is registered at all, they answer value-not-initialized.)
bool GetPropertyReal(const uint32_t deviceInstance, const uint16_t objectType,
const uint32_t objectInstance, const uint32_t propertyIdentifier, float* value,
const bool useArrayIndex, const uint32_t propertyArrayIndex, uint32_t* errorCode)
{
if (objectType == 2 /* analogValue */ &&
propertyIdentifier == BACnetPropertyIdentifier_presentValue) {
*value = MyLookUpValue(objectInstance);
return true;
}
return false; /* Not a value this application serves. */
}

For the variable-length types (CharacterString, OctetString and BitString), the callback receives a buffer and a maxElementCount. Write at most maxElementCount elements, and set valueElementCount to the number you wrote. The CharacterString callback also sets encodingType; 0 means UTF-8, and FPCallbackGetPropertyCharacterString lists the other values.

Pointer arguments are only valid for the duration of the call. Copy anything you need to keep. Do not call back into the stack from inside a callback.

A WriteProperty for Analog Value 1 Present_Value 22.5: the stack checks that the property is writable and WriteProperty is enabled, and refuses with an Error before calling you if not. Then one of two outcomes: your Set callback returns true and the stack sends a SimpleAck, or it returns false with an errorCode and the stack sends an Error.

The stack calls a Set callback, such as FPCallbackSetPropertyReal, in response to a WriteProperty or WritePropertyMultiple request. For a client to write a property, two things must be true:

  • the property is writable, set with BACnetStack_SetPropertyWritable(). The exception is a property that becomes writable when its object is out of service, such as Present_Value and Reliability of an input or value object: it accepts a write while Out_Of_Service is true, without this call;
  • the write service is enabled with BACnetStack_SetServiceEnabled(). ReadProperty is enabled automatically, but WriteProperty and WritePropertyMultiple are not.

The callback receives the new value as an input. Store it and return true. Return false if you did not store it, and set errorCode to say why.

The priority argument is only used when the property is commandable. See Commandable objects below.

Every Get and Set property callback ends in uint32_t* errorCode. That value is a BACnet error code. The stack encodes it into the BACnet-Error response it sends back to the client that made the request. It is not a channel for host or system errors that have no BACnet meaning.

  • Write a bare error code. The stack works out the error class from the code, using the class clause 18 lists for it: device for device-busy and operational-problem, object for unknown-object, resources for the no-space and out-of-memory codes, security for password-failure and access-denied, services for service-request-denied and parameter-out-of-range, and property for any code clause 18 does not place elsewhere.
  • The error code abort-other (56) makes the stack answer with an Abort instead of an Error.
  • Set errorCode only when you are rejecting the request. Leaving it untouched on success is correct.
  • For a Get callback, the stack presets errorCode to success before the call. A false return that leaves it at success means “no value from me”, and the fallback described in How a Get callback is called applies.

The error code values are listed on the Enumerations page.

bool SetPropertyReal(const uint32_t deviceInstance, const uint16_t objectType,
const uint32_t objectInstance, const uint32_t propertyIdentifier, const float value,
const bool useArrayIndex, const uint32_t propertyArrayIndex, const uint8_t priority,
uint32_t* errorCode)
{
if (value < 0.0f || value > 100.0f) {
*errorCode = 37; /* valueOutOfRange */
return false;
}
MyStoreValue(objectInstance, value);
return true;
}

Each Get and Set property callback has two array arguments:

  • useArrayIndex is false when the request is for the whole property, and true when it is for a single element.
  • propertyArrayIndex is the element’s index when useArrayIndex is true. Elements start at 1.

A request for index 0 asks for the array’s length. The stack does not send that to the typed callback. It calls your FPCallbackGetPropertyUnsignedInteger callback, with useArrayIndex true and propertyArrayIndex 0, and you return the element count. The stack then calls the typed callback once per element. So for any array property you serve, answer index 0 in your UnsignedInteger callback and each element in the callback for the element’s datatype.

A few properties document an exception to this convention. Check the documentation of the function that creates the object.

A 16-slot priority array: priority 8 holds 72.0 and priority 16 holds 68.0, the rest are NULL. Present_Value is 72.0, because the highest priority that is not NULL wins. Relinquish_Default, 70.0, is used only when every slot is NULL.

Analog Output, Binary Output, Multi-state Output, Access Door, Lighting Output and Binary Lighting Output objects are always commandable. The Value object types become commandable once you enable both PriorityArray and RelinquishDefault on the object with BACnetStack_SetPropertyEnabled().

For a commandable object, the stack never calls your PresentValue callback. It reads the priority array instead, and reports the highest-priority slot that is not NULL, falling back to RelinquishDefault. For each slot it calls two callbacks, both with propertyIdentifier = BACnetPropertyIdentifier_priorityArray, useArrayIndex = true and a propertyArrayIndex from 1 to 16:

  1. FPCallbackGetPropertyBool, for the “is this slot NULL” flag. Set *value to true when the slot is empty.
  2. The typed Get callback for the object’s datatype, for the slot’s value.
if (propertyIdentifier == BACnetPropertyIdentifier_priorityArray && useArrayIndex) {
*value = !MySlotHasValue(objectType, objectInstance, propertyArrayIndex); /* true = NULL */
return true;
}

Your typed Get callback must also serve RelinquishDefault, which the stack reads when every slot is NULL.

When a client commands the object, your typed Set callback receives the write with its priority. When a client relinquishes a slot by writing NULL, the stack calls your FPCallbackSetPropertyNull callback, registered with BACnetStack_RegisterCallbackSetPropertyNull().

The stack picks the callback by the property’s datatype. Register a Get callback for each datatype you serve, and the matching Set callback for each one clients can write.

Datatype Get callback Set callback
Real FPCallbackGetPropertyReal FPCallbackSetPropertyReal
Double FPCallbackGetPropertyDouble FPCallbackSetPropertyDouble
Unsigned FPCallbackGetPropertyUnsignedInteger FPCallbackSetPropertyUnsignedInteger
Signed FPCallbackGetPropertySignedInteger FPCallbackSetPropertySignedInteger
Enumerated FPCallbackGetPropertyEnumerated FPCallbackSetPropertyEnumerated
Boolean FPCallbackGetPropertyBool FPCallbackSetPropertyBool
CharacterString FPCallbackGetPropertyCharacterString FPCallbackSetPropertyCharacterString
OctetString FPCallbackGetPropertyOctetString FPCallbackSetPropertyOctetString
BitString FPCallbackGetPropertyBitString FPCallbackSetPropertyBitString
Date FPCallbackGetPropertyDate FPCallbackSetPropertyDate
Time FPCallbackGetPropertyTime FPCallbackSetPropertyTime
DateTime FPCallbackGetPropertyDate and FPCallbackGetPropertyTime
ObjectIdentifier FPCallbackGetPropertyObjectIdentifier FPCallbackSetPropertyObjectIdentifier
Null FPCallbackSetPropertyNull
BACnetLightingCommand FPCallbackGetPropertyLightingCommand FPCallbackSetPropertyLightingCommand
BACnetAuthenticationFactorFormat FPCallbackGetPropertyAuthenticationFactorFormat

Each type is registered with the BACnetStack_RegisterCallback* function of the same name, for example BACnetStack_RegisterCallbackGetPropertyReal().

PresentValue follows the object type’s datatype. For example, Analog Input, Analog Output and Analog Value objects use Real; Binary Input, Binary Output and Binary Value objects use Enumerated; and Multi-state Input, Multi-state Output and Multi-state Value objects use Unsigned. BACnetStack_AddObject() has the full table.