lib/client.js

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.bacnetErrorToString = exports.BacnetError = void 0;
// Util Modules
const events_1 = __importDefault(require("events"));
const debug_1 = __importDefault(require("debug"));
// Local Modules
const transport_1 = __importDefault(require("./transport"));
const baServices = __importStar(require("./services"));
const baAsn1 = __importStar(require("./asn1"));
const baApdu = __importStar(require("./apdu"));
const baNpdu = __importStar(require("./npdu"));
const baBvlc = __importStar(require("./bvlc"));
const baEnum = __importStar(require("./enum"));
const EventEmitter = events_1.default.EventEmitter;
const debug = (0, debug_1.default)("bacnet:client:debug");
const trace = (0, debug_1.default)("bacnet:client:trace");
const ALL_INTERFACES = "0.0.0.0";
const LOCALHOST_INTERFACES_IPV4 = "127.0.0.1";
const BROADCAST_ADDRESS = "255.255.255.255";
const DEFAULT_HOP_COUNT = 0xff;
const BVLC_HEADER_LENGTH = 4;
const BVLC_FWD_HEADER_LENGTH = 10; // FORWARDED_NPDU
const beU = baEnum.UnconfirmedServiceChoice;
const unconfirmedServiceMap = {
    [beU.I_AM]: "iAm",
    [beU.WHO_IS]: "whoIs",
    [beU.WHO_HAS]: "whoHas",
    [beU.UNCONFIRMED_COV_NOTIFICATION]: "covNotifyUnconfirmed",
    [beU.TIME_SYNCHRONIZATION]: "timeSync",
    [beU.UTC_TIME_SYNCHRONIZATION]: "timeSyncUTC",
    [beU.UNCONFIRMED_EVENT_NOTIFICATION]: "eventNotify",
    [beU.I_HAVE]: "iHave",
    [beU.UNCONFIRMED_PRIVATE_TRANSFER]: "privateTransfer",
};
const beC = baEnum.ConfirmedServiceChoice;
const confirmedServiceMap = {
    [beC.READ_PROPERTY]: "readProperty",
    [beC.WRITE_PROPERTY]: "writeProperty",
    [beC.READ_PROPERTY_MULTIPLE]: "readPropertyMultiple",
    [beC.WRITE_PROPERTY_MULTIPLE]: "writePropertyMultiple",
    [beC.CONFIRMED_COV_NOTIFICATION]: "covNotify",
    [beC.ATOMIC_WRITE_FILE]: "atomicWriteFile",
    [beC.ATOMIC_READ_FILE]: "atomicReadFile",
    [beC.SUBSCRIBE_COV]: "subscribeCov",
    [beC.SUBSCRIBE_COV_PROPERTY]: "subscribeProperty",
    [beC.DEVICE_COMMUNICATION_CONTROL]: "deviceCommunicationControl",
    [beC.REINITIALIZE_DEVICE]: "reinitializeDevice",
    [beC.CONFIRMED_EVENT_NOTIFICATION]: "eventNotify",
    [beC.READ_RANGE]: "readRange",
    [beC.CREATE_OBJECT]: "createObject",
    [beC.DELETE_OBJECT]: "deleteObject",
    [beC.ACKNOWLEDGE_ALARM]: "alarmAcknowledge",
    [beC.GET_ALARM_SUMMARY]: "getAlarmSummary",
    [beC.GET_ENROLLMENT_SUMMARY]: "getEnrollmentSummary",
    [beC.GET_EVENT_INFORMATION]: "getEventInformation",
    [beC.LIFE_SAFETY_OPERATION]: "lifeSafetyOperation",
    [beC.ADD_LIST_ELEMENT]: "addListElement",
    [beC.REMOVE_LIST_ELEMENT]: "removeListElement",
    [beC.CONFIRMED_PRIVATE_TRANSFER]: "privateTransfer",
};
//TODO is this correct?
const errorReasonEnums = {
    BacnetAbort: baEnum.RejectReason,
    another: baEnum.AbortReason,
};
class BacnetError extends Error {
    constructor(message, bacnetErrorClass, bacnetErrorCode) {
        super(message);
        this.bacnetErrorClass = bacnetErrorClass;
        this.bacnetErrorCode = bacnetErrorCode;
    }
}
exports.BacnetError = BacnetError;
function bacnetErrorToString(err) {
    var _a;
    const isError = err instanceof Error || err instanceof BacnetError;
    if (!isError)
        return;
    const message = err.message;
    const reasonClass = message === null || message === void 0 ? void 0 : message.split(" - ")[0];
    const reason = +((_a = message === null || message === void 0 ? void 0 : message.split("Reason:")[1]) === null || _a === void 0 ? void 0 : _a.split(" ")[0]);
    // if (typeof reason !== "number") return;
    if (!errorReasonEnums[reasonClass])
        return;
    const reasonString = errorReasonEnums[reasonClass][reason];
    const alternativeReasonString = errorReasonEnums["another"][reason];
    return (reasonClass +
        ": " +
        reasonString +
        "or alternatively it could be " +
        alternativeReasonString);
}
exports.bacnetErrorToString = bacnetErrorToString;
class Client extends EventEmitter {
    /**
     *
     * @param options
     */
    constructor(options) {
        super();
        options = options || {};
        this._invokeCounter = 1;
        this._invokeStore = {};
        this._lastSequenceNumber = 0;
        this._segmentStore = [];
        this._settings = {
            port: options.port || 47808,
            interface: options.interface || ALL_INTERFACES,
            transport: options.transport,
            broadcastAddress: options.broadcastAddress || BROADCAST_ADDRESS,
            apduTimeout: options.apduTimeout || 3000,
        };
        options.reuseAddr =
            options.reuseAddr === undefined ? true : !!options.reuseAddr;
        this._transport =
            this._settings.transport ||
                new transport_1.default({
                    port: this._settings.port,
                    interface: this._settings.interface,
                    broadcastAddress: this._settings.broadcastAddress,
                    reuseAddr: options.reuseAddr,
                });
        // Setup code
        this._transport.on("message", this._receiveData.bind(this));
        this._transport.on("error", this._receiveError.bind(this));
        this._transport.on("listening", () => this.emit("listening"));
        this._transport.open();
    }
    /**
     *
     * @returns {number}
     * @private
     */
    _getInvokeId() {
        const id = this._invokeCounter++;
        if (id >= 256)
            this._invokeCounter = 1;
        return id - 1;
    }
    /**
     *
     * @param id
     * @param err
     * @param result
     * @returns {*}
     * @private
     */
    _invokeCallback(id, err, result) {
        const callback = this._invokeStore[id];
        if (callback) {
            return void callback(err, result);
        }
        debug("InvokeId", id, "not found -> drop package");
    }
    /**
     *
     * @param id
     * @param callback
     * @private
     */
    _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);
        };
    }
    /**
     *
     * @param isForwarded
     * @returns {{offset: (number), buffer: *}}
     * @private
     */
    _getBuffer(isForwarded) {
        return Object.assign({}, {
            buffer: Buffer.alloc(this._transport.getMaxPayload()),
            offset: isForwarded ? BVLC_FWD_HEADER_LENGTH : BVLC_HEADER_LENGTH,
        });
    }
    /**
     *
     * @param invokeId
     * @param buffer
     * @param offset
     * @param length
     * @returns {*}
     * @private
     */
    _processError(invokeId, buffer, offset, length) {
        // const result = baServices.error.decode(buffer, offset, length);
        const result = baServices.error.decode(buffer, offset);
        trace("Received error:", result);
        if (!result) {
            return debug("Couldn`t decode Error");
        }
        const err = new BacnetError(baServices.error.buildMessage(result));
        err.bacnetErrorClass = result.class;
        err.bacnetErrorCode = result.code;
        this._invokeCallback(invokeId, err);
    }
    /**
     *
     * @param invokeId
     * @param reason
     * @private
     */
    _processAbort(invokeId, reason) {
        const err = new BacnetError("BacnetAbort - Reason:" + reason);
        err.bacnetAbortReason = reason;
        //@ts-ignore
        err.bacnetErrorClass = baEnum.AbortReason;
        this._invokeCallback(invokeId, err);
    }
    /**
     *
     * @param receiver
     * @param negative
     * @param server
     * @param originalInvokeId
     * @param sequencenumber
     * @param actualWindowSize
     * @private
     */
    _segmentAckResponse(receiver, negative, server, originalInvokeId, sequencenumber, actualWindowSize) {
        const buffer = this._getBuffer(receiver && receiver.forwardedFrom);
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver, null, DEFAULT_HOP_COUNT, baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK, 0);
        baApdu.encodeSegmentAck(buffer, baEnum.PduType.SEGMENT_ACK |
            (negative ? baEnum.PduSegAckBit.NEGATIVE_ACK : 0) |
            (server ? baEnum.PduSegAckBit.SERVER : 0), originalInvokeId, sequencenumber, actualWindowSize);
        this.sendBvlc(receiver, buffer);
    }
    /**
     *
     * @param msg
     * @param first
     * @param moreFollows
     * @param buffer
     * @param offset
     * @param length
     * @private
     */
    _performDefaultSegmentHandling(msg, first, moreFollows, buffer, offset, length) {
        if (first) {
            this._segmentStore = [];
            msg.type &= ~baEnum.PduConReqBit.SEGMENTED_MESSAGE;
            let apduHeaderLen = 3;
            if ((msg.type & baEnum.PDU_TYPE_MASK) ===
                baEnum.PduType.CONFIRMED_REQUEST) {
                apduHeaderLen = 4;
            }
            const apdubuffer = this._getBuffer();
            apdubuffer.offset = 0;
            buffer.copy(apdubuffer.buffer, apduHeaderLen, offset, offset + length);
            if ((msg.type & baEnum.PDU_TYPE_MASK) ===
                baEnum.PduType.CONFIRMED_REQUEST) {
                baApdu.encodeConfirmedServiceRequest(apdubuffer, msg.type, msg.service, msg.maxSegments, msg.maxApdu, msg.invokeId, 0, 0);
            }
            else {
                baApdu.encodeComplexAck(apdubuffer, msg.type, msg.service, msg.invokeId, 0, 0);
            }
            this._segmentStore.push(
            //@ts-ignore
            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 = [];
            msg.type &= ~baEnum.PduConReqBit.SEGMENTED_MESSAGE;
            this._handlePdu(apduBuffer, 0, apduBuffer.length, msg.header);
        }
    }
    /**
     *
     * @param msg
     * @param server
     * @param buffer
     * @param offset
     * @param length
     * @private
     */
    _processSegment(msg, server, buffer, offset, length) {
        let first = false;
        if (msg.sequencenumber === 0 && this._lastSequenceNumber === 0) {
            first = true;
        }
        else {
            if (msg.sequencenumber !== this._lastSequenceNumber + 1) {
                return this._segmentAckResponse(msg.header.address, true, server, msg.invokeId, this._lastSequenceNumber, msg.proposedWindowNumber);
            }
        }
        this._lastSequenceNumber = msg.sequencenumber;
        const moreFollows = msg.type & baEnum.PduConReqBit.MORE_FOLLOWS;
        if (!moreFollows) {
            this._lastSequenceNumber = 0;
        }
        if (msg.sequencenumber % msg.proposedWindowNumber === 0 || !moreFollows) {
            this._segmentAckResponse(msg.header.address, false, server, msg.invokeId, msg.sequencenumber, msg.proposedWindowNumber);
        }
        this._performDefaultSegmentHandling(msg, first, moreFollows, buffer, offset, length);
    }
    /**
     *
     * @param serviceMap
     * @param content
     * @param buffer
     * @param offset
     * @param length
     * @returns {*}
     * @private
     */
    _processServiceRequest(serviceMap, content, buffer, offset, length) {
        let result;
        const sender = content.header.sender;
        if (sender.address === LOCALHOST_INTERFACES_IPV4) {
            debug("Received and skipped localhost service request:", content.service);
            return;
        }
        const name = serviceMap[content.service];
        if (!name) {
            debug("Received unsupported service request:", content.service);
            return;
        }
        const id = content.invokeId ? "@" + content.invokeId : "";
        trace(`Received service request${id}:`, name);
        // Find a function to decode the packet.
        const serviceHandler = baServices[name];
        if (serviceHandler) {
            try {
                content.payload = serviceHandler.decode(buffer, offset, length);
                trace(`Handled service request${id}:`, name, JSON.stringify(content));
            }
            catch (e) {
                // Sometimes incomplete or corrupted messages will cause exceptions
                // during decoding, but we don't want these to terminate the program, so
                // we'll just log them and ignore them.
                debug("Exception thrown when processing message:", e);
                debug("Original message was", name + ":", content);
                return;
            }
            if (!content.payload) {
                return debug("Received invalid", name, "message");
            }
        }
        else {
            debug("No serviceHandler defined for:", name);
            // Call the callback anyway, just with no payload.
        }
        //trace('Passing payload over to callback:', content);
        // Call the user code, if they've defined a callback.
        if (this.listenerCount(name)) {
            trace("listener count by name emits " + name + " with content");
            this.emit(name, content);
        }
        else {
            if (this.listenerCount("unhandledEvent")) {
                trace("unhandled event emiting with content");
                this.emit("unhandledEvent", content);
            }
            else {
                // No 'unhandled event' handler, so respond with an error ourselves.
                // This is better than doing nothing, which can often make the other
                // device think we have gone offline.
                trace("no unhandled event handler with header: " +
                    JSON.stringify(content.header));
                if (content.header.expectingReply) {
                    debug("Replying with error for unhandled service:", name);
                    // Make sure we don't reply pretending to be the caller, if we got a
                    // forwarded message!  Really this should be overridden to be your
                    // own IP, but only if it's not null/undefined to begin with.
                    content.header.sender.forwardedFrom = null;
                    this.errorResponse(content.header.sender, content.service, content.invokeId, baEnum.ErrorClass.SERVICES, baEnum.ErrorCode.REJECT_UNRECOGNIZED_SERVICE);
                }
            }
        }
    }
    /**
     *
     * @param buffer
     * @param offset
     * @param length
     * @param header
     * @private
     */
    _handlePdu(buffer, offset, length, header) {
        let msg;
        trace("handlePdu Header: ", header);
        // Handle different PDU types
        switch (header.apduType & baEnum.PDU_TYPE_MASK) {
            case baEnum.PduType.UNCONFIRMED_REQUEST:
                msg = baApdu.decodeUnconfirmedServiceRequest(buffer, offset);
                msg.header = header;
                msg.header.confirmedService = false;
                this._processServiceRequest(unconfirmedServiceMap, msg, buffer, offset + msg.len, length - msg.len);
                break;
            case baEnum.PduType.SIMPLE_ACK:
                msg = baApdu.decodeSimpleAck(buffer, offset);
                offset += msg.len;
                length -= msg.len;
                this._invokeCallback(msg.invokeId, null, {
                    msg: msg,
                    buffer: buffer,
                    offset: offset + msg.len,
                    length: length - msg.len,
                });
                break;
            case baEnum.PduType.COMPLEX_ACK:
                msg = baApdu.decodeComplexAck(buffer, offset);
                msg.header = header;
                if ((header.apduType & baEnum.PduConReqBit.SEGMENTED_MESSAGE) === 0) {
                    this._invokeCallback(msg.invokeId, null, {
                        msg: msg,
                        buffer: buffer,
                        offset: offset + msg.len,
                        length: length - msg.len,
                    });
                }
                else {
                    this._processSegment(msg, true, buffer, offset + msg.len, length - msg.len);
                }
                break;
            case baEnum.PduType.SEGMENT_ACK:
                msg = baApdu.decodeSegmentAck(buffer, offset);
                msg.header = header;
                this._processSegment(msg, true, buffer, offset + msg.len, length - msg.len);
                break;
            case baEnum.PduType.ERROR:
                msg = baApdu.decodeError(buffer, offset);
                this._processError(msg.invokeId, buffer, offset + msg.len, length - msg.len);
                break;
            case baEnum.PduType.REJECT:
            case baEnum.PduType.ABORT:
                msg = baApdu.decodeAbort(buffer, offset);
                this._processAbort(msg.invokeId, msg.reason);
                break;
            case baEnum.PduType.CONFIRMED_REQUEST:
                msg = baApdu.decodeConfirmedServiceRequest(buffer, offset);
                msg.header = header;
                msg.header.confirmedService = true;
                if ((header.apduType & baEnum.PduConReqBit.SEGMENTED_MESSAGE) === 0) {
                    this._processServiceRequest(confirmedServiceMap, msg, buffer, offset + msg.len, length - msg.len);
                }
                else {
                    this._processSegment(msg, true, buffer, offset + msg.len, length - msg.len);
                }
                break;
            default:
                debug(`Received unknown PDU type ${header.apduType} -> Drop packet`);
                break;
        }
    }
    /**
     *
     * @param buffer
     * @param offset
     * @param msgLength
     * @param header
     * @returns {*}
     * @private
     */
    _handleNpdu(buffer, offset, msgLength, header) {
        // Check data length
        if (msgLength <= 0) {
            return trace("No NPDU data -> Drop package");
        }
        // Parse baNpdu header
        const result = baNpdu.decode(buffer, offset);
        if (!result) {
            return trace("Received invalid NPDU header -> Drop package");
        }
        if (result.funct & baEnum.NpduControlBit.NETWORK_LAYER_MESSAGE) {
            return trace("Received network layer message -> Drop package");
        }
        offset += result.len;
        msgLength -= result.len;
        if (msgLength <= 0) {
            return trace("No APDU data -> Drop package");
        }
        header.apduType = baApdu.getDecodedType(buffer, offset);
        header.expectingReply = !!(result.funct & baEnum.NpduControlBit.EXPECTING_REPLY);
        this._handlePdu(buffer, offset, msgLength, header);
    }
    /**
     *
     * @param buffer
     * @param remoteAddress
     * @returns {*}
     * @private
     */
    _receiveData(buffer, remoteAddress) {
        // Check data length
        if (buffer.length < baEnum.BVLC_HEADER_LENGTH) {
            return trace("Received invalid data -> Drop package");
        }
        // Parse BVLC header
        const result = baBvlc.decode(buffer, 0);
        if (!result) {
            return trace("Received invalid BVLC header -> Drop package");
        }
        let header = {
            // Which function the packet came in on, so later code can distinguish
            // between ORIGINAL_BROADCAST_NPDU and DISTRIBUTE_BROADCAST_TO_NETWORK.
            func: result.func,
            sender: {
                // Address of the host we are directly connected to. String, IP:port.
                address: remoteAddress,
                // If the host is a BBMD passing messages along to another node, this
                // is the address of the distant BACnet node.  String, IP:port.
                // Typically we won't have network connectivity to this address, but
                // we have to include it in replies so the host we are connect to knows
                // where to forward the messages.
                forwardedFrom: null,
            },
        };
        // Check BVLC function
        switch (result.func) {
            case baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU:
            case baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU:
                this._handleNpdu(buffer, result.len, buffer.length - result.len, header);
                break;
            case baEnum.BvlcResultPurpose.FORWARDED_NPDU:
                // Preserve the IP of the node behind the BBMD so we know where to send
                // replies back to.
                header.sender.forwardedFrom = result.originatingIP;
                this._handleNpdu(buffer, result.len, buffer.length - result.len, header);
                break;
            case baEnum.BvlcResultPurpose.REGISTER_FOREIGN_DEVICE:
                let decodeResult = baServices.registerForeignDevice.decode(buffer, result.len, buffer.length - result.len);
                if (!decodeResult) {
                    return trace("Received invalid registerForeignDevice message");
                }
                this.emit("registerForeignDevice", {
                    header: header,
                    payload: decodeResult,
                });
                break;
            case baEnum.BvlcResultPurpose.DISTRIBUTE_BROADCAST_TO_NETWORK:
                this._handleNpdu(buffer, result.len, buffer.length - result.len, header);
                break;
            default:
                debug("Received unknown BVLC function " + result.func + " -> Drop package");
                break;
        }
    }
    /**
     * @event bacnet.error
     * @param {error} err - The error object thrown by the underlying transport layer.
     * @example
     * const bacnet = require('ts-bacnet');
     * const client = new bacnet();
     *
     * client.on('error', (err) => {
     *   console.log('Error occurred: ', err);
     *   client.close();
     * });
     */
    _receiveError(err) {
        this.emit("error", err);
    }
    /**
     * The whoIs command discovers all BACNET devices in a network.
     * @function bacnet.whoIs
     * @param {string} receiver - IP address of the target device.
     * @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.
     * @fires bacnet.iAm
     * @example
     * const bacnet = require('ts-bacnet');
     * const client = new bacnet();
     *
     * client.whoIs();
     */
    whoIs(receiver, options) {
        if (!options) {
            if (receiver &&
                typeof receiver === "object" &&
                receiver.address === undefined &&
                receiver.forwardedFrom === undefined &&
                (receiver.lowLimit !== undefined || receiver.highLimit !== undefined)) {
                // receiver seems to be an options object
                options = receiver;
                receiver = undefined;
            }
        }
        options = options || {};
        const settings = {
            lowLimit: options.lowLimit,
            highLimit: options.highLimit,
        };
        // const hasForwardedFrom =
        //   typeof receiver !== "string" && receiver.forwardedFrom;
        // const buffer = this._getBuffer(!!hasForwardedFrom);
        //@ts-ignore
        const buffer = this._getBuffer(receiver && receiver.forwardedFrom);
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, 
        //@ts-ignore
        receiver, null, DEFAULT_HOP_COUNT, baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK, 0);
        baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduType.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.WHO_IS);
        baServices.whoIs.encode(buffer, settings.lowLimit, settings.highLimit);
        this.sendBvlc(receiver, buffer);
    }
    /**
     * The timeSync command sets the time of a target device.
     * @function bacnet.timeSync
     * @param {string} receiver - IP address of the target device.
     * @param {date} dateTime - The date and time to set on the target device.
     * @example
     * const bacnet = require('ts-bacnet');
     * const client = new bacnet();
     *
     * client.timeSync('192.168.1.43', new Date());
     */
    timeSync(receiver, dateTime) {
        const buffer = this._getBuffer(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
        baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduType.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.TIME_SYNCHRONIZATION);
        baServices.timeSync.encode(buffer, dateTime);
        this.sendBvlc(receiver, buffer);
    }
    /**
     * The timeSyncUTC command sets the UTC time of a target device.
     * @function bacnet.timeSyncUTC
     * @param {string} receiver - IP address of the target device.
     * @param {date} dateTime - The date and time to set on the target device.
     * @example
     * const bacnet = require('ts-bacnet');
     * const client = new bacnet();
     *
     * client.timeSyncUTC('192.168.1.43', new Date());
     */
    timeSyncUTC(receiver, dateTime) {
        const buffer = this._getBuffer(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
        baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduType.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.UTC_TIME_SYNCHRONIZATION);
        baServices.timeSync.encode(buffer, dateTime);
        this.sendBvlc(receiver, buffer);
    }
    /**
     * The readProperty command reads a single property of an object from a device.
     * @function bacnet.readProperty
     * @param {string} receiver - 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('ts-bacnet');
     * const client = new bacnet();
     *
     * client.readProperty('192.168.1.43', {type: 8, instance: 44301}, 28, (err, value) => {
     *   console.log('value: ', value);
     * });
     */
    readProperty(receiver, objectId, propertyId, options, next) {
        //@ts-ignore
        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 || options.arrayIndex === 0
                ? options.arrayIndex
                : baEnum.ASN1_ARRAY_ALL,
        };
        const buffer = this._getBuffer(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, 
        //@ts-ignore
        receiver, null, DEFAULT_HOP_COUNT, baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK, 0);
        const type = baEnum.PduType.CONFIRMED_REQUEST |
            //@ts-ignore
            (settings.maxSegments !== baEnum.MaxSegmentsAccepted.SEGMENTS_0
                ? baEnum.PduConReqBit.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);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            const result = baServices.readProperty.decodeAcknowledge(data.buffer, data.offset, data.length);
            if (!result) {
                return void next(new Error("INVALID_DECODING"));
            }
            next(null, result);
        });
    }
    /**
     * The writeProperty command writes a single property of an object to a device.
     * @function bacnet.writeProperty
     * @param {string} receiver - 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 {ApplicationTag} values.type - 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 and value object in case of success.
     * @example
     * const bacnet = require('ts-bacnet');
     * const client = new bacnet();
     *
     * client.writeProperty('192.168.1.43', {type: 8, instance: 44301}, 28, [
     *   {type: bacnet.enum.ApplicationTag.REAL, value: 100}
     * ], (err, value) => {
     *   console.log('value: ', value);
     * });
     */
    writeProperty(receiver, objectId, propertyId, values, options, next) {
        //@ts-ignore
        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 || baEnum.ASN1_NO_PRIORITY,
        };
        const buffer = this._getBuffer(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, 
        //@ts-ignore Todo somehow types do not match at all
        receiver, null, DEFAULT_HOP_COUNT, baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK, 0);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.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);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            next(err);
        });
    }
    /**
     * The readPropertyMultiple command reads multiple properties in multiple objects from a device.
     * @function bacnet.readPropertyMultiple
     * @param {string} receiver - 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('ts-bacnet');
     * 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(receiver, propertiesArray, options, next) {
        //@ts-ignore
        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 isForwarded = typeof receiver !== "string" && receiver.forwardedFrom;
        const buffer = this._getBuffer(!!isForwarded);
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, 
        //@ts-ignore Todo somehow types do not match at all
        receiver, null, DEFAULT_HOP_COUNT, baEnum.NetworkLayerMessageType.WHO_IS_ROUTER_TO_NETWORK, 0);
        const type = baEnum.PduType.CONFIRMED_REQUEST |
            //@ts-ignore
            (settings.maxSegments !== baEnum.MaxSegmentsAccepted.SEGMENTS_0
                ? baEnum.PduConReqBit.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);
        this.sendBvlc(receiver, buffer);
        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 bacnet.writePropertyMultiple
     * @param {string} receiver - 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 {ApplicationTag} values.values.value.type - 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 and value object in case of success.
     * @example
     * const bacnet = require('ts-bacnet');
     * const client = new bacnet();
     *
     * const values = [
     *   {objectId: {type: 8, instance: 44301}, values: [
     *     {property: {id: 28, index: 12}, value: [{type: bacnet.enum.ApplicationTag.BOOLEAN, value: true}], priority: 8}
     *   ]}
     * ];
     * client.writePropertyMultiple('192.168.1.43', values, (err, value) => {
     *   console.log('value: ', value);
     * });
     */
    writePropertyMultiple(receiver, values, options, next) {
        //@ts-ignore
        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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, 
        //@ts-ignore
        receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.WRITE_PROPERTY_MULTIPLE, settings.maxSegments, settings.maxApdu, settings.invokeId);
        baServices.writePropertyMultiple.encodeObject(buffer, values);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            next(err);
        });
    }
    /**
     * The confirmedCOVNotification command is used to push notifications to other
     * systems that have registered with us via a subscribeCOV message.
     * @function bacnet.confirmedCOVNotification
     * @param {string} receiver - IP address of the target device.
     * @param {object} monitoredObject - The object being monitored, from subscribeCOV.
     * @param {number} monitoredObject.type - Object type.
     * @param {number} monitoredObject.instance - Object instance.
     * @param {number} subscribeId - Subscriber ID from subscribeCOV,
     * @param {number} initiatingDeviceId - Our BACnet device ID.
     * @param {number} lifetime - Number of seconds left until the subscription expires.
     * @param {array} values - values for the monitored object.  See example.
     * @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('ts-bacnet');
     * const client = new bacnet();
     *
     * const settings = {deviceId: 123}; // our BACnet device
     *
     * // Items saved from subscribeCOV message
     * const monitoredObject = {type: 1, instance: 1};
     * const subscriberProcessId = 123;
     *
     * client.confirmedCOVNotification(
     *   '192.168.1.43',
     *   monitoredObject,
     *   subscriberProcessId,
     *   settings.deviceId,
     *   30, // should be lifetime of subscription really
     *   [
     *     {
     *       property: { id: bacnet.enum.PropertyIdentifier.PRESENT_VALUE },
     *       value: [
     *         {value: 123, type: bacnet.enum.ApplicationTag.REAL},
     *       ],
     *     },
     *   ],
     *   (err) => {
     *     console.log('error: ', err);
     *   }
     * );
     */
    confirmedCOVNotification(receiver, monitoredObject, subscribeId, initiatingDeviceId, lifetime, 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.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.CONFIRMED_COV_NOTIFICATION, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baServices.covNotify.encode(buffer, subscribeId, initiatingDeviceId, monitoredObject, lifetime, values);
        baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            next();
        });
    }
    /**
     * The deviceCommunicationControl command enables or disables network communication of the target device.
     * @function bacnet.deviceCommunicationControl
     * @param {string} receiver - 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 and value object in case of success.
     * @example
     * const bacnet = require('ts-bacnet');
     * const client = new bacnet();
     *
     * client.deviceCommunicationControl('192.168.1.43', 0, bacnet.enum.EnableDisable.DISABLE, (err) => {
     *   console.log('error: ', err);
     * });
     */
    deviceCommunicationControl(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.DEVICE_COMMUNICATION_CONTROL, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baServices.deviceCommunicationControl.encode(buffer, timeDuration, enableDisable, settings.password);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            next(err);
        });
    }
    /**
     * The reinitializeDevice command initiates a restart of the target device.
     * @function bacnet.reinitializeDevice
     * @param {string} receiver - 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 and value object in case of success.
     * @example
     * const bacnet = require('ts-bacnet');
     * const client = new bacnet();
     *
     * client.reinitializeDevice('192.168.1.43', bacnet.enum.ReinitializedState.COLDSTART, (err, value) => {
     *   console.log('value: ', value);
     * });
     */
    reinitializeDevice(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.REINITIALIZE_DEVICE, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baServices.reinitializeDevice.encode(buffer, state, settings.password);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            next(err);
        });
    }
    /**
     *
     * @param receiver
     * @param objectId
     * @param position
     * @param fileBuffer
     * @param options
     * @param next
     */
    writeFile(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.ATOMIC_WRITE_FILE, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baServices.atomicWriteFile.encode(buffer, false, objectId, position, fileBuffer);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            const result = baServices.atomicWriteFile.decodeAcknowledge(data.buffer, data.offset, data.length);
            if (!result) {
                return void next(new Error("INVALID_DECODING"));
            }
            next(null, result);
        });
    }
    /**
     *
     * @param receiver
     * @param objectId
     * @param position
     * @param count
     * @param options
     * @param next
     */
    readFile(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.ATOMIC_READ_FILE, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baServices.atomicReadFile.encode(buffer, true, objectId, position, count);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            const result = baServices.atomicReadFile.decodeAcknowledge(data.buffer, data.offset, data.length);
            if (!result) {
                return void next(new Error("INVALID_DECODING"));
            }
            next(null, result);
        });
    }
    /**
     *
     * @param receiver
     * @param objectId
     * @param idxBegin
     * @param quantity
     * @param options
     * @param next
     */
    readRange(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.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);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            const result = baServices.readRange.decodeAcknowledge(data.buffer, data.offset, data.length);
            if (!result) {
                return void next(new Error("INVALID_DECODING"));
            }
            next(null, result);
        });
    }
    /**
     *
     * @param receiver
     * @param objectId
     * @param subscribeId
     * @param cancel
     * @param issueConfirmedNotifications
     * @param lifetime
     * @param options
     * @param next
     */
    subscribeCov(receiver, objectId, subscribeId, cancel, issueConfirmedNotifications, lifetime, options, nextFunc) {
        // if next doesnt exist then options is probably the next function
        const next = nextFunc || 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, 
        // @ts-ignore TODO types not matching with documentation and code
        receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.SUBSCRIBE_COV, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baServices.subscribeCov.encode(buffer, subscribeId, objectId, cancel, issueConfirmedNotifications, lifetime);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            next();
        });
    }
    /**
     *
     * @param receiver
     * @param objectId
     * @param monitoredProperty
     * @param subscribeId
     * @param cancel
     * @param issueConfirmedNotifications
     * @param options
     * @param next
     */
    subscribeProperty(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.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);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            next();
        });
    }
    /**
     * The unconfirmedCOVNotification command sends an unconfirmed COV notification to a device
     * @function bacnet.unconfirmedCOVNotification
     * @param {string} receiver - IP address of the target device.
     * @param {number} subscriberProcessId - The process id which was used by a target device in the subscription.
     * @param {number} initiatingDeviceId - The id of this device.
     * @param {object} monitoredObjectId - Specifies about which object the notification is.
     * @param {number} monitoredObjectId.type - The BACNET object type of the notification.
     * @param {number} monitoredObjectId.instance - The BACNET object instance of the notification.
     * @param {number} timeRemaining - How long the subscription is still active in seconds.
     * @param {object[]} values - List of properties with updated values.
     * @param {object} values.property - Property specifications.
     * @param {number} values.property.id - The updated BACNET property id.
     * @param {number} values.property.index - The array index of the updated property.
     * @param {object[]} values.value - A list of updated values.
     * @param {ApplicationTag} values.value.type - The data-type of the updated value.
     * @param {object} values.value.value - The actual updated value.
     * @param {number} values.priority - The priority of the updated property.
     * @example
     * const bacnet = require('ts-bacnet');
     * const client = new bacnet();
     *
     * client.unconfirmedCOVNotification(
     *            '127.0.0.1',
     *            3,
     *            433,
     *            {type: 2, instance: 122},
     *            120,
     *            [
     *              {
     *                property: {id: 85},
     *                value: [{type: baEnum.ApplicationTag.REAL, value: 12.3}]
     *              },
     *              {
     *                property: {id: 111},
     *                value: [{type: baEnum.ApplicationTag.BIT_STRING, value: 0xFFFF}]
     *              }
     *            ]);
     */
    unconfirmedCOVNotification(receiver, subscriberProcessId, initiatingDeviceId, monitoredObjectId, timeRemaining, values) {
        const buffer = this._getBuffer();
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
        baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduType.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.UNCONFIRMED_COV_NOTIFICATION);
        baServices.covNotify.encode(buffer, subscriberProcessId, initiatingDeviceId, monitoredObjectId, timeRemaining, values);
        baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
        this._transport.send(buffer.buffer, buffer.offset, receiver);
    }
    /**
     *
     * @param receiver
     * @param objectId
     * @param values
     * @param options
     * @param next
     */
    createObject(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.CREATE_OBJECT, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baServices.createObject.encode(buffer, objectId, values);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            next();
        });
    }
    /**
     *
     * @param receiver
     * @param objectId
     * @param options
     * @param next
     */
    deleteObject(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.DELETE_OBJECT, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baServices.deleteObject.encode(buffer, objectId);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            next();
        });
    }
    /**
     *
     * @param receiver
     * @param objectId
     * @param reference
     * @param values
     * @param options
     * @param next
     */
    removeListElement(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.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);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            next();
        });
    }
    /**
     *
     * @param receiver
     * @param objectId
     * @param reference
     * @param values
     * @param options
     * @param next
     */
    addListElement(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.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);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            next();
        });
    }
    /**
     *
     * @param receiver
     * @param options
     * @param next
     */
    getAlarmSummary(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.GET_ALARM_SUMMARY, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            const result = baServices.alarmSummary.decode(data.buffer, data.offset, data.length);
            if (!result) {
                return void next(new Error("INVALID_DECODING"));
            }
            next(null, result);
        });
    }
    /**
     *
     * @param receiver
     * @param objectId
     * @param options
     * @param next
     */
    getEventInformation(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.GET_EVENT_INFORMATION, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baAsn1.encodeContextObjectId(buffer, 0, objectId.type, objectId.instance);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            const result = baServices.eventInformation.decode(data.buffer, data.offset, data.length);
            if (!result) {
                return void next(new Error("INVALID_DECODING"));
            }
            next(null, result);
        });
    }
    /**
     *
     * @param receiver
     * @param objectId
     * @param eventState
     * @param ackText
     * @param evTimeStamp
     * @param ackTimeStamp
     * @param options
     * @param next
     */
    acknowledgeAlarm(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.ACKNOWLEDGE_ALARM, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baServices.alarmAcknowledge.encode(buffer, 57, objectId, eventState, ackText, evTimeStamp, ackTimeStamp);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            next();
        });
    }
    /**
     *
     * @param receiver
     * @param vendorId
     * @param serviceNumber
     * @param data
     * @param options
     * @param next
     */
    confirmedPrivateTransfer(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.CONFIRMED_PRIVATE_TRANSFER, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baServices.privateTransfer.encode(buffer, vendorId, serviceNumber, data);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            next();
        });
    }
    /**
     *
     * @param receiver
     * @param vendorId
     * @param serviceNumber
     * @param data
     */
    unconfirmedPrivateTransfer(receiver, vendorId, serviceNumber, data) {
        const buffer = this._getBuffer(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
        baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduType.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.UNCONFIRMED_PRIVATE_TRANSFER);
        baServices.privateTransfer.encode(buffer, vendorId, serviceNumber, data);
        this.sendBvlc(receiver, buffer);
    }
    /**
     *
     * @param receiver
     * @param acknowledgmentFilter
     * @param options
     * @param next
     */
    getEnrollmentSummary(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.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);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            const result = baServices.getEnrollmentSummary.decodeAcknowledge(data.buffer, data.offset, data.length);
            if (!result) {
                return void next(new Error("INVALID_DECODING"));
            }
            next(null, result);
        });
    }
    /**
     *
     * @param receiver
     * @param eventNotification
     */
    unconfirmedEventNotification(receiver, eventNotification) {
        const buffer = this._getBuffer(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
        baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduType.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.UNCONFIRMED_EVENT_NOTIFICATION);
        baServices.eventNotifyData.encode(buffer, eventNotification);
        this.sendBvlc(receiver, buffer);
    }
    /**
     *
     * @param receiver
     * @param eventNotification
     * @param options
     * @param next
     */
    confirmedEventNotification(receiver, 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(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE |
            baEnum.NpduControlBit.EXPECTING_REPLY, receiver);
        baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduType.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.CONFIRMED_EVENT_NOTIFICATION, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
        baServices.eventNotifyData.encode(buffer, eventNotification);
        this.sendBvlc(receiver, buffer);
        this._addCallback(settings.invokeId, (err, data) => {
            if (err) {
                return void next(err);
            }
            next();
        });
    }
    /**
     * The readPropertyResponse call sends a response with information about one of our properties.
     * @function bacnet.readPropertyResponse
     * @param {string} receiver - IP address of the target device.
     * @param {number} invokeId - ID of the original readProperty request.
     * @param {object} objectId - objectId from the original request,
     * @param {object} property - property being read, taken from the original request.
     * @param {object=} options varying behaviour for special circumstances
     * @param {string=} options.forwardedFrom - If functioning as a BBMD, the IP address this message originally came from.
     */
    readPropertyResponse(receiver, invokeId, objectId, property, value, options = {}) {
        const buffer = this._getBuffer(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
        baApdu.encodeComplexAck(buffer, baEnum.PduType.COMPLEX_ACK, baEnum.ConfirmedServiceChoice.READ_PROPERTY, invokeId);
        baServices.readProperty.encodeAcknowledge(buffer, objectId, property.id, property.index, value);
        this.sendBvlc(receiver, buffer);
    }
    readPropertyMultipleResponse(receiver, invokeId, values) {
        const buffer = this._getBuffer(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
        baApdu.encodeComplexAck(buffer, baEnum.PduType.COMPLEX_ACK, baEnum.ConfirmedServiceChoice.READ_PROPERTY_MULTIPLE, invokeId);
        baServices.readPropertyMultiple.encodeAcknowledge(buffer, values);
        this.sendBvlc(receiver, buffer);
    }
    /**
     * The iAmResponse command is sent as a reply to a whoIs request.
     * @function bacnet.iAmResponse
     * @param {object} receiver - address to send packet to, null for local broadcast.
     * @param {number} deviceId - Our device ID.
     * @param {number} segmentation - an enum.Segmentation value.
     * @param {number} vendorId - The numeric ID assigned to the organisation providing this application.
     */
    iAmResponse(receiver, deviceId, segmentation, vendorId) {
        const buffer = this._getBuffer(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
        baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduType.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.I_AM);
        baServices.iAm.encode(buffer, deviceId, this._transport.getMaxPayload(), segmentation, vendorId);
        this.sendBvlc(receiver, buffer);
    }
    /**
     *
     * @param receiver
     * @param deviceId
     * @param objectId
     * @param objectName
     */
    iHaveResponse(receiver, deviceId, objectId, objectName) {
        const buffer = this._getBuffer(!!(typeof receiver !== "string" && receiver.forwardedFrom));
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
        baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduType.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.I_HAVE);
        baServices.iHave.encode(buffer, deviceId, objectId, objectName);
        this.sendBvlc(receiver, buffer);
    }
    /**
     *
     * @param receiver
     * @param service
     * @param invokeId
     */
    simpleAckResponse(receiver, service, invokeId) {
        if (typeof receiver === "string")
            return;
        const buffer = this._getBuffer(!!receiver.forwardedFrom);
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
        baApdu.encodeSimpleAck(buffer, baEnum.PduType.SIMPLE_ACK, service, invokeId);
        this.sendBvlc(receiver, buffer);
    }
    /**
     *
     * @param receiver
     * @param service
     * @param invokeId
     * @param errorClass
     * @param errorCode
     */
    errorResponse(receiver, service, invokeId, errorClass, errorCode) {
        trace(`error response on ${JSON.stringify(receiver)} service: ${JSON.stringify(service)} invokeId: ${invokeId} errorClass: ${errorClass} errorCode: ${errorCode}`);
        trace(`error message ${baServices.error.buildMessage({
            class: errorClass,
            code: errorCode,
        })}`);
        const hasSource = !!(typeof receiver !== "string" && receiver.forwardedFrom);
        if (typeof receiver === "string")
            throw new Error("receiver must be object");
        const buffer = this._getBuffer(hasSource);
        baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
        baApdu.encodeError(buffer, baEnum.PduType.ERROR, service, invokeId);
        baServices.error.encode(buffer, errorClass, errorCode);
        this.sendBvlc(receiver, buffer);
    }
    /**
     *
     * @param receiver
     * @param buffer
     */
    sendBvlc(receiver, buffer) {
        if (typeof receiver === "string") {
            receiver = {
                address: receiver,
            };
        }
        if (receiver && receiver.forwardedFrom) {
            // Remote node address given, forward to BBMD
            baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.FORWARDED_NPDU, buffer.offset, receiver.forwardedFrom);
        }
        else if (receiver && receiver.address) {
            // Specific address, unicast
            baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
        }
        else {
            // No address, broadcast
            baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU, buffer.offset);
        }
        this._transport.send(buffer.buffer, buffer.offset, (receiver && receiver.address) || null);
    }
    /**
     * The resultResponse is a BVLC-Result message used to respond to certain events, such as BBMD registration.
     * This message cannot be wrapped for passing through a BBMD, as it is used as a BBMD control message.
     * @function bacnet.resultResponse
     * @param {string} receiver - IP address of the target device.
     * @param {number} resultCode - Single value from BvlcResultFormat enum.
     */
    resultResponse(receiver, resultCode) {
        const buffer = this._getBuffer();
        baApdu.encodeResult(buffer, resultCode);
        baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.BVLC_RESULT, buffer.offset);
        this._transport.send(buffer.buffer, buffer.offset, receiver.address);
    }
    /**
     * Unloads the current bacnet instance and closes the underlying UDP socket.
     * @function bacnet.close
     * @example
     * const bacnet = require('ts-bacnet');
     * const client = new bacnet();
     *
     * client.close();
     */
    close() {
        this._transport.close();
    }
    /**
     * Helper function to take an array of enums and produce a bitstring suitable
     * for inclusion as a property.
     *
     * @example
     * [bacnet.enum.PropertyIdentifier.PROTOCOL_OBJECT_TYPES_SUPPORTED]: [
     *   {value: bacnet.createBitstring([
     *     bacnet.enum.ObjectTypesSupported.ANALOG_INPUT,
     *     bacnet.enum.ObjectTypesSupported.ANALOG_OUTPUT,
     *   ]),
     *   type: bacnet.enum.ApplicationTag.BIT_STRING},
     * ],
     */
    static createBitstring(items) {
        let offset = 0;
        let bytes = [];
        let bitsUsed = 0;
        while (items.length) {
            // Find any values between offset and offset+8, for the next byte
            let value = 0;
            items = items.filter((i) => {
                if (i >= offset + 8) {
                    return true;
                } // leave for future iteration
                value |= 1 << (i - offset);
                bitsUsed = Math.max(bitsUsed, i);
                return false; // remove from list
            });
            bytes.push(value);
            offset += 8;
        }
        bitsUsed++;
        return {
            value: bytes,
            bitsUsed,
        };
    }
}
exports.default = Client;