├── test └── .gitkeep ├── icons └── .gitkeep ├── publish ├── .jshintrc ├── README.md ├── package.json ├── .gitignore ├── LICENSE-MIT ├── lib └── bacnet.js ├── bacnet.js └── bacnet.html /test/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icons/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /publish: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | npm version minor && npm publish && npm version patch && git push --tags && git push origin master -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": false, 5 | "eqeqeq": true, 6 | "immed": true, 7 | "indent": 4, 8 | "latedef": false, 9 | "newcap": true, 10 | "noarg": true, 11 | "quotmark": "single", 12 | "regexp": true, 13 | "undef": true, 14 | "unused": false, 15 | "strict": true, 16 | "trailing": true, 17 | "smarttabs": true, 18 | "lastsemic": true, 19 | "sub": true, 20 | "devel": true, 21 | "boss": true, 22 | "eqnull": true, 23 | "forin": false, 24 | "plusplus": false, 25 | "node": true, 26 | "esversion": 6, 27 | "globals":{ 28 | "describe": true, 29 | "it": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## node-red-contrib-bacnet 2 | 3 | [Node-RED][1] contribution package for [BACnet][2]. 4 | 5 | BACnet is a protocol to interact with building automation devices defined by ASHRAE. 6 | 7 | Uses BACnet protocol stack written in pure JavaScript. Check out [bacstack][3] to learn more. 8 | 9 | ### Install 10 | 11 | NOTE: Currently the module is not published, so you would need to use the following command: 12 | 13 | ``` 14 | $ npm install goliatone/node-red-contrib-bacnet 15 | ``` 16 | 17 | ``` 18 | $ npm install --save node-red-contrib-bacnet 19 | ``` 20 | 21 | ### How to use 22 | 23 | ### Debug 24 | 25 | 26 | ``` 27 | DEBUG=node_red_contrib_banet node-red -v 28 | ``` 29 | 30 | 31 | [1]:https://nodered.org/ 32 | [2]:http://www.bacnet.org/ 33 | [3]:https://www.npmjs.com/package/bacstack/ 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-bacnet", 3 | "version": "0.0.0", 4 | "description": "Node-RED contrib node to support BACnet systems", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/goliatone/node-red-contrib-bacnet.git" 12 | }, 13 | "keywords": [ 14 | "bacnet", 15 | "automation", 16 | "iot" 17 | ], 18 | "author": "goliatone (http://goliatone.com/)", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/goliatone/node-red-contrib-bacnet/issues" 22 | }, 23 | "homepage": "https://github.com/goliatone/node-red-contrib-bacnet#readme", 24 | "dependencies": { 25 | "bacstack": "0.0.1-beta.11" 26 | }, 27 | "node-red": { 28 | "nodes": { 29 | "bacnet": "bacnet.js" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/6c75292e1e06f78707dc7fe1d2c7b1130f3c8706/Global/Archives.gitignore 2 | 3 | # It's better to unpack these files and commit the raw source because 4 | # git has its own built in compression methods. 5 | *.7z 6 | *.jar 7 | *.rar 8 | *.zip 9 | *.gz 10 | *.bzip 11 | *.bz2 12 | *.xz 13 | *.lzma 14 | 15 | #packing-only formats 16 | *.iso 17 | *.tar 18 | 19 | #package management formats 20 | *.dmg 21 | *.xpi 22 | *.gem 23 | *.egg 24 | *.deb 25 | *.rpm 26 | 27 | 28 | ### https://raw.github.com/github/gitignore/6c75292e1e06f78707dc7fe1d2c7b1130f3c8706/Global/OSX.gitignore 29 | 30 | .DS_Store 31 | .AppleDouble 32 | .LSOverride 33 | Icon 34 | 35 | 36 | # Thumbnails 37 | ._* 38 | 39 | # Files that might appear on external disk 40 | .Spotlight-V100 41 | .Trashes 42 | 43 | 44 | ### https://raw.github.com/github/gitignore/6c75292e1e06f78707dc7fe1d2c7b1130f3c8706/Node.gitignore 45 | 46 | lib-cov 47 | *.seed 48 | *.log 49 | *.csv 50 | *.dat 51 | *.out 52 | *.pid 53 | *.gz 54 | 55 | pids 56 | logs 57 | results 58 | 59 | npm-debug.log 60 | node_modules 61 | */.repl_history 62 | */.caretaker_* 63 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 goliatone 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/bacnet.js: -------------------------------------------------------------------------------- 1 | const bacnet = require('bacstack'); 2 | 3 | class Bacnet { 4 | constructor(options) { 5 | super(); 6 | this.options = options; 7 | this.devices = new Map(); 8 | this.connect(this.options); 9 | } 10 | 11 | onDestroy() { 12 | if (this.client) { 13 | this.client.removeAllListeners('iAm'); 14 | this.client.close(); 15 | } 16 | } 17 | 18 | connect(options) { 19 | this.client = new bacnet(options); 20 | this.client.on('iAm', (device) => this.onDeviceAdded(device)); 21 | } 22 | 23 | onDeviceAdded(device) { 24 | this.devices.set(device.deviceId, device); 25 | } 26 | 27 | whoIs(lowLimit, highLimit, address, deviceListener) { 28 | lowLimit = this.toNumber(lowLimit); 29 | highLimit = this.toNumber(highLimit); 30 | this.client.on('iAm', deviceListener); 31 | this.client.whoIs(lowLimit, highLimit, address); 32 | } 33 | 34 | readProperty(address, objectType, objectInstance, propertyId, arrayIndex) { 35 | return new Promise((resolve, reject) => { 36 | if (!this.client) { return reject('Not connected'); } 37 | 38 | this.client.readProperty(address, objectType, objectInstance, propertyId, arrayIndex, (err, value) => { 39 | if (err) { return reject(err); } 40 | return resolve(value); 41 | }); 42 | }); 43 | } 44 | 45 | writeProperty(address, objectType, objectInstance, propertyId, priority, valueList) { 46 | priority = this.toNumber(priority) || 1; 47 | 48 | return new Promise((resolve, reject) => { 49 | if (!this.client) { return reject('Not connected'); } 50 | this.client.writeProperty(address, objectType, objectInstance, propertyId, priority, valueList, (err, value) => { 51 | if (err) { return reject(err); } 52 | return resolve(value); 53 | }) 54 | }); 55 | } 56 | 57 | readPropertyMultiple(address, requestArray) { 58 | return new Promise((resolve, reject) => { 59 | if (!this.client) { return reject('Not connected'); } 60 | 61 | this.client.readPropertyMultiple(address, requestArray, (err, value) => { 62 | if (err) { return reject(err); } 63 | return resolve(value); 64 | }); 65 | }); 66 | } 67 | 68 | writePropertyMultiple(address, valueList) { 69 | return new Promise((resolve, reject) => { 70 | if (!this.client) { return reject('Not connected'); } 71 | 72 | this.client.writePropertyMultiple(address, valueList, (err, value) => { 73 | if (err) { return reject(err); } 74 | return resolve(value); 75 | }); 76 | }); 77 | } 78 | 79 | toNumber(value) { 80 | return isNaN(parseInt(value)) ? undefined : +value; 81 | } 82 | } 83 | 84 | module.exports = Bacnet; 85 | -------------------------------------------------------------------------------- /bacnet.js: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | const Bacnet = require('./lib/bacnet'); 3 | 4 | /** 5 | * Backnet Server Node 6 | */ 7 | class BacnetServerNode { 8 | constructor(config) { 9 | RED.nodes.createNode(this, config); 10 | this.connection = new Bacnet(config); 11 | this.on('close', () => this.connection.onDestroy()); 12 | } 13 | } 14 | RED.nodes.registerType('bacnet-server', BacnetServerNode); 15 | 16 | /** 17 | * BacnetDiscovery 18 | * The whoIs command discovers all BACnet 19 | * devices in the network. 20 | */ 21 | class BacnetDiscovery { 22 | constructor(config) { 23 | RED.nodes.createNode(this, config); 24 | this.status({fill:'green', shape: 'dot', text: 'connected'}); 25 | 26 | const { lowLimit, highLimit, address } = config; 27 | const server = RED.nodes.getNode(config.server); 28 | server.connection.whoIs(lowLimit, highLimit, address, (device) => this.send({ device: device })); 29 | } 30 | } 31 | RED.nodes.registerType('bacnet-discovery', BacnetDiscovery); 32 | 33 | /** 34 | * Bacnet Read Property 35 | * 36 | * The readProperty command reads a single 37 | * property of an object from a device. 38 | */ 39 | class BacnetReadProperty { 40 | constructor(config) { 41 | RED.nodes.createNode(this, config); 42 | this.server = RED.nodes.getNode(config.server); 43 | this.defaults = config; 44 | this.on('input', this.onInput); 45 | } 46 | 47 | onInput(input) { 48 | const { address, objectType, objectInstance, propertyId, arrayIndex } = Object.assign({}, this.defaults, input.device); 49 | this.server.connection.readProperty( 50 | address, 51 | objectType, 52 | objectInstance, 53 | propertyId, 54 | arrayIndex 55 | ) 56 | .then((payload) => { 57 | const message = Object.assign({}, input, { payload: payload }); 58 | this.send(message); 59 | }) 60 | .catch((error) => this.error({ error: error })); 61 | }; 62 | } 63 | RED.nodes.registerType('bacnet-read', BacnetReadProperty); 64 | 65 | /** 66 | * Write Property 67 | * 68 | * The writeProperty command writes a single 69 | * property of an object to a device. 70 | */ 71 | class BacnetWriteProperty { 72 | constructor(config) { 73 | RED.nodes.createNode(this, config); 74 | this.server = RED.nodes.getNode(config.server); 75 | this.defaults = config; 76 | this.on('input', this.onInput); 77 | } 78 | 79 | onInput(input) { 80 | const valueList = [{ 81 | type: +(input.payload.applicationTag || this.defaults.applicationTag), 82 | value: (input.payload.value || this.defaults.value) + Math.random() * 10000 83 | }]; 84 | const { address, objectType, objectInstance, propertyId, priority } = Object.assign({}, this.defaults, input.device, input.payload); 85 | console.log('write', valueList) 86 | this.server.connection.writeProperty( 87 | address, 88 | objectType, 89 | objectInstance, 90 | propertyId, 91 | priority, 92 | valueList 93 | ) 94 | .then(() => this.send(input)) 95 | .catch((error) => this.error({ error: error })); 96 | } 97 | } 98 | RED.nodes.registerType('bacnet-write', BacnetWriteProperty); 99 | 100 | /** 101 | * Bacnet Read Property Multiple 102 | * 103 | * The readPropertyMultiple command reads multiple properties from multiple device objects 104 | */ 105 | class BacnetReadPropertyMultiple { 106 | constructor(config) { 107 | RED.nodes.createNode(this, config); 108 | this.server = RED.nodes.getNode(config.server); 109 | this.defaults = config; 110 | this.on('input', this.onInput); 111 | } 112 | 113 | onInput(input) { 114 | if (!input.requestArray) { 115 | this.error('requestArray not provided for Bacnet read multiple.') 116 | return; 117 | } 118 | const { address } = Object.assign({}, this.defaults, input.device); 119 | this.server.connection.readPropertyMultiple( 120 | address, 121 | input.requestArray, 122 | ) 123 | .then((payload) => { 124 | const message = Object.assign({}, input, { payload: payload }); 125 | this.send(message); 126 | }) 127 | .catch((error) => this.error({ error: error })); 128 | }; 129 | } 130 | RED.nodes.registerType('bacnet-read-multiple', BacnetReadPropertyMultiple); 131 | 132 | /** 133 | * Bacnet Write Property Multiple 134 | * 135 | * The writePropertyMultiple command writes multiple properties to multiple device objects 136 | */ 137 | class BacnetWritePropertyMultiple { 138 | constructor(config) { 139 | RED.nodes.createNode(this, config); 140 | this.server = RED.nodes.getNode(config.server); 141 | this.defaults = config; 142 | this.on('input', this.onInput); 143 | } 144 | 145 | onInput(input) { 146 | if (!input.valueList) { 147 | this.error('valueList not provided for Bacnet write multiple.'); 148 | return; 149 | } 150 | 151 | const { address } = Object.assign({}, this.defaults, input.device); 152 | this.server.connection.writePropertyMultiple( 153 | address, 154 | input.valueList, 155 | ) 156 | .then(() => this.send(input)) 157 | .catch((error) => this.error({ error: error })); 158 | }; 159 | } 160 | RED.nodes.registerType('bacnet-write-multiple', BacnetWritePropertyMultiple); 161 | }; 162 | -------------------------------------------------------------------------------- /bacnet.html: -------------------------------------------------------------------------------- 1 | 19 | 20 | 29 | 30 | 57 | 58 | 59 | 85 | 86 | 107 | 108 | 144 | 145 | 146 | 181 | 182 | 192 | 193 | 237 | 238 | 239 | 302 | 303 | 314 | 315 | 367 | 368 | 369 | 383 | 384 | 409 | 410 | 433 | 434 | 435 | 449 | 450 | 494 | 495 | 518 | --------------------------------------------------------------------------------