Skip to content

Commit

Permalink
feat(client): implement missing un/confirmed event handler
Browse files Browse the repository at this point in the history
  • Loading branch information
fh1ch committed Jan 10, 2018
1 parent fc40313 commit 0914a20
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 10 deletions.
20 changes: 10 additions & 10 deletions README.md
Expand Up @@ -39,8 +39,8 @@ following services are already supported at this point in time:
| Read Range | yes¹ | yes¹ |
| Write Property | yes | yes¹ |
| Write Property Multiple | yes | yes¹ |
| Add List Element | yes¹ | |
| Remove List Element | yes¹ | |
| Add List Element | yes¹ | yes¹ |
| Remove List Element | yes¹ | yes¹ |
| Create Object | yes¹ | yes¹ |
| Delete Object | yes¹ | yes¹ |
| Subscribe COV | yes¹ | yes¹ |
Expand All @@ -49,14 +49,14 @@ following services are already supported at this point in time:
| Atomic Write File | yes¹ | yes¹ |
| Reinitialize Device | yes | yes¹ |
| Device Communication Control | yes | yes¹ |
| Get Alarm Summary | yes¹ | |
| Get Event Information | yes¹ | |
| Get Enrollment Summary | | |
| Acknowledge Alarm | yes¹ | |
| Confirmed Event Notification | | yes¹ |
| Unconfirmed Event Notification | | yes¹ |
| Unconfirmed Private Transfer | | |
| Confirmed Private Transfer | | |
| Get Alarm Summary | yes¹ | yes¹ |
| Get Event Information | yes¹ | yes¹ |
| Get Enrollment Summary | yes¹ | yes¹ |
| Acknowledge Alarm | yes¹ | yes¹ |
| Confirmed Event Notification | yes¹ | yes¹ |
| Unconfirmed Event Notification | yes¹ | yes¹ |
| Unconfirmed Private Transfer | yes¹ | yes¹ |
| Confirmed Private Transfer | yes¹ | yes¹ |

¹ Support implemented as Beta (untested, undocumented, breaking interface)

Expand Down
115 changes: 115 additions & 0 deletions lib/client.js
Expand Up @@ -228,6 +228,36 @@ class Client extends EventEmitter {
result = baServices.decodeDeleteObject(buffer, offset, length);
if (!result) return debug('Received invalid deleteObject message');
this.emit('deleteObject', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM) {
result = baServices.decodeAlarmAcknowledge(buffer, offset, length);
if (!result) return debug('Received invalid alarmAcknowledge message');
this.emit('alarmAcknowledge', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_GET_ALARM_SUMMARY) {
this.emit('getAlarmSummary', {address: address, invokeId: invokeId});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_GET_ENROLLMENT_SUMMARY) {
result = baServices.decodeGetEnrollmentSummary(buffer, offset, length);
if (!result) return debug('Received invalid getEntrollmentSummary message');
this.emit('getEntrollmentSummary', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_GET_EVENT_INFORMATION) {
result = baServices.decodeGetEventInformation(buffer, offset, length);
if (!result) return debug('Received invalid getEventInformation message');
this.emit('getEventInformation', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION) {
result = baServices.decodeLifeSafetyOperation(buffer, offset, length);
if (!result) return debug('Received invalid lifeSafetyOperation message');
this.emit('lifeSafetyOperation', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_ADD_LIST_ELEMENT) {
result = baServices.decodeAddListElement(buffer, offset, length);
if (!result) return debug('Received invalid addListElement message');
this.emit('addListElement', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT) {
result = baServices.decodeAddListElement(buffer, offset, length);
if (!result) return debug('Received invalid removeListElement message');
this.emit('removeListElement', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_PRIVATE_TRANSFER) {
result = baServices.decodePrivateTransfer(buffer, offset, length);
if (!result) return debug('Received invalid privateTransfer message');
this.emit('privateTransfer', {address: address, invokeId: invokeId, request: result});
} else {
debug('Received unsupported confirmed service request');
}
Expand Down Expand Up @@ -281,6 +311,14 @@ class Client extends EventEmitter {
result = baServices.decodeEventNotifyData(buffer, offset, length);
if (!result) return debug('Received invalid EventNotify message');
this.emit('eventNotify', {address: address, eventData: result.eventData});
} else if (service === baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_I_HAVE) {
result = baServices.decodeIhaveBroadcast(buffer, offset, length);
if (!result) return debug('Received invalid ihaveBroadcast message');
this.emit('ihaveBroadcast', {address: address, eventData: result.eventData});
} else if (service === baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_PRIVATE_TRANSFER) {
result = baServices.decodePrivateTransfer(buffer, offset, length);
if (!result) return debug('Received invalid privateTransfer message');
this.emit('privateTransfer', {address: address, eventData: result.eventData});
} else {
debug('Received unsupported unconfirmed service request');
}
Expand Down Expand Up @@ -962,6 +1000,83 @@ class Client extends EventEmitter {
});
}

confirmedPrivateTransfer(address, vendorId, serviceNumber, data, options, next) {
next = next || options;
const settings = {
maxSegments: options.maxSegments || baEnum.MaxSegments.MAX_SEG65,
maxAdpu: options.maxAdpu || baEnum.MaxAdpu.MAX_APDU1476,
invokeId: options.invokeId || this._getInvokeId()
};
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.NpduControls.EXPECTING_REPLY, address);
baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.ConfirmedServices.SERVICE_CONFIRMED_PRIVATE_TRANSFER, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodePrivateTransfer(buffer, vendorId, serviceNumber, data);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, address);
this._addCallback(settings.invokeId, (err, data) => {
if (err) return next(err);
next();
});
}

unconfirmedPrivateTransfer(address, vendorId, serviceNumber, data) {
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE, address);
baAdpu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_PRIVATE_TRANSFER);
baServices.encodePrivateTransfer(buffer, vendorId, serviceNumber, data);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, address);
}

