BACnetIP
The "BACnetIP" driver/device support module for EPICS IOCs is an interface for Ethernet capable BACnet devices to the EPICS controls system.
Download
See Github for latest.
- BACnet v2.2.2 source: tar-archive-bacnetv2.2.2.tar.gz Date: 04/13/2022 Release Notes.txt
- Click the link above and download the file.
- Modify the configure/RELEASE file and point EPICS_BASE to the correct location
- Type make and build the application
How to Configure the driver
Define a BACnet Field-Device
In your application's startup command file, st.cmd, you need to define a BACnet field-device by calling the
drvBACnetDefineDevice(...) function.- "Chiller1": A uniquely defined EPICS name. E.g., "Chiller1", "Chiller2", "Foo1", "Foo2", etc.
- 21: A unique BACnet instance number in the range of 0-4194303
- "eth0": The name of your machine's network interface
- 47808: The port which you will use. Typically this will be the BACnet standard port of 0xBAC0, in hex, or 47808, in decimal
- apdu_timeout: A period of time in milliseconds between the driver's request and the field-device's response. If the period of time between the request and response reaches this setting the driver will flag the message as timed-out and issue a retry of the original request.
- apdu_retries: The number of times the driver will resend a request that has timed-out before complaining about the time-out to the console.
- rpm_buffer_limit: This is a buffer limit, in bytes, that pertains only to Read-Property-Multiple message types. This limit will limit the message size of a single BACnet request/response that has multiple items/propeties inside the single request/response.
- rpm_disable: This flag will disable the Read-Property-Multiple (RPM) service all together and use single reads for individual properties, even if the field-device supports RPM-message types.
- Changing the apdu_timeout
drvBACnetSet("Chiller1", "apdu_timeout=3000")
- Changing the apdu_retries
drvBACnetSet("Chiller1", "apdu_retries=3")
- Changing both apdu_timeout and apdu_retries
drvBACnetSet("Chiller1", "apdu_timeout=3000, apdu_retries=3")
- Changing ALL
drvBACnetSet("Chiller1", "apdu_timeout=3000, apdu_retries=3, rpm_buffer_limit=500, rpm_disable=false")
- drvBACnetStart: This will start the BACnet driver threads. It must be placed after iocInit.
Define the BACnet Objects and Properties to Read and Write via EPICS Records
BACnet Objects and Properties
- ai - Analog Input Record
- ao - Analog Output Record
- stringin - String Input Record
- @Device Name: This name must match the device name you created in your st.cmd file.
- Object Type: This is an enumeration for the BACnet object type. Note: See enumerations below.
- Object Instance: The instance number of the object.
- Property Id: This is an enumeration for the BACnet property identifier. Note: See enumerations below.
- [1..n] Array-Index: Used to denote the array-index if your property-Id is of array type
- Analog-Input (0)
- Analog-Output (1)
- Analog-Value (2)
- Binary-Input (3)
- Binary-Output (4)
- Binary-Value (5)
- Multi-State-Input (13)
- Multi-State-Output (14)
- Multi-State-Value (19)
- Present-Value (85)
- Object-Name (77)
- Status-Flags (111)
- Units (117)
- Out-Of-Service (81)
- Priority-Array (87)
- whois
- readprop
Example:
drvBACnetDefineDevice("Chiller1", 21, "eth0", 47808)
The 'drvBACnetDefineDevice(...)' function takes four parameters:
Optionally, you can change the device-specific properties of the driver by calling the 'drvBACnetSet(...)' function in your st.cmd file and passing one or more of the configuration properties mentioned above as the function's parameters.
Examples:#st.cmd
cd ${TOP}/iocBoot/${IOC}
iocInit
drvBACnetStart
The only values that can be read from a BACnet device are values associated with properties of that device. The BACnet device organizes and groups all of its properties into objects. The objects are differentiated by their type and instance number. Some of the standard types are Analog-Inputs, Analog-Outputs, Binary-Inputs and Binary-Outputs. Every object inside of a BACnet device must have an instance number. That instance number must be unique within groups of objects of the same type but can be duplicated across objects of different types.
For example: Suppose that we have a BACnet device with three temperature values that we wish to read in EPICS.Pointing the EPICS Record to an Object & Property of a BACnet Device
Supported EPICS record types:
The ai-record will be the record type used for reading all numerical BACnet property types. And the ao-record will be the record type used for writing all numerical BACnet property types. The stringin-record will be the record type used for reading BACnet property types of the character string value.
So, building on our example from above, if we wanted to read the Present_Value property from ANALOG_INPUT:2 (i.e., instance 2), of "Chiller1", our ai-record would look like the one below.
record(ai, "Chiller1:Temperature-2")
{
field(DTYP, "BACnet")
field(INP, "@Chiller1 0 2 85")
field(SCAN, "1 second")
}
INP FormatThe INP string must have at least 4-parameters but can have an optional 5th parameter known as the Array-Index.
The INP format is as follows:
#Priority-Array example
record(ai, "Chiller1:PriorityArray8")
{
field(DTYP, "BACnet")
field(INP, "@Chiller1 1 2 87 [8]") //Note: Analog-Output objects have Priority-Array properties.
field(SCAN, "1 second")
}
Common Enumerations
Object Enumerations
Property Enumerations
How the driver works
First the driver builds a list of device-Ids, or instance numbers, that you created back in the startup command file, st.cmd.Example:
#st.cmd
drvBACnetDefineDevice("Chiller1", 21, "eth0", 47808)
drvBACnetDefineDevice("Foo-Device", 22, "eth0", 47808)
drvBACnetDefineDevice("<EPICS-Name>", <Instance>, "<iface>", <port>)
Next the driver iterates over the list of device instance numbers and broadcasts a BACnet
"Who-Is" service, which contains the unique instance number, across the network. Any
BACnet devices that are reachable across the network
and receive the "Who-Is" service, AND whose device-Ids match the device-Id encoded
in the "Who-Is" service, will respond back to the driver with the BACnet "I-Am" service.The BACnet "I-Am" response will contain the remote device's network address, used for binding the driver to the remote device, and it will also contain the remote device's "Max-APDU-Length", which is used for understanding message size restrictions.
Now that the driver knows the network address of the remote device, the driver can communicate directly with the device without the use of broadcasts.
Next, the driver sends a "Read-Property" (RP) service request to the remote device requesting the value of its "Protocol_Services_Supported" property that belongs to its Device-Object. Within the structure of the "Protocol_Services_Supported" property the driver can learn what BACnet services the remote device supports, specifically the "Read-Property-Multiple" (RPM) service.
Next, the driver sends individual RP-requests to the remote device(s) for each of the properties that you defined in your EPICS database files, <foo>.db.
Once the driver has made a complete trip through all of the properties defined for a single device, the driver then checks back with its internal record of whether or not the device supports the RPM service. —Remember this was the first read that the driver performed on the device.
If the device supports the RPM-service, then the driver will try to optimize its read requests by grouping as many of the properties-to-be-read as it can into a single RPM-request. Note: the "Max-APDU-Length" and location of routers are what dictate the number of properties in a single RPM-group size.
Else, if the device does not support the RPM-service then the driver will continue to read each property, one by one, using the RP-service.
Writing to BACnet Properties via EPICS
All writes in the BACnet driver are performed with the ao-record. Therefore all writes are numerical, except for the special case of writing Null values. #foo.db
record(ao, "Chiller1:TemperatureSetpoint")
{
field(DTYP, "BACnet")
field(OUT, "@Chiller1 1 2 85 P=16") //P=16 is the 16th-element of the priority-array
field(SCAN, "Passive")
}
record(ao, "Chiller1:TemperatureSetpoint_Null_16")
{
field(DTYP, "BACnet")
field(OUT, "@Chiller1 1 2 85 P=16 T=0") //T=0 is the write "Null" flag for the protocol
field(SCAN, "Passive")
}
Priority-ArraysAll objects that are commandable in BACnet, i.e., objects that are writable, have a built-in prioritization table that is used for prioritizing the values written to the object, by clients, called the Priority-Array. Therefore every value written to a commandable object must have an associated priority-level written with it.
The valid range of the of the Priority-Array's priority-level is 1 to 16. 1 being the highest priority and 16 being the lowest priority.
The BACnet standard has defined specific priority-levels as specific application types. See the table to the right.
Priority Example: Assume an operator is commanding a piece of equipment to be "ON" at priority level-8, which BACnet designates as "Manual Operator", while the Site Safety System commands the sampe piece of equipment to be "OFF" at priority level-1, which BACnet designates as "Manual-Life Safety". The result will be that the equipment remains off or turns off because priority level-1 is the highest priority.
Releasing PrioritiesEvery value written to a priority-level in a commandable object's Priority-Array is sticky. Meaning that that value will stay associated with that priority-level until a client issues a relinquish command to that priority-level.
To relinquish a priority-level and its commanded value a client has to write a "Null" value into the priority-level to which it wishes to relinquish.
Back to our "Priority Example" from above: Once the Site Safety System has completed its purpose, the Site Safety System then writes a "Null" value into priority-level 1 of the commandable object's Priority-Array, relinquishing the "OFF" command, and therefore the commandable object's new priority moves to priority-level 8, assuming priority-level 8 is the next highest priority.
Driver Statistics (Tx, Rx & Er)
BACnetStats Can be used to monitor the network traffic between the BACnet driver and i the remote device..
#foo.db
record(ai, "Chiller1:Tx")
{
field(DTYP, "BACnetStats")
field(INP, "@Chiller1 Tx") //Monitor the number of transmits from the driver to the remote device.
field(SCAN, "1 second")
}
record(ai, "Chiller1:Rx")
{
field(DTYP, "BACnetStats")
field(INP, "@Chiller1 Rx") //Monitor the number of messages received from the remote device to the driver.
field(SCAN, "1 second")
}
record(ai, "Chiller1:Er")
{
field(DTYP, "BACnetStats")
field(INP, "@Chiller1 Er") //Monitor the number of BACnet-Error messages that the driver receives from the remote device.
field(SCAN, "1 second")
}
Command-Line Tools
The 'whois' command line tool is used to discover BACnet devices on a network. When executed from the command line a BACnet Who-Is service request is broadcasted across the network and any BACnet devices that receive the Who-Is service request will respond back to the issuer of the request with a BACnet I-Am service response. The whois program builds a list of all of the I-Am responses, prints the contents of the list to the console, and terminates.
[User linux-x86_64]$ ./whois --help
Usage:
[-i Network Interface Name] [-p Port] [-n Remote Network Number] [Instance Range: [low instance] [high instance]]
Examples:
To send whois request to devices with instance numbers between 0 and 100:
./whois -i eth0 -p 47808 0 100
To send whois request to ALL devices:
./whois -i eth0 -p 47808
[User linux-x86_64]$ ./whois -i "eth0" -p 47808
DEVICE-11 {
Network Address: 160.91.233.122:47808:0:NONE
Object-Identifier: DEVICE:11
Max APDU Length Accepted: 1458
Segmentation Supported: NO_SEGMENTATION
Vendor-Identifier: 37
}
DEVICE-73010 {
Network Address: 160.91.167.31:47808:2002:0x0a
Object-Identifier: DEVICE:73010
Max APDU Length Accepted: 244
Segmentation Supported: NO_SEGMENTATION
Vendor-Identifier: 140
}
DEVICE-21 {
Network Address: 160.91.232.253:47808:2001:0x28
Object-Identifier: DEVICE:21
Max APDU Length Accepted: 480
Segmentation Supported: SEGMENTED_BOTH
Vendor-Identifier: 24
}
readpropThe 'readprop' command line tool is used to read properties from BACnet devices and print the results to the command line.
[User linux-x86_64]$ ./readprop --help
Usage:
[-i Network Interface Name] [-p Port] Device-Instance Object-Type Object-Instance Property-Id [[Array-Index]]
Examples:
To read the Present-Value property of Analog-Input-1 from device-11:
./readprop -i eth0 -p 47808 11 0 1 85
To read the Priority-Array property, element 16, of Analog-Output-1 from device-11:
./readprop -i eth0 -p 47808 11 1 1 87 [16]
To read the Object-List property from device-11:
./readprop -i eth0 -p 47808 11 8 11 76
[User linux-x86_64]$ ./readprop -i "eth0" -p 47808 11 0 1 85
ANALOG_INPUT:1 PROP_PRESENT_VALUE: '85.12'
[User linux-x86_64]$ ./readprop -i "eth0" -p 47808 11 1 1 87 [16]
PROP_PRIORITY_ARRAY[16]: '72.1235'
[User linux-x86_64]$ ./readprop -i "eth0" -p 47808 11 8 11 76
PROP_OBJECT_LIST {
(1) BACNET_OBJECT_IDENTIFIER: 'DEVICE:11'
(2) BACNET_OBJECT_IDENTIFIER: 'ANALOG_INPUT:1'
(3) BACNET_OBJECT_IDENTIFIER: 'ANALOG_INPUT:2'
...
(41) BACNET_OBJECT_IDENTIFIER: 'ANALOG_OUTPUT:1'
(42) BACNET_OBJECT_IDENTIFIER: 'ANALOG_OUTPUT:2'
...
(51) BACNET_OBJECT_IDENTIFIER: 'BINARY_INPUT:1'
(52) BACNET_OBJECT_IDENTIFIER: 'BINARY_INPUT:2'
...
(69) BACNET_OBJECT_IDENTIFIER: 'BINARY_OUTPUT:9'
(70) BACNET_OBJECT_IDENTIFIER: 'BINARY_OUTPUT:10'
}
Documentation
More to come....