BACnet

BACnet is shortcut of "Building Automation and Control Networks". As name suggests this protocol is very popular in building automation and control applications. Its primary use is HVAC but also other elements in infrastructure. BACnet is an open standard both in North America (ASHARE) and worldwide (ISO).

The protocol initially defined 23 standard object types, kinds of inputs/outputs and their role in automation systems. However, this number of supported objects and properties grew up significantly since 1990’s.

The binding allows to read values from the BACnet network as well as write (command) compatible devices.

Table 1. BACnet binding capability table
Device discovery Channel discovery Read Write Subscribe

Yes

Yes

Yes

Yes

No

Binding does not support Change of Value (COV) notifications yet.

While it is defined at protocol level we currently to not support this feature.

Currently, this binding uses library licensed under GPL license. Consider that if you plan to use related code commercially.

Supported hardware

All devices capable of communication BACnet/IP and BACnet/MSTP. Other transport kinds and BACnet/SC is unsupported.

Device discovery

Full discovery of BACnet devices is supported. By default, binding supply unbounded whois request. This request will cause burst traffic in large networks. Also for larger networks it might be necessary to fire discovery several times to detect all nodes.

Thing and Channel discovery

Binding use BACnet protocol features to retrieve device objects. Thanks to it, it is able to enumerate all supported objects and automatically create appropriate channel definitions.

While each BACnet object can be seen and created as a separate Thing, by default binding does not discover objects. This is due to fact that some BACnet controllers can hold hundreds of objects leading ot situation that discovery results are extremely hard to navigate. You can always create Thing representing given input or output object manually.

The discovery process provide basic mapping between Thing and BACnet device. Channels created by binding will represent present values of device objects (inputs, outputs etc).

Readout principles

All reading is done through cyclic polling. By default, binding polls only linked channels. It allows to save bandwidth on busy/big servers.

The refreshInterval parameter defines polling cycle. The default value is assumed to be 1000 (ms). It can be defined and overridden at - Bridge and Thing and Channel level.

Polling based on refreshInterval parameter can be cascaded to limit amount of places where it is defined. Most of our bindings provide this parameter at multiple levels - from Bridge, through Thing down to individual Channel. Value for refresh interval is always defined in milliseconds providing fine-grained control over cycle time. The refresh time is minimum time between poll cycles. Please be aware that it is not guaranteed that cycle time will always be the same.

Read-outs are grouped by refresh interval and, if possible, conducted in groups. Each refresh interval value will result in spinning of a separate polling task.

For example, by defining refreshInterval of 60000 (ms) at the Bridge and leaving it at 0 (ms) at Thing and Channel you will have single task. This task, if possible, will attempt to read the largest possible set of data, unless it is not permitted by configuration. Channels which do not fit into single request will be split and conducted in follow-up requests after response for earlier call arrives. All these are assumed to be executed in the same readout cycle, even if their request is generated after specified refreshInterval.

If you define a refreshInterval of 1000 (ms) at the Channel instance, next to above bridge you will end up with two tasks. First which will fire every minute and second which will trigger every second which will poll single channel.

Optimization of readouts is always specific to protocol.

Binding always polls only 3 channels at the time. This allows to avoid getting into troubles with segmentation.

Channel definitions

Binding ships several channel definitions which reflect OPC UA scalar types. Because read and write procedure is general (it is the same for various data types), configuration of channels remains fairly simple and maps 1:1 to OPC UA nodes.

Example channel definition:

dsl
Bridge co7io-bacnet:ip-device:... "Test device" [...] { (1)
  Channels:
    Type device-read-write : (2)
        analog-input-1  (3)
        "Analog Input 1"  (4)
        [type="ANALOG_INPUT", propertyIdentifier="present-value", instance=1, refreshInterval=30000] (5)
}
1 All channels belong to thing or bridge, hence they are wrapped in Bridge section.
2 Declaration of channel type device-read-write, it is a valid channel kind in context of BACnet binding.
3 Unique channel identifier within bridge definition (this is technical identifier).
4 Human-readable label for channel and its role.
5 Channel configuration parameters which result in reading present value of 1st Analog Input object within device.

Priorities

Binding does support setting priority for write commands as well as resetting value set with priority. Currently, this option is available only at the object level.

Resetting prioritized value is possible through special profile. Profile is configured at item level, beyond channel definition. Once item which have reset profile attached, the triggered profile will cause NULL value to be sent to controller at configured priority.

Schedule objects

Schedule objects are supported, however they are not manageable through regular user interface.

Textual configuration

Below is example of a text configuration which reflect relations within BACnet network. The ip bridge defines broadcast address used for communication, device contains node address, and channel points to actual device object.

dsl
Bridge co7io-bacnet:ipv4:local "BACnet Network" [ broadcastAddress="10.10.10.255", localBindAddress="10.10.10.10", localDeviceId=1010, localNetworkNumber=0 ] {

  Bridge ip-device dev1 "Exhaust temperature" [ address="10.10.10.20", instance=1, network=0 ] {
    Thing analog-input ai1 [ refreshInterval=500 ] {
      Type readableNumber : value "Temperature reading" [ instance=1 ]
    }
    Thing analog-input ai2 "Supply temperature" [ refreshInterval=500 ] {
      Type readableNumber : value "Temperature reading" [ instance=2 ]
    }
  }

  Bridge ip-device dev2 "Compact definition" [ address="10.10.10.20", instance=1, network=0 ] {
    Type deviceReadableNumber : ai1 "Exhaust temperature" [ type="ANALOG_INPUT", instance=1, refreshInterval=500 ]
    Type deviceReadableNumber : ai2 "Supply temperature" [ type="ANALOG_INPUT", instance=2, refreshInterval=500 ]
  }

}
yaml
---
things:
- kind: "Bridge"
  UID: "co7io-bacnet:ipv4:local"
  label: "BACnet Network"
  configuration:
    localDeviceId: 1010
    localNetworkNumber: 0
    localBindAddress: "10.10.10.10"
    broadcastAddress: "10.10.10.255"
  things:
  - kind: "Bridge"
    id: "dev1"
    type: "co7io-bacnet:ip-device"
    label: "Exhaust temperature"
    configuration:
      address: "10.10.10.20"
      instance: 1
      network: 0
    things:
    - id: "ai1"
      type: "co7io-bacnet:analog-input"
      configuration:
        refreshInterval: 500
      channels:
      - id: "value"
        type: "readableNumber"
        label: "Temperature reading"
        configuration:
          instance: 1
    - id: "ai2"
      type: "co7io-bacnet:analog-input"
      label: "Supply temperature"
      configuration:
        refreshInterval: 500
      channels:
      - id: "value"
        type: "readableNumber"
        label: "Temperature reading"
        configuration:
          instance: 2
  - kind: "Bridge"
    id: "dev2"
    type: "co7io-bacnet:ip-device"
    label: "Compact definition"
    configuration:
      address: "10.10.10.20"
      instance: 1
      network: 0
    channels:
    - id: "ai1"
      type: "deviceReadableNumber"
      label: "Exhaust temperature"
      configuration:
        instance: 1
        type: "ANALOG_INPUT"
        refreshInterval: 500
    - id: "ai2"
      type: "deviceReadableNumber"
      label: "Supply temperature"
      configuration:
        instance: 2
        type: "ANALOG_INPUT"
        refreshInterval: 500