diff --git a/index.js b/index.js index 1f868022..d42e4099 100644 --- a/index.js +++ b/index.js @@ -253,6 +253,42 @@ module.exports = function(settings) { client.reinitializeDevice(address, state, password, next); }; + self.writeFile = function(address, objectId, position, count, fileBuffer, next) { + client.writeFile(address, objectId, position, count, fileBuffer, next); + }; + + self.readFile = function(address, objectId, position, count, next) { + client.readFile(address, objectId, position, count, next); + }; + + self.readRange = function(address, objectId, idxBegin, quantity, next) { + client.readRange(address, objectId, idxBegin, quantity, next); + }; + + self.subscribeCOV = function(address, objectId, subscribeId, cancel, issueConfirmedNotifications, lifetime, next) { + client.subscribeCOV(address, objectId, subscribeId, cancel, issueConfirmedNotifications, lifetime, next); + }; + + self.subscribeProperty = function(address, objectId, monitoredProperty, subscribeId, cancel, issueConfirmedNotifications, next) { + client.subscribeProperty(address, objectId, monitoredProperty, subscribeId, cancel, issueConfirmedNotifications, next); + }; + + self.createObject = function(address, objectId, valueList, next) { + client.createObject(address, objectId, valueList, next); + }; + + self.deleteObject = function(address, objectId, next) { + client.deleteObject(address, objectId, next); + }; + + self.removeListElement = function(address, objectId, reference, valueList, next) { + client.removeListElement(address, objectId, reference, valueList, next); + }; + + self.addListElement = function(address, objectId, reference, valueList, next) { + client.addListElement(address, objectId, reference, valueList, next); + }; + /** * Unloads the current BACstack instance and closes the underlying UDP socket. * @function bacstack.close diff --git a/lib/bacnet-client.js b/lib/bacnet-client.js index 6a724685..c7d494fd 100644 --- a/lib/bacnet-client.js +++ b/lib/bacnet-client.js @@ -372,6 +372,147 @@ module.exports = function(settings) { }); }; + self.writeFile = function(address, objectId, position, count, fileBuffer, next) { + var maxSegments = baEnum.BacnetMaxSegments.MAX_SEG65; + var buffer = getBuffer(); + var invokeId = getInvokeId(); + baNpdu.encode(buffer, baEnum.BacnetNpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.BacnetNpduControls.EXPECTING_REPLY, address); + baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, maxSegments, baEnum.BacnetMaxAdpu.MAX_APDU1476, invokeId, 0, 0); + baServices.encodeAtomicWriteFile(buffer, true, objectId, position, 1, fileBuffer, count); + baBvlc.encode(buffer.buffer, baEnum.BacnetBvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset); + transport.send(buffer.buffer, buffer.offset, address); + addCallback(invokeId, function(err, data) { + if (err) return next(err); + var result = baServices.decodeAtomicWriteFileAcknowledge(data.buffer, data.offset, data.length); + if (!result) return next(new Error('INVALID_DECODING')); + next(null, result); + }); + }; + + self.readFile = function(address, objectId, position, count, next) { + var maxSegments = baEnum.BacnetMaxSegments.MAX_SEG65; + var buffer = getBuffer(); + var invokeId = getInvokeId(); + baNpdu.encode(buffer, baEnum.BacnetNpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.BacnetNpduControls.EXPECTING_REPLY, address); + baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.BacnetConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE, maxSegments, baEnum.BacnetMaxAdpu.MAX_APDU1476, invokeId, 0, 0); + baServices.encodeAtomicReadFile(buffer, true, objectId, position, count); + baBvlc.encode(buffer.buffer, baEnum.BacnetBvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset); + transport.send(buffer.buffer, buffer.offset, address); + addCallback(invokeId, function(err, data) { + if (err) return next(err); + var result = baServices.decodeAtomicReadFileAcknowledge(data.buffer, data.offset, data.length); + if (!result) return next(new Error('INVALID_DECODING')); + next(null, result); + }); + }; + + self.readRange = function(address, objectId, idxBegin, quantity, next) { + var maxSegments = baEnum.BacnetMaxSegments.MAX_SEG65; + var buffer = getBuffer(); + var invokeId = getInvokeId(); + baNpdu.encode(buffer, baEnum.BacnetNpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.BacnetNpduControls.EXPECTING_REPLY, address); + baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.BacnetConfirmedServices.SERVICE_CONFIRMED_READ_RANGE, maxSegments, baEnum.BacnetMaxAdpu.MAX_APDU1476, invokeId, 0, 0); + baServices.encodeReadRange(buffer, objectId, baEnum.BacnetPropertyIds.PROP_LOG_BUFFER, baAsn1.BACNET_ARRAY_ALL, baEnum.BacnetReadRangeRequestTypes.RR_BY_POSITION, idxBegin, new Date(), quantity); + baBvlc.encode(buffer.buffer, baEnum.BacnetBvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset); + transport.send(buffer.buffer, buffer.offset, address); + addCallback(invokeId, function(err, data) { + if (err) return next(err); + var result = baServices.decodeReadRangeAcknowledge(data.buffer, data.offset, data.length); + if (!result) return next(new Error('INVALID_DECODING')); + next(null, result); + }); + }; + + self.subscribeCOV = function(address, objectId, subscribeId, cancel, issueConfirmedNotifications, lifetime, next) { + var maxSegments = baEnum.BacnetMaxSegments.MAX_SEG65; + var buffer = getBuffer(); + var invokeId = getInvokeId(); + baNpdu.encode(buffer, baEnum.BacnetNpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.BacnetNpduControls.EXPECTING_REPLY, address); + baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV, maxSegments, baEnum.BacnetMaxAdpu.MAX_APDU1476, invokeId, 0, 0); + baServices.encodeSubscribeCOV(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, lifetime); + baBvlc.encode(buffer.buffer, baEnum.BacnetBvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset); + transport.send(buffer.buffer, buffer.offset, address); + addCallback(invokeId, function(err, data) { + if (err) return next(err); + next(); + }); + }; + + self.subscribeProperty = function(address, objectId, monitoredProperty, subscribeId, cancel, issueConfirmedNotifications, next) { + var maxSegments = baEnum.BacnetMaxSegments.MAX_SEG65; + var buffer = getBuffer(); + var invokeId = getInvokeId(); + baNpdu.encode(buffer, baEnum.BacnetNpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.BacnetNpduControls.EXPECTING_REPLY, address); + baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY, maxSegments, baEnum.BacnetMaxAdpu.MAX_APDU1476, invokeId, 0, 0); + baServices.encodeSubscribeProperty(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, 0, monitoredProperty, false, 0x0f); + baBvlc.encode(buffer.buffer, baEnum.BacnetBvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset); + transport.send(buffer.buffer, buffer.offset, address); + addCallback(invokeId, function(err, data) { + if (err) return next(err); + next(); + }); + }; + + self.createObject = function(address, objectId, valueList, next) { + var maxSegments = baEnum.BacnetMaxSegments.MAX_SEG65; + var buffer = getBuffer(); + var invokeId = getInvokeId(); + baNpdu.encode(buffer, baEnum.BacnetNpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.BacnetNpduControls.EXPECTING_REPLY, address); + baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.BacnetConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT, maxSegments, baEnum.BacnetMaxAdpu.MAX_APDU1476, invokeId, 0, 0); + baServices.encodeCreateProperty(buffer, objectId, valueList); + baBvlc.encode(buffer.buffer, baEnum.BacnetBvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset); + transport.send(buffer.buffer, buffer.offset, address); + addCallback(invokeId, function(err, data) { + if (err) return next(err); + next(); + }); + }; + + self.deleteObject = function(address, objectId, next) { + var maxSegments = baEnum.BacnetMaxSegments.MAX_SEG65; + var buffer = getBuffer(); + var invokeId = getInvokeId(); + baNpdu.encode(buffer, baEnum.BacnetNpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.BacnetNpduControls.EXPECTING_REPLY, address); + baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.BacnetConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT, maxSegments, baEnum.BacnetMaxAdpu.MAX_APDU1476, invokeId, 0, 0); + baServices.encodeDeleteObject(buffer, objectId); + baBvlc.encode(buffer.buffer, baEnum.BacnetBvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset); + transport.send(buffer.buffer, buffer.offset, address); + addCallback(invokeId, function(err, data) { + if (err) return next(err); + next(); + }); + }; + + self.removeListElement = function(address, objectId, reference, valueList, next) { + var maxSegments = baEnum.BacnetMaxSegments.MAX_SEG65; + var buffer = getBuffer(); + var invokeId = getInvokeId(); + baNpdu.encode(buffer, baEnum.BacnetNpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.BacnetNpduControls.EXPECTING_REPLY, address); + baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.BacnetConfirmedServices.SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT, maxSegments, baEnum.BacnetMaxAdpu.MAX_APDU1476, invokeId, 0, 0); + baServices.encodeAddListElement(buffer, objectId, reference.propertyIdentifier, reference.propertyArrayIndex, valueList); + baBvlc.encode(buffer.buffer, baEnum.BacnetBvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset); + transport.send(buffer.buffer, buffer.offset, address); + addCallback(invokeId, function(err, data) { + if (err) return next(err); + next(); + }); + }; + + self.addListElement = function(address, objectId, reference, valueList, next) { + var maxSegments = baEnum.BacnetMaxSegments.MAX_SEG65; + var buffer = getBuffer(); + var invokeId = getInvokeId(); + baNpdu.encode(buffer, baEnum.BacnetNpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.BacnetNpduControls.EXPECTING_REPLY, address); + baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.BacnetPduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.BacnetConfirmedServices.SERVICE_CONFIRMED_ADD_LIST_ELEMENT, maxSegments, baEnum.BacnetMaxAdpu.MAX_APDU1476, invokeId, 0, 0); + baServices.encodeAddListElement(buffer, objectId, reference.propertyIdentifier, reference.propertyArrayIndex, valueList); + baBvlc.encode(buffer.buffer, baEnum.BacnetBvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset); + transport.send(buffer.buffer, buffer.offset, address); + addCallback(invokeId, function(err, data) { + if (err) return next(err); + next(); + }); + }; + self.close = function() { transport.close(); }; diff --git a/lib/bacnet-services.js b/lib/bacnet-services.js index 6ffab0eb..417773e7 100644 --- a/lib/bacnet-services.js +++ b/lib/bacnet-services.js @@ -920,17 +920,6 @@ module.exports.encodeWriteObjectMultiple = function(buffer, valueList) { }); }; -module.exports.decodeDeleteObject = function(buffer, offset, apduLen) { - var result = baAsn1.decodeTagNumberAndValue(buffer, offset); - if (result.tagNumber === 12) return; - var len = 1; - var value = baAsn1.decodeObjectId(buffer, offset + len); - len += value.len; - if (len !== apduLen) return; - value.len = len; - return value; -}; - module.exports.encodeCreateObjectAcknowledge = function(buffer, objectId) { baAsn1.encodeApplicationObjectId(buffer, objectId.type, objectId.instance); }; @@ -1379,10 +1368,19 @@ module.exports.decodeReadRangeAcknowledge = function(buffer, offset, apduLen) { }; }; -module.exports.encodeWriteObjectMultiple = function(buffer, valueList) { - valueList.forEach(function(rValue) { - encodeWritePropertyMultiple(buffer, rValue.objectIdentifier, rValue.values); - }); +module.exports.decodeDeleteObject = function(buffer, offset, apduLen) { + var result = baAsn1.decodeTagNumberAndValue(buffer, offset); + if (result.tagNumber !== 12) return; + var len = 1; + var value = baAsn1.decodeObjectId(buffer, offset + len); + len += value.len; + if (len !== apduLen) return; + value.len = len; + return value; +}; + +module.exports.encodeDeleteObject = function(buffer, objectId) { + baAsn1.encodeApplicationObjectId(buffer, objectId.type, objectId.instance); }; // TODO: Implement decoding for following functions @@ -1503,21 +1501,6 @@ module.exports.encodePrivateTransferAcknowledge = function(buffer, vendorID, ser baAsn1.encodeClosingTag(buffer, 2); }; -module.exports.decodeDeleteObject = function(buffer, offset, apduLen) { - var result = baAsn1.decodeTagNumberAndValue(buffer, offset); - if (result.tagNumber === 12) return; - var len = 1; - var value = baAsn1.decodeObjectId(buffer, offset + len); - len += value.len; - if (len !== apduLen) return; - value.len = len; - return value; -}; - -module.exports.encodeCreateObjectAcknowledge = function(buffer, objectId) { - baAsn1.encodeApplicationObjectId(buffer, objectId.type, objectId.instance); -}; - module.exports.encodeCOVNotify = function(buffer, subscriberProcessIdentifier, initiatingDeviceIdentifier, monitoredObjectIdentifier, timeRemaining, values) { baAsn1.encodeContextUnsigned(buffer, 0, subscriberProcessIdentifier); baAsn1.encodeContextObjectId(buffer, 1, baEnum.BacnetObjectTypes.OBJECT_DEVICE, initiatingDeviceIdentifier); diff --git a/test/integration/add-list-element.spec.js b/test/integration/add-list-element.spec.js new file mode 100644 index 00000000..7b3af632 --- /dev/null +++ b/test/integration/add-list-element.spec.js @@ -0,0 +1,16 @@ +var expect = require('chai').expect; +var utils = require('./utils'); + +describe('bacstack - addListElement integration', function() { + it('should return a timeout error if no device is available', function(next) { + var client = new utils.bacnetClient({adpuTimeout: 200}); + client.addListElement('127.0.0.1', {type: 19, instance: 101}, {propertyIdentifier: 80, propertyArrayIndex: 0}, [ + {type: 1, value: true} + ], function(err, value) { + expect(err).to.eql(new Error('ERR_TIMEOUT')); + expect(value).to.eql(undefined); + client.close(); + next(); + }); + }); +}); diff --git a/test/integration/create-object.spec.js b/test/integration/create-object.spec.js new file mode 100644 index 00000000..66ad67f9 --- /dev/null +++ b/test/integration/create-object.spec.js @@ -0,0 +1,16 @@ +var expect = require('chai').expect; +var utils = require('./utils'); + +describe('bacstack - createObject integration', function() { + it('should return a timeout error if no device is available', function(next) { + var client = new utils.bacnetClient({adpuTimeout: 200}); + client.createObject('127.0.0.1', {type: 2, instance: 300}, [ + {property: {propertyIdentifier: 85, propertyArrayIndex: 1}, value: [{type: 1, value: true}]} + ], function(err, value) { + expect(err).to.eql(new Error('ERR_TIMEOUT')); + expect(value).to.eql(undefined); + client.close(); + next(); + }); + }); +}); diff --git a/test/integration/delete-object.spec.js b/test/integration/delete-object.spec.js new file mode 100644 index 00000000..8a5bb9fb --- /dev/null +++ b/test/integration/delete-object.spec.js @@ -0,0 +1,14 @@ +var expect = require('chai').expect; +var utils = require('./utils'); + +describe('bacstack - deleteObject integration', function() { + it('should return a timeout error if no device is available', function(next) { + var client = new utils.bacnetClient({adpuTimeout: 200}); + client.deleteObject('127.0.0.1', {type: 2, instance: 15}, function(err, value) { + expect(err).to.eql(new Error('ERR_TIMEOUT')); + expect(value).to.eql(undefined); + client.close(); + next(); + }); + }); +}); diff --git a/test/integration/read-file.spec.js b/test/integration/read-file.spec.js new file mode 100644 index 00000000..c2251b5c --- /dev/null +++ b/test/integration/read-file.spec.js @@ -0,0 +1,14 @@ +var expect = require('chai').expect; +var utils = require('./utils'); + +describe('bacstack - readFile integration', function() { + it('should return a timeout error if no device is available', function(next) { + var client = new utils.bacnetClient({adpuTimeout: 200}); + client.readFile('127.0.0.1', {type: 10, instance: 100}, 0, 100, function(err, value) { + expect(err).to.eql(new Error('ERR_TIMEOUT')); + expect(value).to.eql(undefined); + client.close(); + next(); + }); + }); +}); diff --git a/test/integration/read-range.spec.js b/test/integration/read-range.spec.js new file mode 100644 index 00000000..53eb73b3 --- /dev/null +++ b/test/integration/read-range.spec.js @@ -0,0 +1,14 @@ +var expect = require('chai').expect; +var utils = require('./utils'); + +describe('bacstack - readRange integration', function() { + it('should return a timeout error if no device is available', function(next) { + var client = new utils.bacnetClient({adpuTimeout: 200}); + client.readRange('127.0.0.1', {type: 20, instance: 0}, 0, 200, function(err, value) { + expect(err).to.eql(new Error('ERR_TIMEOUT')); + expect(value).to.eql(undefined); + client.close(); + next(); + }); + }); +}); diff --git a/test/integration/remove-list-element.spec.js b/test/integration/remove-list-element.spec.js new file mode 100644 index 00000000..cbe79453 --- /dev/null +++ b/test/integration/remove-list-element.spec.js @@ -0,0 +1,16 @@ +var expect = require('chai').expect; +var utils = require('./utils'); + +describe('bacstack - removeListElement integration', function() { + it('should return a timeout error if no device is available', function(next) { + var client = new utils.bacnetClient({adpuTimeout: 200}); + client.removeListElement('127.0.0.1', {type: 19, instance: 100}, {propertyIdentifier: 80, propertyArrayIndex: 0}, [ + {type: 1, value: true} + ], function(err, value) { + expect(err).to.eql(new Error('ERR_TIMEOUT')); + expect(value).to.eql(undefined); + client.close(); + next(); + }); + }); +}); diff --git a/test/integration/subscribe-cov.spec.js b/test/integration/subscribe-cov.spec.js new file mode 100644 index 00000000..a21561b3 --- /dev/null +++ b/test/integration/subscribe-cov.spec.js @@ -0,0 +1,14 @@ +var expect = require('chai').expect; +var utils = require('./utils'); + +describe('bacstack - subscribeCOV integration', function() { + it('should return a timeout error if no device is available', function(next) { + var client = new utils.bacnetClient({adpuTimeout: 200}); + client.subscribeCOV('127.0.0.1', {type: 5, instance: 3}, 7, false, false, 0, function(err, value) { + expect(err).to.eql(new Error('ERR_TIMEOUT')); + expect(value).to.eql(undefined); + client.close(); + next(); + }); + }); +}); diff --git a/test/integration/subscribe-property.spec.js b/test/integration/subscribe-property.spec.js new file mode 100644 index 00000000..14ce8df7 --- /dev/null +++ b/test/integration/subscribe-property.spec.js @@ -0,0 +1,14 @@ +var expect = require('chai').expect; +var utils = require('./utils'); + +describe('bacstack - subscribeProperty integration', function() { + it('should return a timeout error if no device is available', function(next) { + var client = new utils.bacnetClient({adpuTimeout: 200}); + client.subscribeProperty('127.0.0.1', {type: 5, instance: 33}, {propertyIdentifier: 80, propertyArrayIndex: 0}, 8, false, false, function(err, value) { + expect(err).to.eql(new Error('ERR_TIMEOUT')); + expect(value).to.eql(undefined); + client.close(); + next(); + }); + }); +}); diff --git a/test/integration/write-file.spec.js b/test/integration/write-file.spec.js new file mode 100644 index 00000000..5a202db1 --- /dev/null +++ b/test/integration/write-file.spec.js @@ -0,0 +1,14 @@ +var expect = require('chai').expect; +var utils = require('./utils'); + +describe('bacstack - writeFile integration', function() { + it('should return a timeout error if no device is available', function(next) { + var client = new utils.bacnetClient({adpuTimeout: 200}); + client.writeFile('127.0.0.1', {type: 10, instance: 2}, 0, 4, [5, 6, 7 , 8], function(err, value) { + expect(err).to.eql(new Error('ERR_TIMEOUT')); + expect(value).to.eql(undefined); + client.close(); + next(); + }); + }); +}); diff --git a/test/unit/bacnet-services.spec.js b/test/unit/bacnet-services.spec.js index 3f4e7578..d5af344f 100644 --- a/test/unit/bacnet-services.spec.js +++ b/test/unit/bacnet-services.spec.js @@ -1584,4 +1584,17 @@ describe('bacstack - Services layer', function() { }); }); }); + + describe('DeleteObject', function() { + it('should successfully encode and decode', function() { + var buffer = utils.getBuffer(); + baServices.encodeDeleteObject(buffer, {type: 1, instance: 10}); + var result = baServices.decodeDeleteObject(buffer.buffer, 0, buffer.offset); + delete result.len; + expect(result).to.deep.equal({ + objectType: 1, + instance: 10 + }); + }); + }); });