A BACnet protocol stack written in pure typescript with RXJS and promises. BACnet is a protocol to interact with building automation devices defined by ASHRAE. Big shout out to FH1CH for making the node-bacstack library. A lot of priniciples there, are used here.
Add Bacnet-driver to your project by:
- Login to the Github Packages registry:
npm login --registry=https://npm.pkg.github.com
- Adding the .npmrc file:
registry=https://npm.pkg.github.com/bastiaanv
- Running the following npm command:
npm i @bastiaanv/bacnet-driver
NOTE this library is still in pre-alpa and is not recommended in poduction usages. For those case, please use the node-bacstack library.
Service | Receive | execute |
---|---|---|
whoIs | No | Yes |
iAm | Yes | No |
readProperty | No | Partly |
writeProperty | No | Partly |
readMultiple | No | No |
writeMultiple | No | No |
Subscribe COV | No | Yes |
readProperty: This service can only read unsigned integers, floats, booleans, strings and ObjectIndentifiers
writeProperty: Unsupported types: TIMESTAMP, OCTET_STRING, OBJECTIDENTIFIER, COV_SUBSCRIPTION, READ_ACCESS_RESULT, READ_ACCESS_SPECIFICATION
The whoIs command discovers all BACNET devices in a network. BACNET devices who has received this command will sent a IAm command.
import { BacnetDriver } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDriver({});
bacnetDriver.whoIs();
Name | Type | Description |
---|---|---|
address | string? | An optional parameter to which send the WhoIs command directly, instead of broadcasting. |
Name | Type | Description |
---|---|---|
response |
void | Can trigger IAm commands |
The readProperty command reads a single property of an object from a device. Note, the device should have been found first via the WhoIs command before the read property command can be used.
import { BacnetDriver, ObjectType, PropertyIdentifier } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDiver({});
bacnetDriver
.readProperty({
address: '192.168.1.2',
deviceId: 2000,
objectType: ObjectType.ANALOG_INPUT,
objectInstance: 1,
propertyId: PropertyIdentifier.PRESENT_VALUE,
})
.then(console.log);
Name | Type | Description |
---|---|---|
options |
object | |
options.address |
string | IP address of the target device. |
options.deviceId |
number | The device ID of the target device. |
options.objectType |
number | The object ID to read. The ObjectType could be used from the bacnetDriver enum. |
options.objectInstance |
number | The object instance to read. |
options.propertyId |
number | The property ID in the specified object to read.The PropertyIdentifier could be used from the bacnetDriver enum. |
The returned value depens on the propertyID you have read. This is why the this method returns the Promise<any>
. But when you you the BacnetDriverExtended you can use the predefined methods and will get the correct return type.
Name | Type | Description |
---|---|---|
response |
Promise<any> | Promise<ReadString> | Promise<ReadNumber> | Promise<ReadObject> |
The writeProperty command writes a single property of an object to a device.
Please note, that the following ApplicationTags
are not supported (yet): TIMESTAMP, OBJECTIDENTIFIER, COV_SUBSCRIPTION, READ_ACCESS_RESULT, READ_ACCESS_SPECIFICATION.
Also, the device should have been found first via the WhoIs command before the write property command can be used.
import { BacnetDriver, ObjectType, PropertyIdentifier, ApplicationTags } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDiver({});
bacnetDriver
.writeProperty({
address: '192.168.1.2',
deviceId: 2000,
objectType: ObjectType.ANALOG_INPUT,
objectInstance: 1,
propertyId: PropertyIdentifier.OUT_OF_SERVICE,
values: [{ type: ApplicationTags.BOOLEAN, value: true }],
})
.then(console.log);
Name | Type | Description |
---|---|---|
options |
object | |
options.address |
string | IP address of the target device. |
options.deviceId |
number | The device ID of the target device. |
options.objectType |
number | The object ID to read. The ObjectType could be used from the bacnetDriver enum. |
options.objectInstance |
number | The object instance to read. |
options.propertyId |
number | The property ID in the specified object to read.The PropertyIdentifier could be used from the bacnetDriver enum. |
options.values |
array | |
options.values.type |
number | The data-type of the value to be written. The ApplicationTags could be used from the bacnetDriver enum. |
options.values.value |
any | The actual value to be written. |
Name | Type | Description |
---|---|---|
response |
Promise<void> |
The Subscribe COV command subscribes your application to a specific object in the BACNET device. If a value in the object changes, a covObservable will be fired. Note, the device should have been found first via the WhoIs command before the subscribe COV command can be used.
import { BacnetDriver, ObjectType } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDiver({});
bacnetDriver
.subscribeCov({
address: '192.168.1.2',
deviceId: 2000,
monitoredObject: { type: ObjectType.ANALOG_INPUT, instance: 8 },
cancellable: { issueConfirmedNotifications: false, lifetime: 120 },
})
.then(console.log);
Name | Type | Description |
---|---|---|
options |
object | |
options.address |
string | IP address of the target device. |
options.deviceId |
number | The device ID of the target device. |
options.monitoredObject |
object | |
options.monitoredObject.type |
number | The object ID to read. The ObjectType could be used from the bacnetDriver enum. |
options.monitoredObject.instance |
number | The object instance to read. |
options.processId |
number? | The process ID to use. When not given, the bacnet driver will generate one. |
options.cancellable |
object? | Optional |
options.cancellable.issueConfirmedNotifications |
boolean | Determines is the subscription should use confirmed or unconfirmed notifications |
options.cancellable.lifetime |
number | The lifetime of the subscription |
Name | Type | Description |
---|---|---|
response |
Promise<void> |
The BacnetDriverExtended has predefined BACNET methods for you to use, like: readObjectList
or readPresentValue
. Each of these methods make use of the readProperty
or writeProperty
method.
The readObjectList command reads the object list of a BACNET device.
import { BacnetDriverExtended, ObjectType, PropertyIdentifier } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDriverExtended({});
bacnetDriver
.readObjectList({
address: '192.168.1.2',
deviceId: 2000,
})
.then(console.log);
Name | Type | Description |
---|---|---|
options |
object | |
options.address |
string | IP address of the target device. |
options.deviceId |
number | The device ID of the target device. |
Name | Type | Description |
---|---|---|
response |
Promise<ReadObject[]> |
The readPresentValue command reads the object list of a BACNET device.
import { BacnetDriverExtended, ObjectType } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDriverExtended({});
bacnetDriver
.readPresentValue({
address: '192.168.1.2',
deviceId: 2000,
objectType: ObjectType.ANALOG_INPUT,
objectInstance: 100,
})
.then(console.log);
Name | Type | Description |
---|---|---|
options |
object | |
options.address |
string | IP address of the target device. |
options.deviceId |
number | The device ID of the target device. |
options.objectType |
number | The object ID to read. The ObjectType could be used from the bacnetDriver enum. |
options.objectInstance |
number | The object instance to read. |
Name | Type | Description |
---|---|---|
response |
Promise |
The activateNotificationClass command subscribes your application to limit alarms. This functions should be combined setAlarming with the corresponding BACnet objects.
import { BacnetDriverExtended, ObjectType } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDriverExtended({});
bacnet
.activateNotificationClasses({
address: '192.168.0.1',
deviceId: 1,
objectInstances: [100, 101, 102],
ipAddress: '192.168.0.254',
})
.then(() => {
/* REST OF CODE */
});
Name | Type | Description |
---|---|---|
options |
object | |
options.address |
string | IP address of the target device. |
options.deviceId |
number | The device ID of the target device. |
options.objectInstances |
number[] | The notification classes that should be activated. |
options.ipAddress |
string | The IP address of this devices's BACnet interface |
options.port |
number? | The port of this device's BACnet interface. Default: 0xBAC0 |
options.fromTime |
Date? | The start date from which to recieve alarm notifications. Default: 00:00:00.000 |
options.toTime |
Date? | The end date from which to recieve alarm notifications. Default: 23:59:59.000 |
options.transition |
object? | |
options.transition.toNormal |
boolean | Determines whether to recieve an alarm notification when the state changes to Normal state. Default: true |
options.transition.toOffNormal |
boolean | Determines whether to recieve an alarm notification when the state changes to OffNormal state. Default: true |
options.transition.toFault |
boolean | Determines whether to recieve an alarm notification when the state changes to Fault state. Default: true |
options.days |
object? | |
options.days.monday |
boolean | Determines whether to recieve an alarm notification on Mondays. Default: true |
options.days.tuesday |
boolean | Determines whether to recieve an alarm notification on Tuesdays. Default: true |
options.days.wednesday |
boolean | Determines whether to recieve an alarm notification on Wednesdays. Default: true |
options.days.thursday |
boolean | Determines whether to recieve an alarm notification on Thursdays. Default: true |
options.days.friday |
boolean | Determines whether to recieve an alarm notification on Fridays. Default: true |
options.days.saturday |
boolean | Determines whether to recieve an alarm notification on Saturdays. Default: true |
options.days.sunday |
boolean | Determines whether to recieve an alarm notification on Sundays. Default: true |
Name | Type | Description |
---|---|---|
response |
Promise | Combined with an active limit object can trigger an Alarm event |
The setAlarming command subscribes your application to limit alarms. This functions should be combined activateNotificationClass with the corresponding BACnet notification classes.
import { BacnetDriverExtended, ObjectType } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDriverExtended({});
bacnet.setAlarming({
address: '192.168.0.1',
deviceId: 1,
objectType: ObjectType.ANALOG_INPUT
objectInstance: 1,
lowlimit: { enable: true, overwriteLimitValue: 0 },
highlimit: { enable: true, overwriteLimitValue: 100 },
}).then(() => { /* REST OF CODE */ });
Name | Type | Description |
---|---|---|
options |
object | |
options.address |
string | IP address of the target device. |
options.deviceId |
number | The device ID of the target device. |
options.objectType |
number | The object ID to read. The ObjectType could be used from the bacnetDriver enum. |
options.objectInstance |
number | The object instance to read. |
options.lowLimit |
object | |
options.lowLimit.enable |
boolean | Enables or disables low limit alarming |
options.lowLimit.overwriteLimitValue |
number? | Optional parameter to overwrite the current low limit alarming value. |
options.highLimit |
object | |
options.highLimit.enable |
boolean | Enables or disables hihg limit alarming |
options.highLimit.overwriteLimitValue |
number? | Optional parameter to overwrite the current low high alarming value. |
Name | Type | Description |
---|---|---|
response |
Promise | Combined with an activated notification class can trigger an Alarm event |
This library makes use of RXJS library for event handling.
This subject will fire, when a devices react to the WhoIs command over the network or when a BACNET device has booted.
import { BacnetDriver } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDriver({});
bacnetDrive.iAmObservable.subscribe(console.log);
bacnetDriver.whoIs();
Name | Type | Description |
---|---|---|
response |
object | |
response.address |
string | IP address of the target device. |
response.deviceId |
number | The device ID of the target device. |
This subject will fire, when a object in the BACNET device has been changed and the Bacnet driver has a active subscription, see more (Subscribe Cov)[#Subscribe-Cov].
import { BacnetDriver, ObjectType } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDiver({});
bacnetDriver.covObservable.subscribe(console.log);
bacnetDriver
.subscribeCov({
address: '192.168.1.2',
deviceId: 2000,
monitoredObject: { type: ObjectType.ANALOG_INPUT, instance: 8 },
cancellable: { issueConfirmedNotifications: false, lifetime: 120 },
})
.then(console.log);
Name | Type | Description |
---|---|---|
response |
object | |
response.processId |
number | The process id used for this subscription. |
response.device |
object | |
response.device.instance |
number | The instance of the target device. |
response.device.type |
number | The object ID of the target device. NOTE: this value will always be 8, since it is given by the BACNET specs |
response.monitoredObject |
object | |
response.monitoredObject.type |
number | The object type of the changed object |
response.monitoredObject.instance |
number | The object instance of the changed object. |
response.timeRemaining |
number | The remaining time of the subscription in seconds |
response.cov |
array | |
response.cov.propertyType |
number | The property type for the changed value |
response.cov.value |
any | The new value of the changed property. The type depends on the property. |
This subject will fire, when a object in the BACNET device has changed its state. From normal -> off normal, for example. In order for this to work, the correct Notification class needs to be activated (see, activateNotificationClass) and the BACnet object need to have limit alarm enabled (see setAlarming). NOTE: CURRENT THIS IS NOT IMPLEMENTED YET...
import { BacnetDriverExtended, ObjectType } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDriverExtended({});
bacnetDriver.alarmingObservable.subscribe(console.log)
bacnet.setAlarming({
address: '192.168.0.1',
deviceId: 1,
objectType: ObjectType.ANALOG_INPUT
objectInstance: 1,
lowlimit: { enable: true, overwriteLimitValue: 0 },
highlimit: { enable: true, overwriteLimitValue: 100 },
});
bacnet.activateNotificationClasses({
address: '192.168.0.1',
deviceId: 1,
objectInstances: [ 100, 101, 102],
ipAddress: '192.168.0.254',
});
Name | Type | Description |
---|---|---|
response |
object | |
response.processNumber |
number | The processNumber of the subscription |
response.device |
object | The device from which the notification came from |
response.device.instance |
number | The instance number of the device |
response.device.type |
number | The type of the device (which will always be 8) |
response.object |
object | The object from which the notification came from |
response.object.instance |
number | The instance number of the object |
response.object.type |
number | The type of the object |
response.date |
Date | The date on which the notification has been send |
response.priority |
number | The priority given to the notification |
response.notificationClass |
number | The instance of the notificationClass the notification came from |
response.eventType |
number | The eventType of the notification. The complete eventType list can be found under the Enum's EventType |
response.notificationType |
number | The notification type of the notification. Complete list can be found under the Enum's NotifyType |
response.fromState |
number | The state the object was in. Complete list can be found under the Enum's EventState |
response.toState |
number | The state the object is in now. Complete list can be found under the Enum's EventState |
This subject will fire, when the UDP transporter has failed to send your message. This can occure when you gave this transporter an invalid IP address.
import { BacnetDriver } from '@bastiaanv/bacnet-driver';
const bacnetDriver = new BacnetDriver({});
bacnetDrive.errorObservable.subscribe(console.log);
Name | Type | Description |
---|---|---|
response |
Error | The error that has occured |
Copyright (c) 2020 Bastiaan Verhaar verhaar.bastiaan@gmail.com
Note: This is not an official product of the BACnet Advocacy Group. BACnet® is a registered trademark of American Society of Heating, Refrigerating and Air-Conditioning Engineers (ASHRAE).