getEnrollmentSummary(address, acknowledgmentFilter, options, next) {
next = next || options;
const settings = {
maxSegments: options.maxSegments || baEnum.MaxSegments.MAX_SEG65,
maxAdpu: options.maxAdpu || baEnum.MaxAdpu.MAX_APDU1476,
invokeId: options.invokeId || this._getInvokeId()
};
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.NpduControls.EXPECTING_REPLY, address);
baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.ConfirmedServices.SERVICE_CONFIRMED_GET_ENROLLMENT_SUMMARY, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeGetEnrollmentSummary(buffer, acknowledgmentFilter, options.enrollmentFilter, options.eventStateFilter, options.eventTypeFilter, options.priorityFilter, options.notificationClassFilter);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, address);
this._addCallback(settings.invokeId, (err, data) => {
if (err) return next(err);
const result = baServices.decodeGetEnrollmentSummaryAcknowledge(data.buffer, data.offset, data.length);
if (!result) return next(new Error('INVALID_DECODING'));
next(null, result);
});
}

unconfirmedEventNotification(address, eventNotification) {
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE, address);
baAdpu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION);
baServices.encodeEventNotifyData(buffer, eventNotification);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, address);
}

confirmedEventNotification(address, eventNotification, options, next) {
next = next || options;
const settings = {
maxSegments: options.maxSegments || baEnum.MaxSegments.MAX_SEG65,
maxAdpu: options.maxAdpu || baEnum.MaxAdpu.MAX_APDU1476,
invokeId: options.invokeId || this._getInvokeId()
};
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.NpduControls.EXPECTING_REPLY, address);
baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.ConfirmedServices.SERVICE_CONFIRMED_EVENT_NOTIFICATION, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeEventNotifyData(buffer, eventNotification);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, address);
this._addCallback(settings.invokeId, (err, data) => {
if (err) return next(err);
next();
});
}

