In the context of the Web of Things (WoT), a Binding Template is a blueprint that gives guidance on how to implement a specific IoT protocol, data format, or IoT platform. The Core Binding Templates specification explains the overall mechanism and requirements for any binding to follow. This document gives implementation guidelines regarding BACnet [[BACnet]], a building automation and control networking protocol.
More specifically, this document defines a set of vocabulary terms that can be used inside a Thing Description document, and associated rules that allow to describe WoT operations using BACnet over the network. Additionally, relevant examples are provided to showcase different vocabulary terms and the associated behavior.
This document is a work in progress
BACnet® is a registered trademark of ASHRAE. This document has not been reviewed for publication by ASHRAE, and no endorsement by ASHRAE is implied.
BACnet [[BACnet]] is a building automation and control networking protocol for applications such as heating, ventilating, and air conditioning control, lighting control, access control, and fire detection systems.
This document describes how the Web of Things Thing Description [[WOT-THING-DESCRIPTION]] (TD) can be used to represent devices that use BACnet. In particular, the document explains how to create valid TD Forms for the different operations that BACnet can perform.
Forms of a Thing Description instance with BACnet Binding complies with this specification if it follows the normative statements in [[[#vocabulary]]] and [[[#mappings]]].
A JSON Schema [[?JSON-SCHEMA]] to validate Thing Description instances containing BACnet Binding is provided in the GitHub repository.
It is assumed the reader of this document is familiar with the basic principles and mechanisms of the BACnet standard [[BACnet]].
The following subset of BACnet features is supported by this binding:
This protocol binding is intended to be used to enable web applications to interact with data and control affordances exposed by BACnet devices. It is not intended that this binding be used for device commissioning, onboarding, or device management.
WoT Interaction Affordances are mapped to BACnet Properties. If the BACnet property-identifier is not present in the URI element of the form, the object-identifier is used to interact with the BACnet Object, exposing the present-value Property.
When it is necessary to expose properties other than the present-value property, the property-identifier must be included in the URI element of the form.
Selected BACnet Service parameters are exposed as URI Variables to enable WoT consumers access to BACnet features, for example, Command Priority.
In cases where a URI is needed to refer to data that is accessible using BACnet services, the 'bacnet' URI scheme provides a means to identify a property of an object in the scope of a BACnet device. The 'bacnet' URI scheme does not provide means to identify the BACnet network on which a device resides, or the network access method, physical media, or protocol service to use.
The 'bacnet' URI Scheme used herein is based on the definition in Annex Q.8 of the BACnet Specification [[BACnet]]. The additions to Q.8 are as follows:
The syntax for the 'bacnet' URI scheme is specified below in Augmented Backus-Naur Form (ABNF) [[RFC5234]]:
bacnet-URI = scheme ":" scheme-specific-part [uri-variable-part] scheme = "bacnet" scheme-specific-part = "//" device-identifier "/" object-identifier [ "/" property-identifier [ "/" property-array-index ] ] device-identifier = number / ".this" object-identifier = object-type "," object-instance object-type = number / identifier object-instance = number property-identifier = number / identifier property-array-index = number uri-variable-part = ?(key1=value1) *( "&" keyN=valueN ) number = "0" / non-zero-digit *decimal-digit non-zero-digit = %x31-39 ; "1" to "9" decimal-digit = %x30-39 ; "0" to "9" identifier = lowercase *alphanumeric *( "-" 1*alphanumeric ) alphanumeric = uppercase / lowercase / decimal-digit uppercase = %x41-5A ; "A" to "Z" lowercase = %x61-7A ; "a" to "z"
device-identifier
Component
The device
component specifies the device
instance number in decimal. A device
identifier
of .this
means "this device" so that it can be
used in static files that do not need to be changed when the
device identifier changes.
device-identifier = number / ".this"
A Protocol Binding conforming to this specification SHALL use number type for device-identifier.
object-identifier
Component
The object-identifier
component consist of two
sub-components: the object-type and the object-instance
number. The object-type is either a decimal number in the
range 0 to 210-1 (inclusive) or exactly equal to
the identifier text of one of the named items of the
BACnetObjectType enumeration defined in Clause 21 of
[[BACnet]]. The object-instance number is a decimal number
in the range 0 to 222-1 (inclusive).
object-identifier = object-type "," object-instance object-type = number / identifier object-instance = number
A Protocol Binding conforming to this specification SHALL use number type for object-type.
property-identifier
Component
The property-identifier
component is either a decimal
number or exactly equal to the identifier text of one of the
named items of the BACnetPropertyIdentifier enumeration
defined in Clause 21 of [[BACnet]]. If it is omitted, it
defaults to present-value
except for BACnet
File objects, where the absence of the property
component refers to the entire content of the file accessed
with Stream Access.
property-identifier = number / identifier
A Protocol Binding conforming to this specification SHALL use number type for property-identifier.
property-array-index
Component
The components of an array property may be individually
accessed (read or written) using an "array index".
The index
component is a decimal number.
property-array-index = number
An index of 0 (zero) identifies the count of the number of data elements. If the array index is omitted, it means that all the elements of the array are to be accessed. An array index N, greater than zero, identifies the Nth element in the sequence.
uri-variable-part
ComponentURI variables can be used to supply additional parameters to the BACnet operation. See [[[#vocabulary-uri-variables]]] for further details.
uri-variable-part = ?(key1=value1) *( "&" keyN=valueN )
A string of key-value pairs starting with ? and separated via &.
A decimal number consists of one or more decimal digits. The first digit is not permitted to be zero unless the number consists of a single digit.
number = "0" / non-zero-digit *decimal-digit non-zero-digit = %x31-39 ; "1" to "9" decimal-digit = %x30-39 ; "0" to "9"
An identifier conforms to the definition of an identifier in ASN.1 notation (Clause 12.3 of [[X.680]]). It begins with a lowercase letter and is followed by zero or more letters, digits, and hyphens. A hyphen is not permitted to be the last character, nor is it to be followed by another hyphen. The case of letters in an identifier is significant.
identifier = lowercase *alphanumeric *( "-" 1*alphanumeric ) alphanumeric = uppercase / lowercase / decimal-digit uppercase = %x41-5A ; "A" to "Z" lowercase = %x61-7A ; "a" to "z"
This section describes the vocabulary used in BACnet. A protocol binding implementation should use the vocabulary defined in this section to describe the different configurations that can be used to exchange data between Web of Things clients, devices, and services.
This vocabulary is fully defined in the BACnet ontology published together with this document.
Until the JSON-LD context is published, this document will use the example URI
"https://example.org/bacnet"
for the context with the prefix "bacv"
.
URI variable | Description | Assignment | Range |
---|---|---|---|
commandPriority |
Sets priority for WriteProperty | optional |
integer
|
covIncrement |
Sets COV increment change for reporting via SubscribeCOVProperty (SubscribeCOV with a property ID) | optional |
number
|
URI Variables are included in the 'bacnet' URI Scheme and are included in the Protocol Binding to communicate PDU option settings for the BACnet driver to use when sending requests.
The following JSON example shows the usage of the BACnet vocabulary for URI Variables, specifically CommandPriority and CovIncrement. The semantic annotation is optional.
This template could be used to construct the uriVariables element for TD interaction affordances, to enable the application to communicate URI variables to the BACnet driver, using a consistent format according to the schema constraints below.
These variables may be also fixed in the href and not exposed via URI variables, causing the BACnet driver to use a constant value for this parameter.
Some aspects were identified as improvement candidates in TD.next, which may result in breaking changes regarding URI variables:
{ "@context": ["https://www.w3.org/2022/wot/td/v1.1", { "bacv": "https://example.org/bacnet" }], "uriVariables": { "commandPriority": { "@type": "bacv:CommandPriority", "type": "integer", "enum": [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "default": 16 }, "covIncrement": { "@type": "bacv:CovIncrement", "type": "number", "minimum": 0 } } }
Vocabulary term | Description | Range | Domain |
---|---|---|---|
bacv:usesService |
Indicates the BACnet service to use |
string
(one of |
hctl:Form |
bacv:isISO8601 |
This data uses ISO8601 format |
boolean
|
bacv:Sequence |
bacv:hasBinaryRepresentation |
Points to the binary representation of the resource |
string
(one of |
bacv:OctetString |
bacv:hasMember |
Member of a Sequence or List | bacv:DataType | bacv:Sequence, bacv:List |
bacv:hasNamedMember |
Named Member of a Sequence or Choice | bacv:NamedMember | bacv:Sequence, bacv:Choice |
bacv:hasFieldName |
Name of a Named Member of a Sequence or Choice |
string
|
bacv:NamedMember |
bacv:hasContextTag |
Context Tag for a Named Member of a Sequence or Choice |
boolean
|
bacv:NamedMember |
bacv:hasMapEntry |
An value map entry mapping an Enumerated value to text | bacv:ValueMapEntry | bacv:ValueMap |
bacv:hasLogicalVal |
Logical Value for a ValueMap | integer
or string
or boolean |
bacv:ValueMapEntry |
bacv:hasProtocolVal |
Protocol Value for a ValueMap | integer
|
bacv:ValueMapEntry |
bacv:hasValueMap |
Value map for an Enumeration | bacv:ValueMap | bacv:Boolean, bacv:Enumerated, bacv:Unsigned, bacv:BitString |
This section describes the strategies and default values to use protocol-specific concepts within the WoT Interaction model.
Operation | Default Binding | Variables and parameters |
---|---|---|
readproperty |
"bacv:usesService": "ReadProperty" |
|
writeproperty |
"bacv:usesService": "WriteProperty" |
CommandPriority |
observeproperty |
"bacv:usesService": "SubscribeCOV" |
covIncrement |
unobserveproperty |
"bacv:usesService": "SubscribeCOV" |
|
observeproperty |
"bacv:usesService": "SubscribeCOV" |
Note: BACnet driver must handle Status Flags |
unobserveproperty |
"bacv:usesService": "SubscribeCOV" |
BACnet has its own data model, which cannot always be derived from the data schema of the interaction affordances. Hence the type information is amended in the Form (column 3) of the protocol binding while staying conceptually conformant to the WoT Data Schema (column 2). The goal here is to abstract BACnet's data model into a Web-like JSON-based data model, by still keeping the wire compatibility on the protocol. The table below shows how the mappings are done, comprehensively for all BACnet types.
BACnet Type | WoT Data Schema | BACnet Type Description under the Form |
---|---|---|
bacv:SequenceOf |
{ "type": "array" } |
{
"bacv:hasDataType": {
"@type": "bacv:SequenceOf",
"bacv:hasMember": {
"$comment": "Data type of the element",
"@type": "bacv:..."
}
}
}
|
bacv:Sequence |
{ "type": "object" }
For the special case where a DateTime object is modeled as a Sequence "bacv:isISO8601": true :
{
"type": "string",
"pattern": "pattern": "^((([1-9][0-9]*)?[0-9]{4})|\\*)-((1[0-2]|0[1-9])|\\*)-((3[01]|0[1-9]|[12][0-9])|\\*)T((2[0-3]|[01][0-9])|\\*):([0-5][0-9]|\\*):([0-5][0-9]|\\*)(\\.[0-9]+)?(Z|[+-]\\d\\d:\\d\\d)?$",,
"$comment": "ISO8601 Date Time Format with optional wildcards"
}
|
{
"bacv:hasDataType": {
"@type": "bacv:Sequence",
"bacv:isISO8601": true,
"bacv:hasNamedMember": [
{
"bacv:hasFieldName": "date",
"bacv:hasDataType": {
"@type": "bacv:Date"
}
},
{
"bacv:hasFieldName": "time",
"bacv:hasDataType": {
"@type": "bacv:Time"
}
}
]
}
}
|
bacv:List |
{ "type": "array", "uniqueItems": true } |
{
"bacv:hasDataType": {
"@type": "bacv:List",
"bacv:hasMember": {
"@type": "bacv:..."
}
}
}
|
bacv:Choice |
{ "oneOf": [
{ "type": "..." },
{ "type": "..." },
{ "...": "..." },
]}
|
{
"bacv:hasDataType": {
"@type": "bacv:Choice",
"bacv:hasContextTags": true,
"bacv:hasNamedMember": [
{
"bacv:hasFieldName": "...",
"bacv:hasContextTag": 1,
"bacv:hasDataType": {
"@type": "bacv:..."
}
},
{
"bacv:hasFieldName": "...",
"bacv:hasContextTag": 2,
"bacv:hasDataType": {
"@type": "bacv:..."
}
}
]
}
}
|
bacv:Date |
{
"type": "string",
"pattern": "^((([1-9][0-9]*)?[0-9]{4})|\\*)-((1[0-2]|0[1-9])|\\*)-((3[01]|0[1-9]|[12][0-9])|\\*)$",
"$comment": "ISO8601 Date Format with optional wildcards"
}
|
{
"bacv:hasDataType": {
"@type": "bacv:Date"
}
}
|
bacv:Time |
{
"type": "string",
"pattern": "^((2[0-3]|[01][0-9])|\\*):([0-5][0-9]|\\*):([0-5][0-9]|\\*)(\\.[0-9]+)?(Z|[+-]\\d\\d:\\d\\d)?$",
"$comment": "ISO8601 Time Format with optional wildcards"
}
|
{
"bacv:hasDataType": {
"@type": "bacv:Time"
}
}
|
bacv:WeekNDay |
{
"type": "string",
"pattern": "^((1[0-2]|0[1-9])|O|E|\\*)-(01|08|15|22|29|L7|B7|B14|B21|\\*)-([1-7]|\\*)$",
"$comment": "Custom WeekNDay string: first part month 01-12, O for odd, E for even, * for any; second part week of month: beginning on 01st, 08th, the last 7 days, they days before last 7 days, last 14 days.., or any; third part day of week: 1-7 (Mon-Sun) or any"
}
|
{
"bacv:hasDataType": {
"@type": "bacv:WeekNDay"
}
}
|
bacv:Unsigned |
{ "type": "integer", "minimum": 0 } |
{
"bacv:hasDataType": {
"@type": "bacv:Unsigned"
}
}
|
bacv:Signed |
{ "type": "integer" } |
{
"bacv:hasDataType": {
"@type": "bacv:Signed"
}
}
|
bacv:Real |
{ "type": "number" } |
{
"bacv:hasDataType": {
"@type": "bacv:Real"
}
}
|
bacv:Double |
{ "type": "number" } |
{
"bacv:hasDataType": {
"@type": "bacv:Double"
}
}
|
bacv:Boolean |
{ "type": "boolean" } |
{
"bacv:hasDataType": {
"@type": "bacv:Boolean"
}
}
|
bacv:Enumerated |
{
"$comment": "Contains the possible enum members, as strings or integers",
"enum": []
}
|
{
"bacv:hasDataType": {
"@type": "bacv:Enumerated"
}
}
|
bacv:String |
{ "type": "string" } |
{
"bacv:hasDataType": {
"@type": "bacv:String"
}
}
|
bacv:OctetString |
{
"type": "string",
"pattern": "^(([0-9A-F]{2}-?)+|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{4}|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{2}={2}))$",
"$comment": "Binary data encoded in hex, dotted decimal (e.g. IPv4 address) or base64"
}
|
{
"bacv:hasDataType": {
"@type": "bacv:OctetString",
"bacv:hasBinaryRepresentation": "..."
}
}
|
bacv:BitString |
{
"type": "array",
"items": {
"$comment": "Contains the possible bit numbers",
"enum": []
}
"uniqueItems": true
}
|
{
"bacv:hasDataType": {
"@type": "bacv:BitString"
}
}
|
bacv:Any |
The type field should be left out completely. This is allowed according to WoT Data Schema Vocabulary Definitions.
|
{
"bacv:hasDataType": {
"@type": "bacv:Any"
}
}
|
bacv:Null |
{ "type": "null" } |
"bacv:hasDataType": {
"@type": "bacv:Null"
}
|
bacv:ObjectIdentifier |
{
"type": "string"
"format": "iri-reference",
"$comment": "BACnet Object Identifier is to be converted to an IRI, using the href schema of this protocol binding"
}
|
{
"bacv:hasDataType": {
"@type": "bacv:ObjectIdentifier"
}
}
|
This section will present a set of examples of how the terms defined in this document can be used to describe and configure a Form.
[[[#example-readproperty]]] shows a property with the object type
analog-input
, object instance 1
, and
property identifier present-value
in the scope of a
BACnet device with device instance number 5
.
{ "@context": ["https://www.w3.org/2022/wot/td/v1.1", { "bacv": "https://example.org/bacnet" }], ... "properties": { "analog1": { "type": "number", "readOnly": true, "forms": [{ "op": [ "readproperty" ], "href": "bacnet://5/0,1/85", "bacv:usesService": "ReadProperty", "bacv:hasDataType": { "@type": "bacv:Real" } }] } } }
[[[#example-uri-variables-with-forms]]] demonstrates how the uriVariables
in the
affordance-level can be
used and how they need to be represented in a forms
element.
The "bacv:covIncrement"
indicates that the URI variable set by the Consumer application will be
used to notify the
Consumer only if the property value changes more than the set amount by the URI variable.
{ "@context": ["https://www.w3.org/2022/wot/td/v1.1", { "bacv": "https://example.org/bacnet" }], ... "properties": { "analog1": { "type": "number", "readOnly": true, "uriVariables": { "observeIncrement": { "@type": "bacv:covIncrement", "type": "number", "minimum": 0 } }, "forms": [{ "op": [ "observeproperty" ], "href": "bacnet://5/0,1/85?covIncrement={observeIncrement}", "bacv:usesService": "SubscribeCOV", "bacv:hasDataType": { "@type": "bacv:Real" } }] } } }
[[[#example-uri-variable-for-writing]]] demonstrates how the uriVariable
in the
affordance-level can be used to model a writing priority in a forms
element.
The "bacv:commandPriority"
indicates that the URI variable set by the Consumer application will
be relayed as the priority argument in the WriteProperty request.
For other operations (readproperty, observeproperty, unobserveproperty) this URI variable is irrelevant, so the corresponding forms do not use the URI variable.
If the URI variable is also irrelevant for the writeproperty operation (e.g., when the BACnet object doesn't have a priority array),
it can also be left out of the writeproperty form and the URI variable can be omitted completely.
The URI variable comes with a default, so if the Consumer doesn't provide this URI variable, the default of 16 will be used.
Another possibility is to fix the writing priority in the TD by giving a fixed commandPriority in the href
of the form, and not exposing its value via a URI variable.
These decisions belong to the TD author.
{ "@context": ["https://www.w3.org/2022/wot/td/v1.1", { "bacv": "https://example.org/bacnet" }], ... "properties": { "analog1": { "type": "number", "readOnly": false, "writeOnly": false, "observable": true, "uriVariables": { "writePriority": { "@type": "bacv:commandPriority", "type": "integer", "enum": [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16], "default": 16 } }, "forms": [ { "op": [ "readproperty", "observeproperty", "unobserveproperty" ], "href": "bacnet://5/2,1", "bacv:hasDataType": { "@type": "bacv:Real" } }, { "op": [ "writeproperty" ], "href": "bacnet://5/2,1?commandPriority={writePriority}", "bacv:usesService": "WriteProperty", "bacv:hasDataType": { "@type": "bacv:Real" } } ] } } }
[[[#example-enum-mapping]]] shows how an enum
in the Data Schema is extended in the
forms
to make it understandable by BACnet drivers.
{ "@context": ["https://www.w3.org/2022/wot/td/v1.1", { "bacv": "https://example.org/bacnet" }], ... "properties": { "multistate1": { "type": "string", "enum": ["on", "off", "auto", "manual"] "readOnly": true, "forms": [{ "op": [ "readproperty" ], "href": "bacnet://5/14,1/85", "bacv:usesService": "ReadProperty", "bacv:hasDataType": { "@type": "bacv:Enumerated", "bacv:hasValueMap": [ { "bacv:hasProtocolVal": 1, "bacv:hasLogicalVal": "on" }, { "bacv:hasProtocolVal": 2, "bacv:hasLogicalVal": "off" }, { "bacv:hasProtocolVal": 3, "bacv:hasLogicalVal": "auto" }, { "bacv:hasProtocolVal": 4, "bacv:hasLogicalVal": "normal" } ] } }] } } }