'use strict';
// Util Modules
const EventEmitter = require('events').EventEmitter;
const debug = require('debug')('bacstack');
// Local Modules
const baTransport = require('./transport');
const baServices = require('./services');
const baAsn1 = require('./asn1');
const baAdpu = require('./adpu');
const baNpdu = require('./npdu');
const baBvlc = require('./bvlc');
const baEnum = require('./enum');
const DEFAULT_HOP_COUNT = 0xFF;
const BVLC_HEADER_LENGTH = 4;
/**
* To be able to communicate to BACNET devices, you have to initialize a new bacstack instance.
* @class bacstack
* @param {object=} this._settings - The options object used for parameterizing the bacstack.
* @param {number=} [options.port=47808] - BACNET communication port for listening and sending.
* @param {string=} options.interface - Specific BACNET communication interface if different from primary one.
* @param {string=} [options.broadcastAddress=255.255.255.255] - The address used for broadcast messages.
* @param {number=} [options.adpuTimeout=3000] - The timeout in milliseconds until a transaction should be interpreted as error.
* @example
* const bacnet = require('bacstack');
*
* const client = new bacnet({
* port: 47809, // Use BAC1 as communication port
* interface: '192.168.251.10', // Listen on a specific interface
* broadcastAddress: '192.168.251.255', // Use the subnet broadcast address
* adpuTimeout: 6000 // Wait twice as long for response
* });
*/
class Client extends EventEmitter {
constructor(options) {
super();
options = options || {};
this._invokeCounter = 1;
this._invokeStore = {};
this._lastSequenceNumber = 0;
this._segmentStore = [];
this._settings = {
port: options.port || 47808,
interface: options.interface,
transport: options.transport,
broadcastAddress: options.broadcastAddress || '255.255.255.255',
adpuTimeout: options.adpuTimeout || 3000
};
this._transport = this._settings.transport || new baTransport({
port: this._settings.port,
interface: this._settings.interface,
broadcastAddress: this._settings.broadcastAddress
});
// Setup code
this._transport.on('message', this._receiveData.bind(this));
this._transport.on('error', this._receiveError.bind(this));
this._transport.open();
}
// Helper utils
_getInvokeId() {
const id = this._invokeCounter++;
if (id >= 256) this._invokeCounter = 1;
return id - 1;
}
_invokeCallback(id, err, result) {
const callback = this._invokeStore[id];
if (callback) return callback(err, result);
debug('InvokeId ', id, ' not found -> drop package');
}
_addCallback(id, callback) {
const timeout = setTimeout(() => {
delete this._invokeStore[id];
callback(new Error('ERR_TIMEOUT'));
}, this._settings.adpuTimeout);
this._invokeStore[id] = (err, data) => {
clearTimeout(timeout);
delete this._invokeStore[id];
callback(err, data);
};
}
_getBuffer() {
return {
buffer: Buffer.alloc(this._transport.getMaxPayload()),
offset: BVLC_HEADER_LENGTH
};
}
// Service Handlers
_processError(invokeId, buffer, offset, length) {
const result = baServices.decodeError(buffer, offset, length);
if (!result) return debug('Couldn`t decode Error');
this._invokeCallback(invokeId, new Error('BacnetError - Class:' + result.class + ' - Code:' + result.code));
}
_processAbort(invokeId, reason) {
this._invokeCallback(invokeId, new Error('BacnetAbort - Reason:' + reason));
}
_segmentAckResponse(receiver, negative, server, originalInvokeId, sequencenumber, actualWindowSize) {
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE, receiver, null, DEFAULT_HOP_COUNT, baEnum.NetworkMessageTypes.NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK, 0);
baAdpu.encodeSegmentAck(buffer, baEnum.PduTypes.PDU_TYPE_SEGMENT_ACK | (negative ? baEnum.PduTypes.NEGATIVE_ACK : 0) | (server ? baEnum.PduTypes.SERVER : 0), originalInvokeId, sequencenumber, actualWindowSize);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, receiver);
}
_performDefaultSegmentHandling(sender, adr, type, service, invokeId, maxSegments, maxAdpu, sequencenumber, first, moreFollows, buffer, offset, length) {
if (first) {
this._segmentStore = [];
type &= ~baEnum.PduTypes.SEGMENTED_MESSAGE;
let adpuHeaderLen = 3;
if ((type & baEnum.PduTypes.PDU_TYPE_MASK) === baEnum.PduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST) {
adpuHeaderLen = 4;
}
const adpubuffer = this._getBuffer();
adpubuffer.offset = 0;
buffer.copy(adpubuffer.buffer, adpuHeaderLen, offset, offset + length);
if ((type & baEnum.PduTypes.PDU_TYPE_MASK) === baEnum.PduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST) {
baAdpu.encodeConfirmedServiceRequest(adpubuffer, type, service, maxSegments, maxAdpu, invokeId, 0, 0);
} else {
baAdpu.encodeComplexAck(adpubuffer, type, service, invokeId, 0, 0);
}
this._segmentStore.push(adpubuffer.buffer.slice(0, length + adpuHeaderLen));
} else {
this._segmentStore.push(buffer.slice(offset, offset + length));
}
if (!moreFollows) {
const apduBuffer = Buffer.concat(this._segmentStore);
this._segmentStore = [];
type &= ~baEnum.PduTypes.SEGMENTED_MESSAGE;
this._handlePdu(adr, type, apduBuffer, 0, apduBuffer.length);
}
}
_processSegment(receiver, type, service, invokeId, maxSegments, maxAdpu, server, sequencenumber, proposedWindowNumber, buffer, offset, length) {
let first = false;
if (sequencenumber === 0 && this._lastSequenceNumber === 0) {
first = true;
} else {
if (sequencenumber !== this._lastSequenceNumber + 1) {
return this._segmentAckResponse(receiver, true, server, invokeId, this._lastSequenceNumber, proposedWindowNumber);
}
}
this._lastSequenceNumber = sequencenumber;
const moreFollows = ((type & baEnum.PduTypes.MORE_FOLLOWS) === baEnum.PduTypes.MORE_FOLLOWS);
if (!moreFollows) {
this._lastSequenceNumber = 0;
}
if ((sequencenumber % proposedWindowNumber) === 0 || !moreFollows) {
this._segmentAckResponse(receiver, false, server, invokeId, sequencenumber, proposedWindowNumber);
}
this._performDefaultSegmentHandling(this, receiver, type, service, invokeId, maxSegments, maxAdpu, sequencenumber, first, moreFollows, buffer, offset, length);
}
_processConfirmedServiceRequest(address, type, service, maxSegments, maxAdpu, invokeId, buffer, offset, length) {
let result;
debug('Handle this._processConfirmedServiceRequest');
if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY) {
result = baServices.decodeReadProperty(buffer, offset, length);
if (!result) return debug('Received invalid readProperty message');
this.emit('readProperty', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY) {
result = baServices.decodeWriteProperty(buffer, offset, length);
if (!result) return debug('Received invalid writeProperty message');
this.emit('writeProperty', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE) {
result = baServices.decodeReadPropertyMultiple(buffer, offset, length);
if (!result) return debug('Received invalid readPropertyMultiple message');
this.emit('readPropertyMultiple', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE) {
result = baServices.decodeWritePropertyMultiple(buffer, offset, length);
if (!result) return debug('Received invalid writePropertyMultiple message');
this.emit('writePropertyMultiple', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_COV_NOTIFICATION) {
result = baServices.decodeCOVNotify(buffer, offset, length);
if (!result) return debug('Received invalid covNotify message');
this.emit('covNotify', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_ATOMIC_WRITE_FILE) {
result = baServices.decodeAtomicWriteFile(buffer, offset, length);
if (!result) return debug('Received invalid atomicWriteFile message');
this.emit('atomicWriteFile', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_ATOMIC_READ_FILE) {
result = baServices.decodeAtomicReadFile(buffer, offset, length);
if (!result) return debug('Received invalid atomicReadFile message');
this.emit('atomicReadFile', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV) {
result = baServices.decodeSubscribeCOV(buffer, offset, length);
if (!result) return debug('Received invalid subscribeCOV message');
this.emit('subscribeCOV', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY) {
result = baServices.decodeSubscribeProperty(buffer, offset, length);
if (!result) return debug('Received invalid subscribeProperty message');
this.emit('subscribeProperty', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL) {
result = baServices.decodeDeviceCommunicationControl(buffer, offset, length);
if (!result) return debug('Received invalid deviceCommunicationControl message');
this.emit('deviceCommunicationControl', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_REINITIALIZE_DEVICE) {
result = baServices.decodeReinitializeDevice(buffer, offset, length);
if (!result) return debug('Received invalid reinitializeDevice message');
this.emit('reinitializeDevice', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_EVENT_NOTIFICATION) {
result = baServices.decodeEventNotifyData(buffer, offset, length);
if (!result) return debug('Received invalid eventNotifyData message');
this.emit('eventNotifyData', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_READ_RANGE) {
result = baServices.decodeReadRange(buffer, offset, length);
if (!result) return debug('Received invalid readRange message');
this.emit('readRange', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_CREATE_OBJECT) {
result = baServices.decodeCreateObject(buffer, offset, length);
if (!result) return debug('Received invalid createObject message');
this.emit('createObject', {address: address, invokeId: invokeId, request: result});
} else if (service === baEnum.ConfirmedServices.SERVICE_CONFIRMED_DELETE_OBJECT) {
result = baServices.decodeDeleteObject(buffer, offset, length);
if (!result) return debug('Received invalid deleteObject message');
this.emit('deleteObject', {address: address, invokeId: invokeId, request: result});
} else {
debug('Received unsupported confirmed service request');
}
}
_processUnconfirmedServiceRequest(address, type, service, buffer, offset, length) {
let result;
debug('Handle this._processUnconfirmedServiceRequest');
if (service === baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_I_AM) {
result = baServices.decodeIamBroadcast(buffer, offset);
if (!result) return debug('Received invalid iAm message');
/**
* @event bacstack.iAm
* @param {object} device - An object representing the detected device.
* @param {string} device.address - The IP address of the detected device.
* @param {number} device.deviceId - The BACNET device-id of the detected device.
* @param {number} device.maxAdpu - The max ADPU size the detected device is supporting.
* @param {number} device.segmentation - The type of segmentation the detected device is supporting.
* @param {number} device.vendorId - The BACNET vendor-id of the detected device.
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* client.on('iAm', (device) => {
* console.log('address: ', device.address, ' - deviceId: ', device.deviceId, ' - maxAdpu: ', device.maxAdpu, ' - segmentation: ', device.segmentation, ' - vendorId: ', device.vendorId);
* });
*/
this.emit('iAm', {address: address, deviceId: result.deviceId, maxApdu: result.maxApdu, segmentation: result.segmentation, vendorId: result.vendorId});
} else if (service === baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS) {
result = baServices.decodeWhoIsBroadcast(buffer, offset, length);
if (!result) return debug('Received invalid WhoIs message');
this.emit('whoIs', {address: address, lowLimit: result.lowLimit, highLimit: result.highLimit});
} else if (service === baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_WHO_HAS) {
result = baServices.decodeWhoHasBroadcast(buffer, offset, length);
if (!result) return debug('Received invalid WhoHas message');
this.emit('whoHas', {address: address, lowLimit: result.lowLimit, highLimit: result.highLimit, objectId: result.objectId, objectName: result.objectName});
} else if (service === baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_COV_NOTIFICATION) {
result = baServices.decodeCOVNotify(buffer, offset, length);
if (!result) return debug('Received invalid covNotifyUnconfirmed message');
this.emit('covNotifyUnconfirmed', {address: address, request: result});
} else if (service === baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION) {
result = baServices.decodeTimeSync(buffer, offset, length);
if (!result) return debug('Received invalid TimeSync message');
this.emit('timeSync', {address: address, dateTime: result.dateTime});
} else if (service === baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION) {
result = baServices.decodeTimeSync(buffer, offset, length);
if (!result) return debug('Received invalid TimeSyncUTC message');
this.emit('timeSyncUTC', {address: address, dateTime: result.dateTime});
} else if (service === baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_EVENT_NOTIFICATION) {
result = baServices.decodeEventNotifyData(buffer, offset, length);
if (!result) return debug('Received invalid EventNotify message');
this.emit('eventNotify', {address: address, eventData: result.eventData});
} else {
debug('Received unsupported unconfirmed service request');
}
}
_handlePdu(address, type, buffer, offset, length) {
let result;
// Handle different PDU types
switch (type & baEnum.PduTypes.PDU_TYPE_MASK) {
case baEnum.PduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST:
result = baAdpu.decodeUnconfirmedServiceRequest(buffer, offset);
this._processUnconfirmedServiceRequest(address, result.type, result.service, buffer, offset + result.len, length - result.len);
break;
case baEnum.PduTypes.PDU_TYPE_SIMPLE_ACK:
result = baAdpu.decodeSimpleAck(buffer, offset);
offset += result.len;
length -= result.len;
this._invokeCallback(result.invokeId, null, {result: result, buffer: buffer, offset: offset + result.len, length: length - result.len});
break;
case baEnum.PduTypes.PDU_TYPE_COMPLEX_ACK:
result = baAdpu.decodeComplexAck(buffer, offset);
if ((type & baEnum.PduTypes.SEGMENTED_MESSAGE) === 0) {
this._invokeCallback(result.invokeId, null, {result: result, buffer: buffer, offset: offset + result.len, length: length - result.len});
} else {
this._processSegment(address, result.type, result.service, result.invokeId, baEnum.MaxSegments.MAX_SEG0, baEnum.MaxAdpu.MAX_APDU50, false, result.sequencenumber, result.proposedWindowNumber, buffer, offset + result.len, length - result.len);
}
break;
case baEnum.PduTypes.PDU_TYPE_SEGMENT_ACK:
result = baAdpu.decodeSegmentAck(buffer, offset);
//m_last_segment_ack.Set(address, result.originalInvokeId, result.sequencenumber, result.actualWindowSize);
//this._processSegmentAck(address, result.type, result.originalInvokeId, result.sequencenumber, result.actualWindowSize, buffer, offset + result.len, length - result.len);
break;
case baEnum.PduTypes.PDU_TYPE_ERROR:
result = baAdpu.decodeError(buffer, offset);
this._processError(result.invokeId, buffer, offset + result.len, length - result.len);
break;
case baEnum.PduTypes.PDU_TYPE_REJECT:
case baEnum.PduTypes.PDU_TYPE_ABORT:
result = baAdpu.decodeAbort(buffer, offset);
this._processAbort(result.invokeId, result.reason);
break;
case baEnum.PduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST:
result = baAdpu.decodeConfirmedServiceRequest(buffer, offset);
if ((type & baEnum.PduTypes.SEGMENTED_MESSAGE) === 0) {
this._processConfirmedServiceRequest(address, result.type, result.service, result.maxSegments, result.maxAdpu, result.invokeId, buffer, offset + result.len, length - result.len);
} else {
this._processSegment(address, result.type, result.service, result.invokeId, result.maxSegments, result.maxAdpu, true, result.sequencenumber, result.proposedWindowNumber, buffer, offset + result.len, length - result.len);
}
break;
default:
debug('Received unknown PDU type -> Drop package');
break;
}
}
_handleNpdu(buffer, offset, msgLength, remoteAddress) {
// Check data length
if (msgLength <= 0) return debug('No NPDU data -> Drop package');
// Parse baNpdu header
const result = baNpdu.decode(buffer, offset);
if (!result) return debug('Received invalid NPDU header -> Drop package');
if ((result.funct & baEnum.NpduControls.NETWORK_LAYER_MESSAGE) === baEnum.NpduControls.NETWORK_LAYER_MESSAGE) {
return debug('Received network layer message -> Drop package');
}
offset += result.len;
msgLength -= result.len;
if (msgLength <= 0) return debug('No APDU data -> Drop package');
const apduType = baAdpu.getDecodedType(buffer, offset);
this._handlePdu(remoteAddress, apduType, buffer, offset, msgLength);
}
_receiveData(buffer, remoteAddress) {
// Check data length
if (buffer.length < baBvlc.BVLC_HEADER_LENGTH) return debug('Received invalid data -> Drop package');
// Parse BVLC header
const result = baBvlc.decode(buffer, 0);
if (!result) return debug('Received invalid BVLC header -> Drop package');
// Check BVLC function
if (result.func === baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU || result.func === baEnum.BvlcFunctions.BVLC_ORIGINAL_BROADCAST_NPDU || result.func === baEnum.BvlcFunctions.BVLC_FORWARDED_NPDU) {
this._handleNpdu(buffer, result.len, buffer.length - result.len, remoteAddress);
} else {
debug('Received unknown BVLC function -> Drop package');
}
}
_receiveError(err) {
/**
* @event bacstack.error
* @param {error} err - The IP address of the detected device.
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* client.on('error', (err) => {
* console.log('Error occurred: ', err);
* client.close();
* });
*/
this.emit('error', err);
}
/**
* The whoIs command discovers all BACNET devices in a network.
* @function bacstack.whoIs
* @param {object=} options
* @param {number=} options.lowLimit - Minimal device instance number to search for.
* @param {number=} options.highLimit - Maximal device instance number to search for.
* @param {string=} options.address - Unicast address if command should address a device directly.
* @fires bacstack.iAm
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* client.whoIs();
*/
whoIs(options) {
options = options || {};
const settings = {
lowLimit: options.lowLimit,
highLimit: options.highLimit,
address: options.address || this._transport.getBroadcastAddress()
};
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE, this._settings.address, null, DEFAULT_HOP_COUNT, baEnum.NetworkMessageTypes.NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK, 0);
baAdpu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_WHO_IS);
baServices.encodeWhoIsBroadcast(buffer, settings.lowLimit, settings.highLimit);
const npduType = (this._settings.address !== this._transport.getBroadcastAddress()) ? baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU : baEnum.BvlcFunctions.BVLC_ORIGINAL_BROADCAST_NPDU;
baBvlc.encode(buffer.buffer, npduType, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, settings.address);
}
/**
* The timeSync command sets the time of a target device.
* @function bacstack.timeSync
* @param {string} address - IP address of the target device.
* @param {date} dateTime - The date and time to set on the target device.
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* client.timeSync('192.168.1.43', new Date());
*/
timeSync(address, dateTime) {
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_TIME_SYNCHRONIZATION);
baServices.encodeTimeSync(buffer, dateTime);
const npduType = (address !== this._transport.getBroadcastAddress()) ? baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU : baEnum.BvlcFunctions.BVLC_ORIGINAL_BROADCAST_NPDU;
baBvlc.encode(buffer.buffer, npduType, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, address);
}
/**
* The timeSyncUTC command sets the UTC time of a target device.
* @function bacstack.timeSyncUTC
* @param {string} address - IP address of the target device.
* @param {date} dateTime - The date and time to set on the target device.
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* client.timeSyncUTC('192.168.1.43', new Date());
*/
timeSyncUTC(address, dateTime) {
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_UTC_TIME_SYNCHRONIZATION);
baServices.encodeTimeSync(buffer, dateTime);
const npduType = (address !== this._transport.getBroadcastAddress()) ? baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU : baEnum.BvlcFunctions.BVLC_ORIGINAL_BROADCAST_NPDU;
baBvlc.encode(buffer.buffer, npduType, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, address);
}
/**
* The readProperty command reads a single property of an object from a device.
* @function bacstack.readProperty
* @param {string} address - IP address of the target device.
* @param {object} objectId - The BACNET object ID to read.
* @param {number} objectId.type - The BACNET object type to read.
* @param {number} objectId.instance - The BACNET object instance to read.
* @param {number} propertyId - The BACNET property id in the specified object to read.
* @param {object=} options
* @param {MaxSegments=} options.maxSegments - The maximimal allowed number of segments.
* @param {MaxAdpu=} options.maxAdpu - The maximal allowed ADPU size.
* @param {number=} options.invokeId - The invoke ID of the confirmed service telegram.
* @param {number=} options.arrayIndex - The array index of the property to be read.
* @param {function} next - The callback containing an error, in case of a failure and value object in case of success.
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* client.readProperty('192.168.1.43', {type: 8, instance: 44301}, 28, (err, value) => {
* console.log('value: ', value);
* });
*/
readProperty(address, objectId, propertyId, 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(),
arrayIndex: options.arrayIndex || baAsn1.BACNET_ARRAY_ALL
};
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.NpduControls.EXPECTING_REPLY, address, null, DEFAULT_HOP_COUNT, baEnum.NetworkMessageTypes.NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK, 0);
const type = baEnum.PduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST | (settings.maxSegments !== baEnum.MaxSegments.MAX_SEG0 ? baEnum.PduTypes.SEGMENTED_RESPONSE_ACCEPTED : 0);
baAdpu.encodeConfirmedServiceRequest(buffer, type, baEnum.ConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeReadProperty(buffer, objectId.type, objectId.instance, propertyId, settings.arrayIndex);
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.decodeReadPropertyAcknowledge(data.buffer, data.offset, data.length);
if (!result) return next(new Error('INVALID_DECODING'));
next(null, result);
});
}
/**
* The writeProperty command writes a single property of an object to a device.
* @function bacstack.writeProperty
* @param {string} address - IP address of the target device.
* @param {object} objectId - The BACNET object ID to write.
* @param {number} objectId.type - The BACNET object type to write.
* @param {number} objectId.instance - The BACNET object instance to write.
* @param {number} propertyId - The BACNET property id in the specified object to write.
* @param {object[]} values - A list of values to be written to the specified property.
* @param {ApplicationTags} values.tag - The data-type of the value to be written.
* @param {number} values.value - The actual value to be written.
* @param {object=} options
* @param {MaxSegments=} options.maxSegments - The maximimal allowed number of segments.
* @param {MaxAdpu=} options.maxAdpu - The maximal allowed ADPU size.
* @param {number=} options.invokeId - The invoke ID of the confirmed service telegram.
* @param {number=} options.arrayIndex - The array index of the property to be read.
* @param {number=} options.priority - The priority of the value to be written.
* @param {function} next - The callback containing an error, in case of a failure and value object in case of success.
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* client.writeProperty('192.168.1.43', {type: 8, instance: 44301}, 28, [
* {type: bacnet.enum.ApplicationTags.BACNET_APPLICATION_TAG_REAL, value: 100}
* ], (err, value) => {
* console.log('value: ', value);
* });
*/
writeProperty(address, objectId, propertyId, values, 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(),
arrayIndex: options.arrayIndex || baAsn1.BACNET_ARRAY_ALL,
priority: options.priority
};
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE | baEnum.NpduControls.EXPECTING_REPLY, address, null, DEFAULT_HOP_COUNT, baEnum.NetworkMessageTypes.NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK, 0);
baAdpu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST, baEnum.ConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeWriteProperty(buffer, objectId.type, objectId.instance, propertyId, settings.arrayIndex, settings.priority, values);
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) => {
next(err);
});
}
/**
* The readPropertyMultiple command reads multiple properties in multiple objects from a device.
* @function bacstack.readPropertyMultiple
* @param {string} address - IP address of the target device.
* @param {object[]} requestArray - List of object and property specifications to be read.
* @param {object} requestArray.objectId - Specifies which object to read.
* @param {number} requestArray.objectId.type - The BACNET object type to read.
* @param {number} requestArray.objectId.instance - The BACNET object instance to read.
* @param {object[]} requestArray.properties - List of properties to be read.
* @param {number} requestArray.properties.id - The BACNET property id in the specified object to read. Also supports 8 for all properties.
* @param {object=} options
* @param {MaxSegments=} options.maxSegments - The maximimal allowed number of segments.
* @param {MaxAdpu=} options.maxAdpu - The maximal allowed ADPU size.
* @param {number=} options.invokeId - The invoke ID of the confirmed service telegram.
* @param {function} next - The callback containing an error, in case of a failure and value object in case of success.
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* const requestArray = [
* {objectId: {type: 8, instance: 4194303}, properties: [{id: 8}]}
* ];
* client.readPropertyMultiple('192.168.1.43', requestArray, (err, value) => {
* console.log('value: ', value);
* });
*/
readPropertyMultiple(address, propertiesArray, 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, null, DEFAULT_HOP_COUNT, baEnum.NetworkMessageTypes.NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK, 0);
const type = baEnum.PduTypes.PDU_TYPE_CONFIRMED_SERVICE_REQUEST | (baEnum.maxSegments !== baEnum.MaxSegments.MAX_SEG0 ? baEnum.PduTypes.SEGMENTED_RESPONSE_ACCEPTED : 0);
baAdpu.encodeConfirmedServiceRequest(buffer, type, baEnum.ConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeReadPropertyMultiple(buffer, propertiesArray);
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.decodeReadPropertyMultipleAcknowledge(data.buffer, data.offset, data.length);
if (!result) return next(new Error('INVALID_DECODING'));
next(null, result);
});
}
/**
* The writePropertyMultiple command writes multiple properties in multiple objects to a device.
* @function bacstack.writePropertyMultiple
* @param {string} address - IP address of the target device.
* @param {object[]} values - List of object and property specifications to be written.
* @param {object} values.objectId - Specifies which object to read.
* @param {number} values.objectId.type - The BACNET object type to read.
* @param {number} values.objectId.instance - The BACNET object instance to read.
* @param {object[]} values.values - List of properties to be written.
* @param {object} values.values.property - Property specifications to be written.
* @param {number} values.values.property.id - The BACNET property id in the specified object to write.
* @param {number} values.values.property.index - The array index of the property to be written.
* @param {object[]} values.values.value - A list of values to be written to the specified property.
* @param {ApplicationTags} values.values.value.tag - The data-type of the value to be written.
* @param {object} values.values.value.value - The actual value to be written.
* @param {number} values.values.priority - The priority to be used for writing to the property.
* @param {object=} options
* @param {MaxSegments=} options.maxSegments - The maximimal allowed number of segments.
* @param {MaxAdpu=} options.maxAdpu - The maximal allowed ADPU size.
* @param {number=} options.invokeId - The invoke ID of the confirmed service telegram.
* @param {function} next - The callback containing an error, in case of a failure and value object in case of success.
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* const values = [
* {objectId: {type: 8, instance: 44301}, values: [
* {property: {id: 28, index: 12}, value: [{type: bacnet.enum.ApplicationTags.BACNET_APPLICATION_TAG_BOOLEAN, value: true}], priority: 8}
* ]}
* ];
* client.writePropertyMultiple('192.168.1.43', values, (err, value) => {
* console.log('value: ', value);
* });
*/
writePropertyMultiple(address, values, 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_WRITE_PROP_MULTIPLE, settings.maxSegments, settings.maxAdpu, settings.invokeId);
baServices.encodeWriteObjectMultiple(buffer, values);
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) => {
next(err);
});
}
/**
* The deviceCommunicationControl command enables or disables network communication of the target device.
* @function bacstack.deviceCommunicationControl
* @param {string} address - IP address of the target device.
* @param {number} timeDuration - The time to hold the network communication state in seconds. 0 for infinite.
* @param {EnableDisable} enableDisable - The network communication state to set.
* @param {object=} options
* @param {MaxSegments=} options.maxSegments - The maximimal allowed number of segments.
* @param {MaxAdpu=} options.maxAdpu - The maximal allowed ADPU size.
* @param {number=} options.invokeId - The invoke ID of the confirmed service telegram.
* @param {string=} options.password - The optional password used to set the network communication state.
* @param {function} next - The callback containing an error, in case of a failure and value object in case of success.
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* client.deviceCommunicationControl('192.168.1.43', 0, bacnet.enum.EnableDisable.DISABLE, (err, value) => {
* console.log('value: ', value);
* });
*/
deviceCommunicationControl(address, timeDuration, enableDisable, 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(),
password: options.password
};
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_DEVICE_COMMUNICATION_CONTROL, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeDeviceCommunicationControl(buffer, timeDuration, enableDisable, settings.password);
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) => {
next(err);
});
}
/**
* The reinitializeDevice command initiates a restart of the target device.
* @function bacstack.reinitializeDevice
* @param {string} address - IP address of the target device.
* @param {ReinitializedStates} state - The type of restart to be initiated.
* @param {object=} options
* @param {MaxSegments=} options.maxSegments - The maximimal allowed number of segments.
* @param {MaxAdpu=} options.maxAdpu - The maximal allowed ADPU size.
* @param {number=} options.invokeId - The invoke ID of the confirmed service telegram.
* @param {string=} options.password - The optional password used to restart the device.
* @param {function} next - The callback containing an error, in case of a failure and value object in case of success.
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* client.reinitializeDevice('192.168.1.43', bacnet.enum.ReinitializedStates.BACNET_REINIT_COLDSTART, (err, value) => {
* console.log('value: ', value);
* });
*/
reinitializeDevice(address, state, 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(),
password: options.password
};
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_REINITIALIZE_DEVICE, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeReinitializeDevice(buffer, state, settings.password);
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) => {
next(err);
});
}
writeFile(address, objectId, position, fileBuffer, 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_ATOMIC_WRITE_FILE, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeAtomicWriteFile(buffer, false, objectId, position, fileBuffer);
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.decodeAtomicWriteFileAcknowledge(data.buffer, data.offset, data.length);
if (!result) return next(new Error('INVALID_DECODING'));
next(null, result);
});
}
readFile(address, objectId, position, count, 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_ATOMIC_READ_FILE, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeAtomicReadFile(buffer, true, objectId, position, count);
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.decodeAtomicReadFileAcknowledge(data.buffer, data.offset, data.length);
if (!result) return next(new Error('INVALID_DECODING'));
next(null, result);
});
}
readRange(address, objectId, idxBegin, quantity, 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_READ_RANGE, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeReadRange(buffer, objectId, baEnum.PropertyIds.PROP_LOG_BUFFER, baAsn1.BACNET_ARRAY_ALL, baEnum.ReadRangeRequestTypes.RR_BY_POSITION, idxBegin, new Date(), quantity);
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.decodeReadRangeAcknowledge(data.buffer, data.offset, data.length);
if (!result) return next(new Error('INVALID_DECODING'));
next(null, result);
});
}
subscribeCOV(address, objectId, subscribeId, cancel, issueConfirmedNotifications, lifetime, 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_SUBSCRIBE_COV, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeSubscribeCOV(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, lifetime);
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();
});
}
subscribeProperty(address, objectId, monitoredProperty, subscribeId, cancel, issueConfirmedNotifications, 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_SUBSCRIBE_COV_PROPERTY, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeSubscribeProperty(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, 0, monitoredProperty, false, 0x0f);
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();
});
}
createObject(address, objectId, values, 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_CREATE_OBJECT, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeCreateObject(buffer, objectId, values);
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();
});
}
deleteObject(address, objectId, 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_DELETE_OBJECT, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeDeleteObject(buffer, objectId);
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();
});
}
removeListElement(address, objectId, reference, values, 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_REMOVE_LIST_ELEMENT, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeAddListElement(buffer, objectId, reference.id, reference.index, values);
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();
});
}
addListElement(address, objectId, reference, values, 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_ADD_LIST_ELEMENT, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeAddListElement(buffer, objectId, reference.id, reference.index, values);
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();
});
}
getAlarmSummary(address, 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_ALARM_SUMMARY, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
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.decodeAlarmSummary(data.buffer, data.offset, data.length);
if (!result) return next(new Error('INVALID_DECODING'));
next(null, result);
});
}
getEventInformation(address, objectId, 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_EVENT_INFORMATION, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baAsn1.encodeContextObjectId(buffer, 0, objectId.type, objectId.instance);
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.decodeEventInformation(data.buffer, data.offset, data.length);
if (!result) return next(new Error('INVALID_DECODING'));
next(null, result);
});
}
acknowledgeAlarm(address, objectId, eventState, ackText, evTimeStamp, ackTimeStamp, 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_ACKNOWLEDGE_ALARM, settings.maxSegments, settings.maxAdpu, settings.invokeId, 0, 0);
baServices.encodeAlarmAcknowledge(buffer, 57, objectId, eventState, ackText, evTimeStamp, ackTimeStamp);
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();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE, receiver);
baAdpu.encodeComplexAck(buffer, baEnum.PduTypes.PDU_TYPE_COMPLEX_ACK, baEnum.ConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY, invokeId);
baServices.encodeReadPropertyAcknowledge(buffer, objectId, property.id, property.index, value);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, receiver);
}
readPropertyMultipleResponse(receiver, invokeId, values) {
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE, receiver);
baAdpu.encodeComplexAck(buffer, baEnum.PduTypes.PDU_TYPE_COMPLEX_ACK, baEnum.ConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE, invokeId);
baServices.encodeReadPropertyMultipleAcknowledge(buffer, values);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, receiver);
}
iAmResponse(deviceId, segmentation, vendorId) {
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE, this._transport.getBroadcastAddress());
baAdpu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_I_AM);
baServices.encodeIamBroadcast(buffer, deviceId, this._transport.getMaxPayload(), segmentation, vendorId);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_BROADCAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, this._transport.getBroadcastAddress());
}
iHaveResponse(deviceId, objectId, objectName) {
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE, this._transport.getBroadcastAddress());
baAdpu.EecodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST, baEnum.UnconfirmedServices.SERVICE_UNCONFIRMED_I_HAVE);
baServices.EncodeIhaveBroadcast(buffer, deviceId, objectId, objectName);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_BROADCAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, this._transport.getBroadcastAddress());
}
simpleAckResponse(receiver, service, invokeId) {
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE, receiver);
baAdpu.encodeSimpleAck(buffer, baEnum.PduTypes.PDU_TYPE_SIMPLE_ACK, service, invokeId);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, receiver);
}
errorResponse(receiver, service, invokeId, errorClass, errorCode) {
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControls.PRIORITY_NORMAL_MESSAGE, receiver);
baAdpu.encodeError(buffer, baEnum.PduTypes.PDU_TYPE_ERROR, service, invokeId);
baServices.encodeError(buffer, errorClass, errorCode);
baBvlc.encode(buffer.buffer, baEnum.BvlcFunctions.BVLC_ORIGINAL_UNICAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, receiver);
}
/**
* Unloads the current BACstack instance and closes the underlying UDP socket.
* @function bacstack.close
* @example
* const bacnet = require('bacstack');
* const client = new bacnet();
*
* client.close();
*/
close() {
this._transport.close();
}
}
module.exports = Client;