Application Programming Interface ¶ BACnet objects ¶ BACnet objects are represented as function block instances put in the object tree in conjunction with an CODESYS BACnet2 object device (like “BACnet2 Analog Value”). BACnet objects provide a set of properties - for details see the BACnet-Standard ANSI/ASHRAE Standard 135-2020 . Names of CODESYS BACnet2 object function blocks follows the BACnet-Standard except whitespace (” “) is removed, and a prefix BACnet is added. Example: BACnet-Standard: “Analog Value” (Object Type) - see ANSI/ASHRAE Standard 135-2020 chapter 12.4 CODESYS BACnet2: function block BACnetAnalogValue Those BACnet object function blocks represents the property access for their properties as either value access (FUNCTION_BLOCK PROPERTY with Get / Set semantic) or functional access (FUNCTION_BLOCK PROPERTY providing specific, functional interface). Examples: value access - Command.ProfileLocation := “”; functional access - Command.ActionText.AddElement(“text”); This property access is using data types defined in CmpBACnet2 in most cases, only some aspects of property access use IEC-61311 standard types for convenience (STRING and WSTRING might be the most prominent examples). Furthermore the existance, writeability and eventually persistance of those BACnet properties can be controlled by the application (lets call this property attributes later on). For a subset of the properties the initial value can be configured using the configurator within the CODESYS engineering tool - this applies to mostly all properties of scalar / simple types, but not to properties of complex types. The property attributes can be configured for all properties using the configurator within the CODESYS engineering tool - see folder “propertyAttributes”. Furthermore the property attributes can also be controlled by application code, for more details see IBACnetPropertyConfiguration and BACnetDefaultImpl.PropertyConfiguration / BACnetDefaultImpl.PropertyConfigurationMostlyAllWritable. BACnet types (of BACnet object properties) defined in the BACnet-Standard falls into one of the following categories: simple type - for example see BACnet-Standard: BOOLEAN, Unsigned, REAL, STRING, WSTRING complex type not containing SEQUENCE ’s (of other elements - for example see BACnet-Standard: BACnetObjectPropertyReference complex type containing SEQUENCE ’s (of other elements) - for example see BACnet-Standard: BACnetActionList dynamic type - see BACnet-Standard: ANY SEQUENCE is a concept of the BACnet-Standard represented as POINTER TO type in CODESYS BACnet2 resp. CmpBACnet2 types. BACnet object properties defined in the BACnet-Standard falls into one of these categories: single value fix size array - see BACnet-Standard: BACnetARRAY[?] of - example “Analog Value Object Type” - Event_Message_Texts_Config (BACnetARRAY[3] of CharacterString) dynamic size array - see BACnet-Standard: BACnetARRAY[N] of - example “Global Group Object Type” - Group_Members (BACnetARRAY[N] of BACnetDeviceObjectPropertyReference) list (always dynamic size) - see BACnet-Standard: BACnetLIST of - example “Calendar Object Type” - Date_List (BACnetLIST of BACnetCalendarEntry) property access and content data memory ¶ The property access is often implemented using the data type CmpBACnet2.IEC_BACNET_PROPERTY_CONTENTS in some way. CmpBACnet2.IEC_BACNET_PROPERTY_CONTENTS determines the BACnet type used in the member tag , the number of elements in the member nElements and refers to the content data with the member buffer (of type CmpBACnet2.IEC_BACNET_BUFFER). Memory for CmpBACnet2.IEC_BACNET_BUFFER.pBuffer is either provided by the application or returned by the BACnet API (and needs to be released by the application using FreeStackAllocatedMemory in this case). In case the BACnet API returns the memory for CmpBACnet2.IEC_BACNET_BUFFER.pBuffer, the whole content data are put in one contiguous content data memory block, even if the content data itself contains POINTER’s, so FreeStackAllocatedMemory(IEC_BACNET_PROPERTY_CONTENTS.buffer.pBuffer) is sufficient to release all content data memory. In case the application provides content data memory - for example for write property access - it is wise to avoid dynamically (heap) allocated content data memory, but to work with stack allocated content data memory instead. This approach avoids the hazzle to do complex deep dealloc of application content data memory. Please be aware: in case application provided, dynamically (heap) allocated content data memory is inevitable it’s up to your diligence to do proper deep dealloc of application content data memory to avoid memory leaks. BACnetARRAY[] of vs. BACnetLIST of properties ¶ The BACnet-Standard states: citation: The difference between a “BACnetARRAY” property and a “BACnetLIST” property is that the elements of the array can be uniquely accessed by an array index while the elements of the “BACnetLIST” property can only be positionally accessed using the ReadRange service. Moreover, the number of elements in the BACnetARRAY may be ascertained by reading the array index 0, while the number of elements present in a “BACnetLIST” property can only be determined by reading the entire property value and performing a count. end of citation. For more details see ANSI/ASHRAE Standard 135-2020 chapter 12.1.5 Array and List Properties. The BACnet-Standard enforces this significant difference between BACnetARRAY and BACnetLIST by means of BACnet wire telegram definitions (APDU’s) - so a BACnet client can not do a index based property access for BACnetLIST properties (on the wire directly). Please keep in mind, that BACnetARRAY[] of element ordering might be relevant. property type ANY ¶ This property type allows to represent property values of any (other) type defined in the BACnet-Standard, which makes the correct handling a bit more challenging. The property access for those properties is implemented using functional access (with Get/Set semantic) with the argument type CmpBACnet2.IEC_BACNET_PROPERTY_CONTENTS. As described before CmpBACnet2.IEC_BACNET_PROPERTY_CONTENTS determines the BACnet type used in the member tag , the number of elements in the member nElements and refers to the content data with the member buffer (of type CmpBACnet2.IEC_BACNET_BUFFER). So it is up to the application code to get this right. Fortunately the BACnet-Standard is utilizing this concept of ANY only for the “Schedule Object Type” properties Present_Value and Schedule_Default, which limits the hazzle to a very small subset of the API. application programming interface conventions for property access ¶ It had been laid out before that property access is represented as value access (Get / Set) or functional access . Within the aspect of property access there is a need to spend some thoughts about memory (allocation / deallocation) and memory ownership transfer from CODESYS BACnet2 to the application. Property access is represented as value access for the following subset of all BACnet properties: single value of simple type or complex type not containing SEQUENCE’s fix size array of simple type or complex type not containing SEQUENCE’s whereas of the name of the value access FUNCTION_BLOCK PROPERTY follows the BACnet-Standard except underscore (“_”) is removed. Example: BACnet-Standard: “Analog Value Object Type” property Out_Of_Service (type BOOLEAN) CODESYS BACnet: function block BACnetAnalogValue PROPERTY OutOfService (type IEC-61311 BOOL) All property access of the types listed above provides pure value semantics and such no memory ownership transfer is involved. Please note: for fix size array properties the value access get / set the whole array (by value). All other property access existent at the CODESYS BACnet2 API is represented as functional access for: single value of complex type containing SEQUENCE’s fix size array properties of complex type containing SEQUENCE’s dynamic size array properties list properties whereas of the name of the functional access FUNCTION_BLOCK PROPERTY follows the BACnet-Standard except underscore (“_”) is removed. For functional access : the related FUNCTION_BLOCK PROPERTY provides a specific, functional interface containing appropriate METHODs memory ownership transfer is involved at some points (METHODs) For BACnet single value properties of complex type containing SEQUENCE’s the property access is represented as functional access with the METHODs listed below: GetContent() - get the property value, passing memory ownership to the caller via a POINTER TO, requiring FreeStackAllocatedMemory after processing SetContent() - set the property value Example: BACnet-Standard: “Analog Value Object Type” property Priority_Array(BACnetPriorityArray) CODESYS BACnet: function block BACnetAnalogValue TODO Interface - PriorityArray.GetContent() - PriorityArray.SetContent() For BACnet fix size array properties of complex type containing SEQUENCE’s the property access is represented as functional access with the METHODSs listed below: GetContent() - get the whole array, passing memory ownership to the caller via a POINTER TO, requiring FreeStackAllocatedMemory after processing SetContent() - set the whole array GetCount() - get the number of elements in the array GetElement() - get array element at index (0 .. number of elements-1), passing memory ownership to the caller via a POINTER TO, requiring FreeStackAllocatedMemory after processing UpdateElement() - update array element at index (0 .. number of elements-1) Example: BACnet-Standard: “Schedule Object Type” property Weekly_Schedule(BACnetARRAY[7] of BACnetDailySchedule) CODESYS BACnet: function block BACnetSchedule PROPERTY WeeklySchedule of type IBACnetARRAYF_of_BACnetDailySchedule - WeeklySchedule.GetContent() - WeeklySchedule.SetContent() - WeeklySchedule.GetCount() - WeeklySchedule.GetElement() - WeeklySchedule.UpdateElement() For BACnet dynamic size array properties the property access is represented as functional access with the METHODs listed below: GetContent() - get the whole array, passing memory ownership to the caller via a POINTER TO, requiring FreeStackAllocatedMemory after processing SetContent() - set the whole array GetCount() - get the number of elements in the array GetElement() - get array element at index (0 .. number of elements-1), if element type is complex type containing SEQUENCE’s passing memory ownership to the caller via a POINTER TO, requiring FreeStackAllocatedMemory after processing UpdateElement() - update array element at index (0 .. number of elements-1) InsertElement() - insert array element at index (0 .. number of elements-1) AddElement() - add element at the end off array RemoveElement() - remove array element at index (0 .. number of elements-1) Example: BACnet-Standard: “Global Group Object Type” property Group_Members (BACnetARRAY[N] of BACnetDeviceObjectPropertyReference) CODESYS BACnet: function block BACnetGlobalGroup PROPERTY GroupMembers of type IBACnetARRAYN_of_BACnetDeviceObjectPropertyReference - GroupMembers.GetContent() - GroupMembers.SetContent() - GroupMembers.GetCount() - GroupMembers.GetElement() - GroupMembers.UpdateElement() - GroupMembers.InsertElement() - GroupMembers.AddElement() - GroupMembers.RemoveElement() Why is the GetElement() passing memory ownership to the caller in some cases? Having a value semantic requires the caller to know the needed memory size to provide. For complex type containing SEQUENCE’s is not possible for the caller to determine the memory size prior the get the element value - sorry for this inconvenience. For BACnet list properties properties the property access is represented as functional access with the METHODs listed below: GetContent() - get the whole list, passing memory ownership to the caller via a POINTER TO, requiring FreeStackAllocatedMemory after processing SetContent() - set the whole list GetCount() - get the number of elements in the list UpdateElement() - update list element at index (0 .. number of elements-1) InsertElement() - insert list element at index (0 .. number of elements-1) AddElement() - add element at the end off list (0 .. number of elements-1) RemoveElement() - remove list element at index (0 .. number of elements-1) Please note that the number of elements in the list is also provided from Get(), so avoid the expensive call to GetCount() wherever possible. Example: BACnet-Standard: “Calendar Object Type” property Date_List(BACnetLIST of BACnetCalendarEntry) CODESYS BACnet: function block BACnetCalendar PROPERTY DateList of type IBACnetLIST_of_BACnetCalendarEntry - DateList.GetContent() - DateList.SetContent() - DateList.GetCount() - DateList.UpdateElement() - DateList.InsertElement() - DateList.AddElement() - DateList.RemoveElement() exceptions from property access naming conventions ¶ In some exceptional cases naming doesn’t clamp to the BACnet-Standard too much, because: - part of the BACnet property name duplicates the container type (array vs. list) “Group Object Type” property List_Of_Group_Members “Schedule Object Type” property List_Of_Object_Property_References “Timer Object Type” property List_Of_Object_Property_References For those properties the API skips the prefix “ List_Of_ ” within the names of the related functional access properties. BACnet property name is wrong regarding the container type (array vs. list) “Channel Object Type” property List_Of_Object_Property_References (of type BACnetARRAY[N] of BACnetDeviceObjectPropertyReference) For this property the API skips the prefix “ List_Of_ ” within the names of the related functional access PROPERTY. BACnet property name clashes with a CODESYS IEC-61131 keyword For those properties the API adds a postfix “Property” to the names of the related functional access properties. example “Command Object Type” property Action is named ActionProperty property access and memory (allocation / deallocation) - simple rule of thumb ¶ The CODESYS BACnet2 API has been designed to make it easy to get memory allocation / deallocation at property access right. Please note - BACnet fix size array properties of complex type containing SEQUENCE’s doesnt provide property access represented as value access for good reason. Complex type containing SEQUENCE’s require the memory for the SEQUENCE (referenced by the POINTER TO member of the datatype) been allocated by the BACstack reading those properties, thus requiring the caller the to release the memory calling FreeStackAllocatedMemory after processing. Similiar to this BACnet list properties doesnt provide property element read access for good reason. Of ourse the need to call FreeStackAllocatedMemory is documented wherever needed, but it’s helpful to have simple rules as well. So the simple rule of thumb is: 1.) wherever there is a memory ownership transfer from CODESYS BACnet2 to the application there is a need to call FreeStackAllocatedMemory after usage. 2.) if property access is represented as value access there is always value semantic so no memory ownership transfer 3.) if property access is represented as functional access 3.1.) if property is of complex type containing SEQUENCE’s GetContent() and GetElement() do memory ownership transfer 3.2.) if property is of simple type or complex type not containing SEQUENCE’s GetContent() does memory ownership transfer all other methods do have value semantic, so there is no memory ownership transfer The differantiation between 2.1.) and 2.2) is a bit more subtle. GetContent() does always memory ownership transfer but GetElement() might or might not so read documentation of GetElement() carefully. In case GetElement() write the element value onto a variable provided by the caller via VAR_IN_OUT, there is value semantic and so no memory ownership transfer. In case GetElement() passes the element value to the caller by the means of POINTER TO there is memory ownership transfer. 4.) you need to take care for your own memory you pass on to property write access STRING / WSTRING exceptions from property access type consistency ¶ It had been laid out before, that property access is using data types defined in CmpBACnet2 in most cases, only some aspects of property access use IEC-61311 standard types for convenience. This comes with an exception in consistency regarding STRING / WSTRING vs. CmpBACnet.IEC_BACNET_STRING. The BACstack and so CmpBACnet2 is using CmpBACnet.IEC_BACNET_STRING to represent the BACnet type called CharacterString within the BACnet-Standard. CmpBACnet.IEC_BACNET_STRING comes with a POINTER TO string buffer and features for codepage, which makes the handling a bit mor complex than STRING / WSTRING. Property access within CODESYS BACnet up to version 1.7.*.* was utilizing STRING / WSTRING for single value properties for convenience. This avoids the hazzle for the user to deal with encoding/decoding, codepage-conversion and memory ownership transfer for CmpBACnet.IEC_BACNET_STRING.data on those pieces of the API, but pays the price of in-place conversion at call. The shortcoming of this approach was: CharacterString nested into complex types had not been treated this way, because it would require complete transformation of all complex types containg CharacterString into BACnet.library types plus the whole conversion functionality - which is expensive in many ways. This inconsistency brought up with CODESYS BACnet up to version 1.7.*.* had been kept in the within CODESYS BACnet2 API for two reason: compatibility some convenience for single value properties of type CharacterString