fh1ch/node-bacstack

View on GitHub
lib/client.js

Summary

Maintainability
F
1 wk
Test Coverage
'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 baApdu = require('./apdu');
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.apduTimeout=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
 *   apduTimeout: 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',
      apduTimeout: options.apduTimeout || 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.apduTimeout);
    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.error.decode(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.NpduControlPriority.NORMAL_MESSAGE, receiver, null, DEFAULT_HOP_COUNT, baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK, 0);
    baApdu.encodeSegmentAck(buffer, baEnum.PduTypes.SEGMENT_ACK | (negative ? baEnum.PduSegAckBits.NEGATIVE_ACK : 0) | (server ? baEnum.PduSegAckBits.SERVER : 0), originalInvokeId, sequencenumber, actualWindowSize);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, receiver);
  }

  _performDefaultSegmentHandling(sender, adr, type, service, invokeId, maxSegments, maxApdu, sequencenumber, first, moreFollows, buffer, offset, length) {
    if (first) {
      this._segmentStore = [];
      type &= ~baEnum.PduConReqBits.SEGMENTED_MESSAGE;
      let apduHeaderLen = 3;
      if ((type & baEnum.PDU_TYPE_MASK) === baEnum.PduTypes.CONFIRMED_REQUEST) {
        apduHeaderLen = 4;
      }
      const apdubuffer = this._getBuffer();
      apdubuffer.offset = 0;
      buffer.copy(apdubuffer.buffer, apduHeaderLen, offset, offset + length);
      if ((type & baEnum.PDU_TYPE_MASK) === baEnum.PduTypes.CONFIRMED_REQUEST) {
        baApdu.encodeConfirmedServiceRequest(apdubuffer, type, service, maxSegments, maxApdu, invokeId, 0, 0);
      } else {
        baApdu.encodeComplexAck(apdubuffer, type, service, invokeId, 0, 0);
      }
      this._segmentStore.push(apdubuffer.buffer.slice(0, length + apduHeaderLen));
    } else {
      this._segmentStore.push(buffer.slice(offset, offset + length));
    }
    if (!moreFollows) {
      const apduBuffer = Buffer.concat(this._segmentStore);
      this._segmentStore = [];
      type &= ~baEnum.PduConReqBits.SEGMENTED_MESSAGE;
      this._handlePdu(adr, type, apduBuffer, 0, apduBuffer.length);
    }
  }

  _processSegment(receiver, type, service, invokeId, maxSegments, maxApdu, 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.PduConReqBits.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, maxApdu, sequencenumber, first, moreFollows, buffer, offset, length);
  }

  _processConfirmedServiceRequest(address, type, service, maxSegments, maxApdu, invokeId, buffer, offset, length) {
    let result;
    debug('Handle this._processConfirmedServiceRequest');
    if (service === baEnum.ConfirmedServiceChoice.READ_PROPERTY) {
      result = baServices.readProperty.decode(buffer, offset, length);
      if (!result) return debug('Received invalid readProperty message');
      this.emit('readProperty', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.WRITE_PROPERTY) {
      result = baServices.writeProperty.decode(buffer, offset, length);
      if (!result) return debug('Received invalid writeProperty message');
      this.emit('writeProperty', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.READ_PROPERTY_MULTIPLE) {
      result = baServices.readPropertyMultiple.decode(buffer, offset, length);
      if (!result) return debug('Received invalid readPropertyMultiple message');
      this.emit('readPropertyMultiple', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.WRITE_PROPERTY_MULTIPLE) {
      result = baServices.writePropertyMultiple.decode(buffer, offset, length);
      if (!result) return debug('Received invalid writePropertyMultiple message');
      this.emit('writePropertyMultiple', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.CONFIRMED_COV_NOTIFICATION) {
      result = baServices.covNotify.decode(buffer, offset, length);
      if (!result) return debug('Received invalid covNotify message');
      this.emit('covNotify', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.ATOMIC_WRITE_FILE) {
      result = baServices.atomicWriteFile.decode(buffer, offset, length);
      if (!result) return debug('Received invalid atomicWriteFile message');
      this.emit('atomicWriteFile', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.ATOMIC_READ_FILE) {
      result = baServices.atomicReadFile.decode(buffer, offset, length);
      if (!result) return debug('Received invalid atomicReadFile message');
      this.emit('atomicReadFile', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.SUBSCRIBE_COV) {
      result = baServices.subscribeCov.decode(buffer, offset, length);
      if (!result) return debug('Received invalid subscribeCOV message');
      this.emit('subscribeCOV', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.SUBSCRIBE_COV_PROPERTY) {
      result = baServices.subscribeProperty.decode(buffer, offset, length);
      if (!result) return debug('Received invalid subscribeProperty message');
      this.emit('subscribeProperty', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.DEVICE_COMMUNICATION_CONTROL) {
      result = baServices.deviceCommunicationControl.decode(buffer, offset, length);
      if (!result) return debug('Received invalid deviceCommunicationControl message');
      this.emit('deviceCommunicationControl', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.REINITIALIZE_DEVICE) {
      result = baServices.reinitializeDevice.decode(buffer, offset, length);
      if (!result) return debug('Received invalid reinitializeDevice message');
      this.emit('reinitializeDevice', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.CONFIRMED_EVENT_NOTIFICATION) {
      result = baServices.eventNotifyData.decode(buffer, offset, length);
      if (!result) return debug('Received invalid eventNotifyData message');
      this.emit('eventNotifyData', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.READ_RANGE) {
      result = baServices.readRange.decode(buffer, offset, length);
      if (!result) return debug('Received invalid readRange message');
      this.emit('readRange', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.CREATE_OBJECT) {
      result = baServices.createObject.decode(buffer, offset, length);
      if (!result) return debug('Received invalid createObject message');
      this.emit('createObject', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.DELETE_OBJECT) {
      result = baServices.deleteObject.decode(buffer, offset, length);
      if (!result) return debug('Received invalid deleteObject message');
      this.emit('deleteObject', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.ACKNOWLEDGE_ALARM) {
      result = baServices.alarmAcknowledge.decode(buffer, offset, length);
      if (!result) return debug('Received invalid alarmAcknowledge message');
      this.emit('alarmAcknowledge', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.GET_ALARM_SUMMARY) {
      this.emit('getAlarmSummary', {address: address, invokeId: invokeId});
    } else if (service === baEnum.ConfirmedServiceChoice.GET_ENROLLMENT_SUMMARY) {
      result = baServices.getEnrollmentSummary.decode(buffer, offset, length);
      if (!result) return debug('Received invalid getEntrollmentSummary message');
      this.emit('getEntrollmentSummary', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.GET_EVENT_INFORMATION) {
      result = baServices.getEventInformation.decode(buffer, offset, length);
      if (!result) return debug('Received invalid getEventInformation message');
      this.emit('getEventInformation', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.LIFE_SAFETY_OPERATION) {
      result = baServices.lifeSafetyOperation.decode(buffer, offset, length);
      if (!result) return debug('Received invalid lifeSafetyOperation message');
      this.emit('lifeSafetyOperation', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.ADD_LIST_ELEMENT) {
      result = baServices.addListElement.decode(buffer, offset, length);
      if (!result) return debug('Received invalid addListElement message');
      this.emit('addListElement', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.REMOVE_LIST_ELEMENT) {
      result = baServices.addListElement.decode(buffer, offset, length);
      if (!result) return debug('Received invalid removeListElement message');
      this.emit('removeListElement', {address: address, invokeId: invokeId, request: result});
    } else if (service === baEnum.ConfirmedServiceChoice.CONFIRMED_PRIVATE_TRANSFER) {
      result = baServices.privateTransfer.decode(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');
    }
  }

  _processUnconfirmedServiceRequest(address, type, service, buffer, offset, length) {
    let result;
    debug('Handle this._processUnconfirmedServiceRequest');
    if (service === baEnum.UnconfirmedServiceChoice.I_AM) {
      result = baServices.iAmBroadcast.decode(buffer, offset);
      if (!result) return debug('Received invalid iAm message');

      /**
       * The iAm event represents the response to a whoIs request to detect all devices in a BACNET network.
       * @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.maxApdu - The max APDU 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, ' - maxApdu: ', device.maxApdu, ' - 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.UnconfirmedServiceChoice.WHO_IS) {
      result = baServices.whoIs.decode(buffer, offset, length);
      if (!result) return debug('Received invalid WhoIs message');

      /**
       * The whoIs event represents the request for an IAm reponse to detect all devices in a BACNET network.
       * @event bacstack.whoIs
       * @param {object} request - An object representing the received request.
       * @param {string} request.address - The IP address of the device sending the request.
       * @param {number=} request.lowLimit - The lower limit of the BACNET device-id.
       * @param {number=} request.highLimit - The higher limit of the BACNET device-id.
       * @example
       * const bacnet = require('bacstack');
       * const client = new bacnet();
       *
       * client.on('whoIs', (request) => {
       *   console.log('address: ', device.address, ' - lowLimit: ', device.lowLimit, ' - highLimit: ', device.highLimit);
       * });
       */
      this.emit('whoIs', {address: address, lowLimit: result.lowLimit, highLimit: result.highLimit});
    } else if (service === baEnum.UnconfirmedServiceChoice.WHO_HAS) {
      result = baServices.whoHas.decode(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.UnconfirmedServiceChoice.UNCONFIRMED_COV_NOTIFICATION) {
      result = baServices.covNotify.decode(buffer, offset, length);
      if (!result) return debug('Received invalid covNotifyUnconfirmed message');
      this.emit('covNotifyUnconfirmed', {address: address, request: result});
    } else if (service === baEnum.UnconfirmedServiceChoice.TIME_SYNCHRONIZATION) {
      result = baServices.timeSync.decode(buffer, offset, length);
      if (!result) return debug('Received invalid TimeSync message');

      /**
       * The timeSync event represents the request to synchronize the local time to the received time.
       * @event bacstack.timeSync
       * @param {object} request - An object representing the received request.
       * @param {string} request.address - The IP address of the device sending the request.
       * @param {date} request.dateTime - The time to be synchronized to.
       * @example
       * const bacnet = require('bacstack');
       * const client = new bacnet();
       *
       * client.on('timeSync', (request) => {
       *   console.log('address: ', device.address, ' - dateTime: ', device.dateTime);
       * });
       */
      this.emit('timeSync', {address: address, dateTime: result.dateTime});
    } else if (service === baEnum.UnconfirmedServiceChoice.UTC_TIME_SYNCHRONIZATION) {
      result = baServices.timeSync.decode(buffer, offset, length);
      if (!result) return debug('Received invalid TimeSyncUTC message');

      /**
       * The timeSyncUTC event represents the request to synchronize the local time to the received UTC time.
       * @event bacstack.timeSyncUTC
       * @param {object} request - An object representing the received request.
       * @param {string} request.address - The IP address of the device sending the request.
       * @param {date} request.dateTime - The time to be synchronized to.
       * @example
       * const bacnet = require('bacstack');
       * const client = new bacnet();
       *
       * client.on('timeSyncUTC', (request) => {
       *   console.log('address: ', device.address, ' - dateTime: ', device.dateTime);
       * });
       */
      this.emit('timeSyncUTC', {address: address, dateTime: result.dateTime});
    } else if (service === baEnum.UnconfirmedServiceChoice.UNCONFIRMED_EVENT_NOTIFICATION) {
      result = baServices.eventNotifyData.decode(buffer, offset, length);
      if (!result) return debug('Received invalid EventNotify message');
      this.emit('eventNotify', {address: address, eventData: result.eventData});
    } else if (service === baEnum.UnconfirmedServiceChoice.I_HAVE) {
      result = baServices.iHaveBroadcast.decode(buffer, offset, length);
      if (!result) return debug('Received invalid ihaveBroadcast message');
      this.emit('ihaveBroadcast', {address: address, eventData: result.eventData});
    } else if (service === baEnum.UnconfirmedServiceChoice.UNCONFIRMED_PRIVATE_TRANSFER) {
      result = baServices.privateTransfer.decode(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');
    }
  }

  _handlePdu(address, type, buffer, offset, length) {
    let result;
    // Handle different PDU types
    switch (type & baEnum.PDU_TYPE_MASK) {
      case baEnum.PduTypes.UNCONFIRMED_REQUEST:
        result = baApdu.decodeUnconfirmedServiceRequest(buffer, offset);
        this._processUnconfirmedServiceRequest(address, result.type, result.service, buffer, offset + result.len, length - result.len);
        break;
      case baEnum.PduTypes.SIMPLE_ACK:
        result = baApdu.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.COMPLEX_ACK:
        result = baApdu.decodeComplexAck(buffer, offset);
        if ((type & baEnum.PduConReqBits.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.MaxSegmentsAccepted.SEGMENTS_0, baEnum.MaxApduLengthAccepted.OCTETS_50, false, result.sequencenumber, result.proposedWindowNumber, buffer, offset + result.len, length - result.len);
        }
        break;
      case baEnum.PduTypes.SEGMENT_ACK:
        result = baApdu.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.ERROR:
        result = baApdu.decodeError(buffer, offset);
        this._processError(result.invokeId, buffer, offset + result.len, length - result.len);
        break;
      case baEnum.PduTypes.REJECT:
      case baEnum.PduTypes.ABORT:
        result = baApdu.decodeAbort(buffer, offset);
        this._processAbort(result.invokeId, result.reason);
        break;
      case baEnum.PduTypes.CONFIRMED_REQUEST:
        result = baApdu.decodeConfirmedServiceRequest(buffer, offset);
        if ((type & baEnum.PduConReqBits.SEGMENTED_MESSAGE) === 0) {
          this._processConfirmedServiceRequest(address, result.type, result.service, result.maxSegments, result.maxApdu, result.invokeId, buffer, offset + result.len, length - result.len);
        } else {
          this._processSegment(address, result.type, result.service, result.invokeId, result.maxSegments, result.maxApdu, 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.NpduControlBits.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 = baApdu.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.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU || result.func === baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU || result.func === baEnum.BvlcResultPurpose.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 error object thrown by the underlying transport layer.
     * @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.NpduControlPriority.NORMAL_MESSAGE, this._settings.address, null, DEFAULT_HOP_COUNT, baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK, 0);
    baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.WHO_IS);
    baServices.whoIs.encode(buffer, settings.lowLimit, settings.highLimit);
    const npduType = (this._settings.address !== this._transport.getBroadcastAddress()) ? baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU : baEnum.BvlcResultPurpose.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.NpduControlPriority.NORMAL_MESSAGE, address);
    baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.TIME_SYNCHRONIZATION);
    baServices.timeSync.encode(buffer, dateTime);
    const npduType = (address !== this._transport.getBroadcastAddress()) ? baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU : baEnum.BvlcResultPurpose.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.NpduControlPriority.NORMAL_MESSAGE, address);
    baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.UTC_TIME_SYNCHRONIZATION);
    baServices.timeSync.encode(buffer, dateTime);
    const npduType = (address !== this._transport.getBroadcastAddress()) ? baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU : baEnum.BvlcResultPurpose.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 {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId(),
      arrayIndex: options.arrayIndex || baEnum.ASN1_ARRAY_ALL
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address, null, DEFAULT_HOP_COUNT, baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK, 0);
    const type = baEnum.PduTypes.CONFIRMED_REQUEST | (settings.maxSegments !== baEnum.MaxSegmentsAccepted.SEGMENTS_0 ? baEnum.PduConReqBits.SEGMENTED_RESPONSE_ACCEPTED : 0);
    baApdu.encodeConfirmedServiceRequest(buffer, type, baEnum.ConfirmedServiceChoice.READ_PROPERTY, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.readProperty.encode(buffer, objectId.type, objectId.instance, propertyId, settings.arrayIndex);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.readProperty.decodeAcknowledge(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 {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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.
   * @example
   * const bacnet = require('bacstack');
   * const client = new bacnet();
   *
   * client.writeProperty('192.168.1.43', {type: 8, instance: 44301}, 28, [
   *   {type: bacnet.enum.ApplicationTags.REAL, value: 100}
   * ], (err) => {
   *   console.log('error: ', err);
   * });
   */
  writeProperty(address, objectId, propertyId, values, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId(),
      arrayIndex: options.arrayIndex || baEnum.ASN1_ARRAY_ALL,
      priority: options.priority
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address, null, DEFAULT_HOP_COUNT, baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK, 0);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.WRITE_PROPERTY, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.writeProperty.encode(buffer, objectId.type, objectId.instance, propertyId, settings.arrayIndex, settings.priority, values);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => 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[]} propertiesArray - List of object and property specifications to be read.
   * @param {object} propertiesArray.objectId - Specifies which object to read.
   * @param {number} propertiesArray.objectId.type - The BACNET object type to read.
   * @param {number} propertiesArray.objectId.instance - The BACNET object instance to read.
   * @param {object[]} propertiesArray.properties - List of properties to be read.
   * @param {number} propertiesArray.properties.id - The BACNET property id in the specified object to read. Also supports 8 for all properties.
   * @param {object=} options
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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 propertiesArray = [
   *   {objectId: {type: 8, instance: 4194303}, properties: [{id: 8}]}
   * ];
   * client.readPropertyMultiple('192.168.1.43', propertiesArray, (err, value) => {
   *   console.log('value: ', value);
   * });
   */
  readPropertyMultiple(address, propertiesArray, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address, null, DEFAULT_HOP_COUNT, baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK, 0);
    const type = baEnum.PduTypes.CONFIRMED_REQUEST | (settings.maxSegments !== baEnum.MaxSegmentsAccepted.SEGMENTS_0 ? baEnum.PduConReqBits.SEGMENTED_RESPONSE_ACCEPTED : 0);
    baApdu.encodeConfirmedServiceRequest(buffer, type, baEnum.ConfirmedServiceChoice.READ_PROPERTY_MULTIPLE, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.readPropertyMultiple.encode(buffer, propertiesArray);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.readPropertyMultiple.decodeAcknowledge(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 {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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.
   * @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.BOOLEAN, value: true}], priority: 8}
   *   ]}
   * ];
   * client.writePropertyMultiple('192.168.1.43', values, (err) => {
   *   console.log('error: ', err);
   * });
   */
  writePropertyMultiple(address, values, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.WRITE_PROPERTY_MULTIPLE, settings.maxSegments, settings.maxApdu, settings.invokeId);
    baServices.writePropertyMultiple.encodeObject(buffer, values);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => 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 {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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.
   * @example
   * const bacnet = require('bacstack');
   * const client = new bacnet();
   *
   * client.deviceCommunicationControl('192.168.1.43', 0, bacnet.enum.EnableDisable.DISABLE, (err) => {
   *   console.log('error: ', err);
   * });
   */
  deviceCommunicationControl(address, timeDuration, enableDisable, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId(),
      password: options.password
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.DEVICE_COMMUNICATION_CONTROL, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.deviceCommunicationControl.encode(buffer, timeDuration, enableDisable, settings.password);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => 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 {ReinitializedState} state - The type of restart to be initiated.
   * @param {object=} options
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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.
   * @example
   * const bacnet = require('bacstack');
   * const client = new bacnet();
   *
   * client.reinitializeDevice('192.168.1.43', bacnet.enum.ReinitializedState.COLDSTART, (err) => {
   *   console.log('error: ', err);
   * });
   */
  reinitializeDevice(address, state, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId(),
      password: options.password
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.REINITIALIZE_DEVICE, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.reinitializeDevice.encode(buffer, state, settings.password);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => next(err));
  }

  /**
   * The writeFile command writes a file buffer to a specific position of a file object.
   * @function bacstack.writeFile
   * @param {string} address - IP address of the target device.
   * @param {object} objectId - The BACNET object ID representing the file object.
   * @param {number} objectId.type - The BACNET object type representing the file object.
   * @param {number} objectId.instance - The BACNET object instance representing the file object.
   * @param {number} position - The position in the file to write at.
   * @param {Array.<number[]>} fileBuffer - The content to be written to the file.
   * @param {object=} options
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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();
   *
   * client.writeFile('192.168.1.43', {type: 8, instance: 44301}, 0, [[5, 6, 7, 8], [5, 6, 7, 8]], (err, value) => {
   *   console.log('value: ', value);
   * });
   */
  writeFile(address, objectId, position, fileBuffer, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.ATOMIC_WRITE_FILE, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.atomicWriteFile.encode(buffer, false, objectId, position, fileBuffer);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.atomicWriteFile.decodeAcknowledge(data.buffer, data.offset, data.length);
      if (!result) return next(new Error('INVALID_DECODING'));
      next(null, result);
    });
  }

  /**
   * The readFile command reads a number of bytes at a specific position of a file object.
   * @function bacstack.readFile
   * @param {string} address - IP address of the target device.
   * @param {object} objectId - The BACNET object ID representing the file object.
   * @param {number} objectId.type - The BACNET object type representing the file object.
   * @param {number} objectId.instance - The BACNET object instance representing the file object.
   * @param {number} position - The position in the file to read at.
   * @param {number} count - The number of octets to read.
   * @param {object=} options
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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();
   *
   * client.readFile('192.168.1.43', {type: 8, instance: 44301}, 0, 100, (err, value) => {
   *   console.log('value: ', value);
   * });
   */
  readFile(address, objectId, position, count, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.ATOMIC_READ_FILE, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.atomicReadFile.encode(buffer, true, objectId, position, count);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.atomicReadFile.decodeAcknowledge(data.buffer, data.offset, data.length);
      if (!result) return next(new Error('INVALID_DECODING'));
      next(null, result);
    });
  }

  /**
   * The readRange command reads a number if list items of an array or list object.
   * @function bacstack.readRange
   * @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} idxBegin - The index of the first/last item to read.
   * @param {number} quantity - The number of records to read.
   * @param {object=} options
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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();
   *
   * client.readRange('192.168.1.43', {type: 8, instance: 44301}, 0, 200, (err, value) => {
   *   console.log('value: ', value);
   * });
   */
  readRange(address, objectId, idxBegin, quantity, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.READ_RANGE, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.readRange.encode(buffer, objectId, baEnum.PropertyIdentifier.LOG_BUFFER, baEnum.ASN1_ARRAY_ALL, baEnum.ReadRangeType.BY_POSITION, idxBegin, new Date(), quantity);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.readRange.decodeAcknowledge(data.buffer, data.offset, data.length);
      if (!result) return next(new Error('INVALID_DECODING'));
      next(null, result);
    });
  }

  /**
   * The subscribeCOV command subscribes to an object for "Change of Value" notifications.
   * @function bacstack.subscribeCOV
   * @param {string} address - IP address of the target device.
   * @param {object} objectId - The BACNET object ID to subscribe for.
   * @param {number} objectId.type - The BACNET object type to subscribe for.
   * @param {number} objectId.instance - The BACNET object instance to subscribe for.
   * @param {number} subscribeId - A unique identifier to map the subscription.
   * @param {boolean} cancel - Cancel an existing subscription instead of creating a new one.
   * @param {boolean} issueConfirmedNotifications - Identifies if unconfirmed/confirmed notifications shall be returned.
   * @param {number} lifetime - Number of seconds for the subscription to stay active, 0 for infinite.
   * @param {object=} options
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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.
   * @example
   * const bacnet = require('bacstack');
   * const client = new bacnet();
   *
   * client.subscribeCOV('192.168.1.43', {type: 8, instance: 44301}, 7, false, false, 0, (err) => {
   *   console.log('error: ', err);
   * });
   */
  subscribeCOV(address, objectId, subscribeId, cancel, issueConfirmedNotifications, lifetime, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.SUBSCRIBE_COV, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.subscribeCov.encode(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, lifetime);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => next(err));
  }

  /**
   * The subscribeProperty command subscribes to a specific property of an object for "Change of Value" notifications.
   * @function bacstack.subscribeProperty
   * @param {string} address - IP address of the target device.
   * @param {object} objectId - The BACNET object ID to subscribe for.
   * @param {number} objectId.type - The BACNET object type to subscribe for.
   * @param {number} objectId.instance - The BACNET object instance to subscribe for.
   * @param {object} monitoredProperty
   * @param {object} monitoredProperty.id - The property ID to subscribe for.
   * @param {object} monitoredProperty.index - The property index to subscribe for.
   * @param {number} subscribeId - A unique identifier to map the subscription.
   * @param {boolean} cancel - Cancel an existing subscription instead of creating a new one.
   * @param {boolean} issueConfirmedNotifications - Identifies if unconfirmed/confirmed notifications shall be returned.
   * @param {object=} options
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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.
   * @example
   * const bacnet = require('bacstack');
   * const client = new bacnet();
   *
   * client.subscribeProperty('192.168.1.43', {type: 8, instance: 44301}, {id: 80, index: 0}, 8, false, false, (err) => {
   *   console.log('error: ', err);
   * });
   */
  subscribeProperty(address, objectId, monitoredProperty, subscribeId, cancel, issueConfirmedNotifications, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.SUBSCRIBE_COV_PROPERTY, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.subscribeProperty.encode(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, 0, monitoredProperty, false, 0x0f);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => next(err));
  }

  createObject(address, objectId, values, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.CREATE_OBJECT, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.createObject.encode(buffer, objectId, values);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => next(err));
  }

  /**
   * The deleteObject command removes an object instance from a target device.
   * @function bacstack.deleteObject
   * @param {string} address - IP address of the target device.
   * @param {object} objectId - The BACNET object ID to delete.
   * @param {number} objectId.type - The BACNET object type to delete.
   * @param {number} objectId.instance - The BACNET object instance to delete.
   * @param {object=} options
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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.
   * @example
   * const bacnet = require('bacstack');
   * const client = new bacnet();
   *
   * client.deleteObject('192.168.1.43', {type: 8, instance: 44301}, (err) => {
   *   console.log('error: ', err);
   * });
   */
  deleteObject(address, objectId, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.DELETE_OBJECT, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.deleteObject.encode(buffer, objectId);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => next(err));
  }

  removeListElement(address, objectId, reference, values, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.REMOVE_LIST_ELEMENT, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.addListElement.encode(buffer, objectId, reference.id, reference.index, values);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => next(err));
  }

  addListElement(address, objectId, reference, values, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.ADD_LIST_ELEMENT, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.addListElement.encode(buffer, objectId, reference.id, reference.index, values);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => next(err));
  }

  /**
   * DEPRECATED The getAlarmSummary command returns a list of all active alarms on the target device.
   * @function bacstack.getAlarmSummary
   * @param {string} address - IP address of the target device.
   * @param {object=} options
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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();
   *
   * client.getAlarmSummary('192.168.1.43', (err, value) => {
   *   console.log('value: ', value);
   * });
   */
  getAlarmSummary(address, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.GET_ALARM_SUMMARY, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.alarmSummary.decode(data.buffer, data.offset, data.length);
      if (!result) return next(new Error('INVALID_DECODING'));
      next(null, result);
    });
  }

  /**
   * The getEventInformation command returns a list of all active event states on the target device.
   * @function bacstack.getEventInformation
   * @param {string} address - IP address of the target device.
   * @param {object=} objectId - The optional BACNET object ID to continue preceding calls.
   * @param {number=} objectId.type - The optional BACNET object type to continue preceding calls.
   * @param {number=} objectId.instance - The optional BACNET object instance to continue preceding calls.
   * @param {object=} options
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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();
   *
   * client.getEventInformation('192.168.1.43', {}, (err, value) => {
   *   console.log('value: ', value);
   * });
   */
  getEventInformation(address, objectId, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.GET_EVENT_INFORMATION, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baAsn1.encodeContextObjectId(buffer, 0, objectId.type, objectId.instance);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.eventInformation.decode(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.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.ACKNOWLEDGE_ALARM, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.alarmAcknowledge.encode(buffer, 57, objectId, eventState, ackText, evTimeStamp, ackTimeStamp);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => next(err));
  }

  /**
   * The confirmedPrivateTransfer command invokes a confirmed proprietary/non-standard service.
   * @function bacstack.confirmedPrivateTransfer
   * @param {string} address - IP address of the target device.
   * @param {number} vendorId - The unique vendor identification code.
   * @param {number} serviceNumber - The unique service identifier.
   * @param {number[]} [data] - Optional additional payload data.
   * @param {object=} options
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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.
   * @example
   * const bacnet = require('bacstack');
   * const client = new bacnet();
   *
   * client.confirmedPrivateTransfer('192.168.1.43', 0, 7, [0x00, 0xaa, 0xfa, 0xb1, 0x00], (err) => {
   *   console.log('error: ', err);
   * });
   */
  confirmedPrivateTransfer(address, vendorId, serviceNumber, data, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.CONFIRMED_PRIVATE_TRANSFER, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.privateTransfer.encode(buffer, vendorId, serviceNumber, data);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => next(err));
  }

  /**
   * The unconfirmedPrivateTransfer command invokes an unconfirmed proprietary/non-standard service.
   * @function bacstack.unconfirmedPrivateTransfer
   * @param {string} address - IP address of the target device.
   * @param {number} vendorId - The unique vendor identification code.
   * @param {number} serviceNumber - The unique service identifier.
   * @param {number[]} [data] - Optional additional payload data.
   * @example
   * const bacnet = require('bacstack');
   * const client = new bacnet();
   *
   * client.unconfirmedPrivateTransfer('192.168.1.43', 0, 7, [0x00, 0xaa, 0xfa, 0xb1, 0x00]);
   */
  unconfirmedPrivateTransfer(address, vendorId, serviceNumber, data) {
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, address);
    baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.UNCONFIRMED_PRIVATE_TRANSFER);
    baServices.privateTransfer.encode(buffer, vendorId, serviceNumber, data);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
  }

  /**
   * DEPRECATED The getEnrollmentSummary command returns a list of event-initiating objects on the target device.
   * @function bacstack.getEnrollmentSummary
   * @param {string} address - IP address of the target device.
   * @param {number} acknowledgmentFilter - Filter for ALL/ACKED/NOT-ACKED, 0/1/2.
   * @param {object=} options
   * @param {object=} options.enrollmentFilter - Filter for enrollment.
   * @param {EventState=} options.eventStateFilter - Filter for event state.
   * @param {EventType=} options.eventTypeFilter - Filter for event type.
   * @param {object=} options.priorityFilter
   * @param {number} options.priorityFilter.min - Filter for minimal priority
   * @param {number} options.priorityFilter.max - Filter for maximal priority
   * @param {number=} options.notificationClassFilter - Filter for notification class.
   * @param {MaxSegmentsAccepted=} options.maxSegments - The maximimal allowed number of segments.
   * @param {MaxApduLengthAccepted=} options.maxApdu - The maximal allowed APDU 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();
   *
   * client.getEnrollmentSummary('192.168.1.43', 0, (err, value) => {
   *   console.log('value: ', value);
   * });
   */
  getEnrollmentSummary(address, acknowledgmentFilter, options, next) {
    next = next || options;
    const settings = {
      maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.GET_ENROLLMENT_SUMMARY, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.getEnrollmentSummary.encode(buffer, acknowledgmentFilter, options.enrollmentFilter, options.eventStateFilter, options.eventTypeFilter, options.priorityFilter, options.notificationClassFilter);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.getEnrollmentSummary.decodeAcknowledge(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.NpduControlPriority.NORMAL_MESSAGE, address);
    baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.UNCONFIRMED_EVENT_NOTIFICATION);
    baServices.eventNotifyData.encode(buffer, eventNotification);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.MaxSegmentsAccepted.SEGMENTS_65,
      maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
      invokeId: options.invokeId || this._getInvokeId()
    };
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
    baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.CONFIRMED_EVENT_NOTIFICATION, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
    baServices.eventNotifyData.encode(buffer, eventNotification);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
    this._transport.send(buffer.buffer, buffer.offset, address);
    this._addCallback(settings.invokeId, (err) => next(err));
  }

  // Public Device Functions
  readPropertyResponse(receiver, invokeId, objectId, property, value) {
    const buffer = this._getBuffer();
    baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
    baApdu.encodeComplexAck(buffer, baEnum.PduTypes.COMPLEX_ACK, baEnum.ConfirmedServiceChoice.READ_PROPERTY, invokeId);
    baServices.readProperty.encodeAcknowledge(buffer, objectId, property.id, property.index, value);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.NpduControlPriority.NORMAL_MESSAGE, receiver);
    baApdu.encodeComplexAck(buffer, baEnum.PduTypes.COMPLEX_ACK, baEnum.ConfirmedServiceChoice.READ_PROPERTY_MULTIPLE, invokeId);
    baServices.readPropertyMultiple.encodeAcknowledge(buffer, values);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.NpduControlPriority.NORMAL_MESSAGE, this._transport.getBroadcastAddress());
    baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.I_AM);
    baServices.iAmBroadcast.encode(buffer, deviceId, this._transport.getMaxPayload(), segmentation, vendorId);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.NpduControlPriority.NORMAL_MESSAGE, this._transport.getBroadcastAddress());
    baApdu.EecodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.I_HAVE);
    baServices.EncodeIhaveBroadcast(buffer, deviceId, objectId, objectName);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.NpduControlPriority.NORMAL_MESSAGE, receiver);
    baApdu.encodeSimpleAck(buffer, baEnum.PduTypes.SIMPLE_ACK, service, invokeId);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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.NpduControlPriority.NORMAL_MESSAGE, receiver);
    baApdu.encodeError(buffer, baEnum.PduTypes.ERROR, service, invokeId);
    baServices.error.encode(buffer, errorClass, errorCode);
    baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.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;