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.

  1. Click the link above and download the file.
  2. Modify the configure/RELEASE file and point EPICS_BASE to the correct location
  3. Type make and build the application

How to Configure the driver

  1. Define a BACnet Field-Device

  2. In your application's startup command file, st.cmd, you need to define a BACnet field-device by calling the drvBACnetDefineDevice(...) function.

    Example:
    drvBACnetDefineDevice("Chiller1", 21, "eth0", 47808)

    The 'drvBACnetDefineDevice(...)' function takes four parameters:

    1. "Chiller1": A uniquely defined EPICS name. E.g., "Chiller1", "Chiller2", "Foo1", "Foo2", etc.
    2. 21: A unique BACnet instance number in the range of 0-4194303
    3. "eth0": The name of your machine's network interface
    4. 47808: The port which you will use. Typically this will be the BACnet standard port of 0xBAC0, in hex, or 47808, in decimal
    BACnet Device Configuration Properties:
    • 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.

    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:
    • 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.
    • #st.cmd
      cd ${TOP}/iocBoot/${IOC}
      iocInit
      drvBACnetStart
      
  3. Define the BACnet Objects and Properties to Read and Write via EPICS Records

  4. BACnet Objects and Properties

    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.

    As you can see in the image above, each temperature value can be uniquely identified by the object type (i.e., ANALOG_INPUT), instance number (i.e., 1, 2, or 3), and property (i.e., Present_Value).

    Pointing the EPICS Record to an Object & Property of a BACnet Device

    Supported EPICS record types:
    • ai - Analog Input Record
    • ao - Analog Output Record
    • stringin - String Input Record

    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 Format
    The 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:
    1. @Device Name: This name must match the device name you created in your st.cmd file.
    2. Object Type: This is an enumeration for the BACnet object type. Note: See enumerations below.
    3. Object Instance: The instance number of the object.
    4. Property Id: This is an enumeration for the BACnet property identifier. Note: See enumerations below.
    5. [1..n] Array-Index: Used to denote the array-index if your property-Id is of array type
    
    #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

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

    Property Enumerations

    • Present-Value (85)
    • Object-Name (77)
    • Status-Flags (111)
    • Units (117)
    • Out-Of-Service (81)
    • Priority-Array (87)

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-Arrays

All 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 Priorities

Every 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

  • whois
  • readprop
whois

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
}

readprop

The '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....