// Public Device Functions
readPropertyResponse(receiver, invokeId, objectId, property, value) {
const buffer = this._getBuffer();
Expand Down
31 changes: 31 additions & 0 deletions test/integration/confirmed-event-notification.spec.js
@@ -0,0 +1,31 @@
'use strict';

const expect = require('chai').expect;
const utils = require('./utils');

describe('bacstack - confirmedEventNotification integration', () => {
it('should return a timeout error if no device is available', (next) => {
const client = new utils.bacnetClient({adpuTimeout: 200});
const date = new Date();
date.setMilliseconds(880);
client.confirmedEventNotification('127.0.0.1', {
processId: 3,
initiatingObjectId: {},
eventObjectId: {},
timeStamp: {type: 2, value: date},
notificationClass: 9,
priority: 7,
eventType: 2,
messageText: 'Test1234$',
notifyType: 1,
changeOfValueTag: 1,
changeOfValueChangeValue: 90,
changeOfValueStatusFlags: {bitsUsed: 24, value: [0xaa, 0xaa, 0xaa]}
}, (err, value) => {
expect(err.message).to.eql('ERR_TIMEOUT');
expect(value).to.eql(undefined);
client.close();
next();
});
});
});
16 changes: 16 additions & 0 deletions test/integration/confirmed-private-transfer.spec.js
@@ -0,0 +1,16 @@
'use strict';

const expect = require('chai').expect;
const utils = require('./utils');

describe('bacstack - confirmedPrivateTransfer integration', () => {
it('should return a timeout error if no device is available', (next) => {
const client = new utils.bacnetClient({adpuTimeout: 200});
client.confirmedPrivateTransfer('127.0.0.1', 0, 8, [0x00, 0xaa, 0xfa, 0xb1, 0x00], (err, value) => {
expect(err.message).to.eql('ERR_TIMEOUT');
expect(value).to.eql(undefined);
client.close();
next();
});
});
});
16 changes: 16 additions & 0 deletions test/integration/get-enrollment-summary.spec.js
@@ -0,0 +1,16 @@
'use strict';

const expect = require('chai').expect;
const utils = require('./utils');

describe('bacstack - getEnrollmentSummary integration', () => {
it('should return a timeout error if no device is available', (next) => {
const client = new utils.bacnetClient({adpuTimeout: 200});
client.getEnrollmentSummary('127.0.0.1', 0, {notificationClassFilter: 5}, (err, value) => {
expect(err.message).to.eql('ERR_TIMEOUT');
expect(value).to.eql(undefined);
client.close();
next();
});
});
});
29 changes: 29 additions & 0 deletions test/integration/unconfirmed-event-notification.spec.js
@@ -0,0 +1,29 @@
'use strict';

const expect = require('chai').expect;
const utils = require('./utils');

describe('bacstack - unconfirmedEventNotification integration', () => {
it('should correctly send a telegram', () => {
const client = new utils.bacnetClient({adpuTimeout: 200});
const date = new Date();
date.setMilliseconds(880);
client.unconfirmedEventNotification('127.0.0.1', {
processId: 3,
initiatingObjectId: {type: 60, instance: 12},
eventObjectId: {type: 61, instance: 1121},
timeStamp: {type: 2, value: date},
notificationClass: 9,
priority: 7,
eventType: 0,
messageText: 'Test1234$',
notifyType: 1,
ackRequired: true,
fromState: 5,
toState: 6,
changeOfBitstringReferencedBitString: {bitsUsed: 24, value: [0xaa, 0xaa, 0xaa]},
changeOfBitstringStatusFlags: {bitsUsed: 24, value: [0xaa, 0xaa, 0xaa]}
});
client.close();
});
});
12 changes: 12 additions & 0 deletions test/integration/unconfirmed-private-transfer.spec.js
@@ -0,0 +1,12 @@
'use strict';

const expect = require('chai').expect;
const utils = require('./utils');

describe('bacstack - unconfirmedPrivateTransfer integration', () => {
it('should correctly send a telegram', () => {
const client = new utils.bacnetClient({adpuTimeout: 200});
client.unconfirmedPrivateTransfer('127.0.0.1', 0, 7, [0x00, 0xaa, 0xfa, 0xb1, 0x00]);
client.close();
});
});

0 comments on commit 0914a20

Please sign in to comment.