lib/read-device.js

"use strict";
//@ts-nocheck
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseValue = void 0;
/**
 * This script will discover all devices in the network and read out all
 * properties and deliver a JSON as device description
 *
 * If a deviceId is given as first parameter then only this device is discovered
 */
const process_1 = __importDefault(require("process"));
const __1 = require("..");
const EngineeringUnitHumanReadable_1 = require("./EngineeringUnitHumanReadable");
// Map the Property types to their enums/bitstrings
const PropertyIdentifierToEnumMap = {};
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.OBJECT_TYPE] =
    __1.enums.ObjectType;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.SEGMENTATION_SUPPORTED] =
    __1.enums.Segmentation;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.EVENT_STATE] =
    __1.enums.EventState;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.UNITS] =
    __1.enums.EngineeringUnits;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.RELIABILITY] =
    __1.enums.Reliability;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.NOTIFY_TYPE] =
    __1.enums.NotifyType;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.POLARITY] = __1.enums.Polarity;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.PROTOCOL_SERVICES_SUPPORTED] = __1.enums.ServicesSupported;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.PROTOCOL_OBJECT_TYPES_SUPPORTED] = __1.enums.ObjectTypesSupported;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.STATUS_FLAGS] =
    __1.enums.StatusFlags;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.LIMIT_ENABLE] =
    __1.enums.LimitEnable;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.EVENT_ENABLE] =
    __1.enums.EventTransitionBits;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.ACKED_TRANSITIONS] =
    __1.enums.EventTransitionBits;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.SYSTEM_STATUS] =
    __1.enums.DeviceStatus;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.SYSTEM_STATUS] =
    __1.enums.DeviceStatus;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.ACK_REQUIRED] =
    __1.enums.EventTransitionBits;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.LOGGING_TYPE] =
    __1.enums.LoggingType;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.FILE_ACCESS_METHOD] =
    __1.enums.FileAccessMethod;
PropertyIdentifierToEnumMap[__1.enums.PropertyIdentifier.NODE_TYPE] =
    __1.enums.NodeType;
// Sometimes the Map needs to be more specific
const ObjectTypeSpecificPropertyIdentifierToEnumMap = {};
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_INPUT] =
    {};
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_INPUT][__1.enums.PropertyIdentifier.PRESENT_VALUE] = __1.enums.BinaryPV;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_INPUT][__1.enums.PropertyIdentifier.MODE] = __1.enums.BinaryPV;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.ANALOG_INPUT] =
    {};
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.ANALOG_INPUT][__1.enums.PropertyIdentifier.PRESENT_VALUE] = __1.enums.BinaryPV; //????
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.ANALOG_OUTPUT] =
    {};
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.ANALOG_OUTPUT][__1.enums.PropertyIdentifier.PRESENT_VALUE] = __1.enums.BinaryPV; //????
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_OUTPUT] =
    {};
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_OUTPUT][__1.enums.PropertyIdentifier.PRESENT_VALUE] = __1.enums.BinaryPV;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_OUTPUT][__1.enums.PropertyIdentifier.RELINQUISH_DEFAULT] = __1.enums.BinaryPV;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_VALUE] =
    {};
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_VALUE][__1.enums.PropertyIdentifier.PRESENT_VALUE] = __1.enums.BinaryPV;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_VALUE][__1.enums.PropertyIdentifier.RELINQUISH_DEFAULT] = __1.enums.BinaryPV;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_LIGHTING_OUTPUT] = {};
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_LIGHTING_OUTPUT][__1.enums.PropertyIdentifier.PRESENT_VALUE] = __1.enums.BinaryLightingPV;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BITSTRING_VALUE] = {};
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.BINARY_VALUE][__1.enums.PropertyIdentifier.PRESENT_VALUE] = __1.enums.BinaryPV; // ???
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LIFE_SAFETY_POINT] = {};
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LIFE_SAFETY_POINT][__1.enums.PropertyIdentifier.PRESENT_VALUE] = __1.enums.LifeSafetyState;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LIFE_SAFETY_POINT][__1.enums.PropertyIdentifier.TRACKING_VALUE] = __1.enums.LifeSafetyState;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LIFE_SAFETY_POINT][__1.enums.PropertyIdentifier.MODE] = __1.enums.LifeSafetyMode;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LIFE_SAFETY_POINT][__1.enums.PropertyIdentifier.ACCEPTED_MODES] = __1.enums.LifeSafetyMode;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LIFE_SAFETY_POINT][__1.enums.PropertyIdentifier.SILENCED] = __1.enums.LifeSafetyState;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LIFE_SAFETY_POINT][__1.enums.PropertyIdentifier.OPERATION_EXPECTED] = __1.enums.LifeSafetyOperation;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LIFE_SAFETY_ZONE] = {};
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LIFE_SAFETY_ZONE][__1.enums.PropertyIdentifier.PRESENT_VALUE] = __1.enums.LifeSafetyState;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LIFE_SAFETY_ZONE][__1.enums.PropertyIdentifier.MODE] = __1.enums.LifeSafetyMode;
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LOAD_CONTROL] =
    {};
ObjectTypeSpecificPropertyIdentifierToEnumMap[__1.enums.ObjectType.LOAD_CONTROL][__1.enums.PropertyIdentifier.PRESENT_VALUE] = __1.enums.ShedState;
// For Objects we read out All properties if cli parameter --all is provided
const propSubSet = process_1.default.argv.includes("--all")
    ? Object.values(__1.enums.PropertyIdentifier)
    : [
        /* normally supported from all devices */
        __1.enums.PropertyIdentifier.OBJECT_IDENTIFIER,
        __1.enums.PropertyIdentifier.OBJECT_NAME,
        __1.enums.PropertyIdentifier.OBJECT_TYPE,
        __1.enums.PropertyIdentifier.PRESENT_VALUE,
        __1.enums.PropertyIdentifier.STATUS_FLAGS,
        __1.enums.PropertyIdentifier.EVENT_STATE,
        __1.enums.PropertyIdentifier.RELIABILITY,
        __1.enums.PropertyIdentifier.OUT_OF_SERVICE,
        __1.enums.PropertyIdentifier.UNITS,
        /* other properties */
        __1.enums.PropertyIdentifier.DESCRIPTION,
        __1.enums.PropertyIdentifier.SYSTEM_STATUS,
        __1.enums.PropertyIdentifier.VENDOR_NAME,
        __1.enums.PropertyIdentifier.VENDOR_IDENTIFIER,
        __1.enums.PropertyIdentifier.MODEL_NAME,
        __1.enums.PropertyIdentifier.FIRMWARE_REVISION,
        __1.enums.PropertyIdentifier.APPLICATION_SOFTWARE_VERSION,
        __1.enums.PropertyIdentifier.LOCATION,
        __1.enums.PropertyIdentifier.LOCAL_DATE,
        __1.enums.PropertyIdentifier.LOCAL_TIME,
        __1.enums.PropertyIdentifier.UTC_OFFSET,
        __1.enums.PropertyIdentifier.DAYLIGHT_SAVINGS_STATUS,
        __1.enums.PropertyIdentifier.PROTOCOL_VERSION,
        __1.enums.PropertyIdentifier.PROTOCOL_REVISION,
        __1.enums.PropertyIdentifier.PROTOCOL_SERVICES_SUPPORTED,
        __1.enums.PropertyIdentifier.PROTOCOL_OBJECT_TYPES_SUPPORTED,
        __1.enums.PropertyIdentifier.OBJECT_LIST,
        __1.enums.PropertyIdentifier.MAX_APDU_LENGTH_ACCEPTED,
        __1.enums.PropertyIdentifier.SEGMENTATION_SUPPORTED,
        __1.enums.PropertyIdentifier.APDU_TIMEOUT,
        __1.enums.PropertyIdentifier.NUMBER_OF_APDU_RETRIES,
        __1.enums.PropertyIdentifier.DEVICE_ADDRESS_BINDING,
        __1.enums.PropertyIdentifier.DATABASE_REVISION,
        __1.enums.PropertyIdentifier.MAX_INFO_FRAMES,
        __1.enums.PropertyIdentifier.MAX_MASTER,
        __1.enums.PropertyIdentifier.ACTIVE_COV_SUBSCRIPTIONS,
        __1.enums.PropertyIdentifier.ACTIVE_COV_MULTIPLE_SUBSCRIPTIONS,
    ];
const debug = process_1.default.argv.includes("--debug");
/**
 * Retrieve all properties manually because ReadPropertyMultiple is not available
 * @param address
 * @param objectId
 * @param callback
 * @param propList
 * @param result
 * @returns {*}
 */
function getAllPropertiesManually(address, objectId, callback, propList, result) {
    if (!propList) {
        propList = propSubSet.map((x) => x); // Clone the array
    }
    if (!result) {
        result = [];
    }
    if (!propList.length) {
        return callback({
            values: [
                {
                    objectId: objectId,
                    values: result,
                },
            ],
        });
    }
    const prop = propList.shift();
    // Read only object-list property
    bacnetClient.readProperty(address, objectId, prop, {}, (err, value) => {
        if (!err) {
            if (debug) {
                console.log("Handle value " + prop + ": ", JSON.stringify(value));
            }
            const objRes = value.property;
            objRes.value = value.values;
            result.push(objRes);
        }
        else {
            // console.log('Device do not contain object ' + enums.getEnumName(enums.PropertyIdentifier, prop));
        }
        getAllPropertiesManually(address, objectId, callback, propList, result);
    });
}
function readBit(buffer, i, bit) {
    return (buffer[i] >> bit) % 2;
}
/**
 * sets a bit in a buffer
 * @param buffer
 * @param i
 * @param bit
 * @param value
 */
function setBit(buffer, i, bit, value) {
    if (value === 0) {
        buffer[i] &= ~(1 << bit);
    }
    else {
        buffer[i] |= 1 << bit;
    }
}
function handleBitString(buffer, bitsUsed, usedEnum) {
    const res = [];
    for (let i = 0; i < bitsUsed; i++) {
        const bufferIndex = Math.floor(i / 8);
        if (readBit(buffer, bufferIndex, i % 8)) {
            res.push(__1.enums.getEnumName(usedEnum, i));
        }
    }
    return res;
}
/**
 * Parses a property value
 * @param address
 * @param parentType
 * @param value
 * @param supportsMultiple
 * @param callback
 */
function parseValue(address, objId, parentType, value, supportsMultiple = false, callback) {
    let resValue = null;
    let error = null;
    if (value &&
        value.type &&
        value.value !== null &&
        value.value !== undefined) {
        switch (value.type) {
            case __1.enums.ApplicationTag.NULL:
                // should be null already, but set again
                resValue = null;
                break;
            case __1.enums.ApplicationTag.BOOLEAN:
                // convert number to a real boolean
                resValue = !!value.value;
                break;
            case __1.enums.ApplicationTag.UNSIGNED_INTEGER:
            case __1.enums.ApplicationTag.SIGNED_INTEGER:
            case __1.enums.ApplicationTag.REAL:
            case __1.enums.ApplicationTag.DOUBLE:
            case __1.enums.ApplicationTag.CHARACTER_STRING:
                // datatype should be correct already
                resValue = value.value;
                break;
            case __1.enums.ApplicationTag.DATE:
            case __1.enums.ApplicationTag.TIME:
            case __1.enums.ApplicationTag.TIMESTAMP:
                // datatype should be Date too
                // Javascript do not have date/timestamp only
                resValue = value.value;
                break;
            case __1.enums.ApplicationTag.BIT_STRING:
                // handle bitstrings specific and more generic
                if (ObjectTypeSpecificPropertyIdentifierToEnumMap[parentType] &&
                    ObjectTypeSpecificPropertyIdentifierToEnumMap[parentType][objId]) {
                    resValue = handleBitString(value.value.value, value.value.bitsUsed, ObjectTypeSpecificPropertyIdentifierToEnumMap[parentType][objId]);
                }
                else if (PropertyIdentifierToEnumMap[objId]) {
                    resValue = handleBitString(value.value.value, value.value.bitsUsed, PropertyIdentifierToEnumMap[objId]);
                }
                else {
                    if (parentType !== __1.enums.ObjectType.BITSTRING_VALUE) {
                        console.log("Unknown value for BIT_STRING type for objId " +
                            __1.enums.getEnumName(__1.enums.PropertyIdentifier, objId) +
                            " and parent type " +
                            __1.enums.getEnumName(__1.enums.ObjectType, parentType));
                    }
                    resValue = value.value;
                }
                break;
            case __1.enums.ApplicationTag.ENUMERATED:
                // handle enumerations specific and more generic
                if (ObjectTypeSpecificPropertyIdentifierToEnumMap[parentType] &&
                    ObjectTypeSpecificPropertyIdentifierToEnumMap[parentType][objId]) {
                    resValue = __1.enums.getEnumName(ObjectTypeSpecificPropertyIdentifierToEnumMap[parentType][objId], value.value);
                }
                else if (PropertyIdentifierToEnumMap[objId]) {
                    resValue = __1.enums.getEnumName(PropertyIdentifierToEnumMap[objId], value.value);
                }
                else {
                    console.log("Unknown value for ENUMERATED type for objId " +
                        __1.enums.getEnumName(__1.enums.PropertyIdentifier, objId) +
                        " and parent type " +
                        __1.enums.getEnumName(__1.enums.ObjectType, parentType));
                    resValue = value.value;
                }
                break;
            case __1.enums.ApplicationTag.OBJECTIDENTIFIER:
                // Look up object identifiers
                // Some object identifiers should not be looked up because we end in loops else
                if (objId === __1.enums.PropertyIdentifier.OBJECT_IDENTIFIER ||
                    objId === __1.enums.PropertyIdentifier.STRUCTURED_OBJECT_LIST ||
                    objId === __1.enums.PropertyIdentifier.SUBORDINATE_LIST) {
                    resValue = value.value;
                }
                else if (supportsMultiple) {
                    const requestArray = [
                        {
                            objectId: value.value,
                            // meaning read all properties
                            properties: [{ id: 8 }],
                        },
                    ];
                    bacnetClient.readPropertyMultiple(address, requestArray, (err, resValue) => {
                        //console.log(JSON.stringify(value.value) + ': ' + JSON.stringify(resValue));
                        parseDeviceObject(address, resValue, value.value, true, callback);
                    });
                    return;
                }
                else {
                    getAllPropertiesManually(address, value.value, (result) => {
                        parseDeviceObject(address, result, value.value, false, callback);
                    });
                    return;
                }
                break;
            case __1.enums.ApplicationTag.OCTET_STRING:
                // It is kind of binary data??
                resValue = value.value;
                break;
            case __1.enums.ApplicationTag.ERROR:
                // lookup error class and code
                error = {
                    errorClass: __1.enums.getEnumName(__1.enums.ErrorClass, value.value.errorClass),
                    errorCode: __1.enums.getEnumName(__1.enums.ErrorCode, value.value.errorCode),
                };
                resValue = null;
                break;
            case __1.enums.ApplicationTag.OBJECT_PROPERTY_REFERENCE:
            case __1.enums.ApplicationTag.DEVICE_OBJECT_PROPERTY_REFERENCE:
            case __1.enums.ApplicationTag.DEVICE_OBJECT_REFERENCE:
            case __1.enums.ApplicationTag.READ_ACCESS_SPECIFICATION: //???
                resValue = value.value;
                break;
            case __1.enums.ApplicationTag.CONTEXT_SPECIFIC_DECODED:
                parseValue(address, parentType, value.value, supportsMultiple, callback);
                return;
            case __1.enums.ApplicationTag.READ_ACCESS_RESULT: // ????
                resValue = value.value;
                break;
            default:
                console.log("unknown type " + value.type + ": " + JSON.stringify(value));
                resValue = value;
        }
    }
    // format unit to human readable string
    if (objId === __1.enums.PropertyIdentifier.UNITS) {
        const unit = typeof resValue === "string"
            ? resValue
            : resValue && __1.enums.getEnumName(__1.enums.EngineeringUnits, resValue);
        const formattedName = unit && (0, EngineeringUnitHumanReadable_1.getEngineeringUnitHumanReadable)(unit);
        if (formattedName) {
            resValue = formattedName;
        }
    }
    if (objId === Bacnet.enum.PropertyIdentifier.UNITS) {
        const unit = typeof resValue === "string"
            ? resValue
            : resValue &&
                Bacnet.enum.getEnumName(Bacnet.enum.EngineeringUnits, resValue);
        const formattedName = unit && (0, EngineeringUnitHumanReadable_1.getEngineeringUnitHumanReadable)(unit);
        if (formattedName) {
            resValue = formattedName;
        }
    }
    if (resValue === null || resValue === undefined) {
        resValue = " ";
    }
    setImmediate(() => callback(resValue));
}
exports.parseValue = parseValue;
/**
 * Parse an object structure
 * @param address
 * @param obj
 * @param parent
 * @param supportsMultiple
 * @param callback
 */
function parseDeviceObject(address, obj, parent, supportsMultiple, callback) {
    if (debug) {
        console.log("START parseDeviceObject: " +
            JSON.stringify(parent) +
            " : " +
            JSON.stringify(obj));
    }
    if (!obj) {
        console.log("object not valid on parse device object");
        return;
    }
    if (!obj.values || !Array.isArray(obj.values)) {
        console.log("No device or invalid response");
        callback({ ERROR: "No device or invalid response" });
        return;
    }
    let cbCount = 0;
    let objDef = {};
    const finalize = () => {
        // Normalize and remove single item arrays
        Object.keys(objDef).forEach((devId) => {
            Object.keys(objDef[devId]).forEach((objId) => {
                if (objDef[devId][objId].length === 1) {
                    objDef[devId][objId] = objDef[devId][objId][0];
                }
            });
        });
        // If (standard case) only one device was in do not create sub structures)
        if (obj.values.length === 1) {
            objDef = objDef[obj.values[0].objectId.instance];
        }
        if (debug) {
            console.log("END parseDeviceObject: " +
                JSON.stringify(parent) +
                " : " +
                JSON.stringify(objDef));
        }
        callback(objDef);
    };
    obj.values.forEach((devBaseObj) => {
        if (!devBaseObj.objectId) {
            console.log("No device Id found in object data");
            return;
        }
        if (devBaseObj.objectId.type === undefined ||
            devBaseObj.objectId.instance === undefined) {
            console.log("No device type or instance found in object data");
            return;
        }
        if (!devBaseObj.values || !Array.isArray(devBaseObj.values)) {
            console.log("No device values response");
            return;
        }
        const deviceId = devBaseObj.objectId.instance;
        objDef[deviceId] = {};
        devBaseObj.values.forEach((devObj) => {
            let objId = __1.enums.getEnumName(__1.enums.PropertyIdentifier, devObj.id);
            if (devObj.index !== 4294967295) {
                objId += "-" + devObj.index;
            }
            if (debug) {
                console.log("Handle Object property:", deviceId, objId, devObj.value);
            }
            devObj.value.forEach((val) => {
                if (JSON.stringify(val.value) === JSON.stringify(parent)) {
                    // ignore parent object
                    objDef[deviceId][objId] = objDef[deviceId][objId] || [];
                    objDef[deviceId][objId].push(val.value);
                    return;
                }
                cbCount++;
                parseValue(address, parent.type, val, supportsMultiple, (parsedValue) => {
                    if (debug) {
                        console.log("RETURN parsedValue", deviceId, objId, devObj.value, parsedValue);
                    }
                    objDef[deviceId][objId] = objDef[deviceId][objId] || [];
                    objDef[deviceId][objId].push(parsedValue);
                    if (!--cbCount) {
                        finalize();
                    }
                });
            });
        });
    });
    if (cbCount === 0) {
        finalize();
    }
}
let objectsDone = 0;
/**
 * Print result info object
 * @param deviceId
 * @param obj
 */
function printResultObject(deviceId, obj) {
    objectsDone++;
    console.log(`Device ${deviceId} (${objectsDone}/${Object.keys(knownDevices).length}) read successfully ...`);
    console.log(JSON.stringify(obj));
    console.log();
    console.log();
    if (objectsDone === Object.keys(knownDevices).length) {
        setTimeout(() => {
            bacnetClient.close();
            console.log("closed transport " + Date.now());
        }, 1000);
    }
}
let limitToDevice = null;
if (process_1.default.argv.length === 3) {
    limitToDevice = parseInt(process_1.default.argv[2]);
    if (isNaN(limitToDevice)) {
        limitToDevice = null;
    }
}
/* // create instance of Bacnet
const bacnetClient = new Bacnet({ apduTimeout: 4000, interface: "0.0.0.0" });

// emitted for each new message
bacnetClient.on("message", (msg, rinfo) => {
  console.log(msg);
  if (rinfo) console.log(rinfo);
});

// emitted on errors
bacnetClient.on("error", (err) => {
  console.error(err);
  bacnetClient.close();
});

// emmitted when Bacnet server listens for incoming UDP packages
bacnetClient.on("listening", () => {
  console.log("sent whoIs " + Date.now());
  // discover devices once we are listening
  bacnetClient.whoIs();
});

const knownDevices = [];

// emitted when a new device is discovered in the network
bacnetClient.on("iAm", (device) => {
  // address object of discovered device,
  // just use in subsequent calls that are directed to this device
  const address = device.header.sender;

  //discovered device ID
  const deviceId = device.payload.deviceId;
  if (knownDevices.includes(deviceId)) return;
  if (limitToDevice !== null && limitToDevice !== deviceId) return;

  console.log("Found Device " + deviceId + " on " + JSON.stringify(address));
  knownDevices.push(deviceId);

  const propertyList = [];
  propSubSet.forEach((item) => {
    propertyList.push({ id: item });
  });

  const requestArray = [
    {
      objectId: { type: 8, instance: deviceId },
      properties: propertyList,
    },
  ];

  bacnetClient.readPropertyMultiple(address, requestArray, (err, value) => {
    if (err) {
      console.log(deviceId, "No ReadPropertyMultiple supported:", err.message);
      getAllPropertiesManually(
        address,
        { type: 8, instance: deviceId },
        (result) => {
          parseDeviceObject(
            address,
            result,
            { type: 8, instance: deviceId },
            false,
            (res) => printResultObject(deviceId, res)
          );
        }
      );
    } else {
      console.log(deviceId, "ReadPropertyMultiple supported ...");
      parseDeviceObject(
        address,
        value,
        { type: 8, instance: deviceId },
        true,
        (res) => printResultObject(deviceId, res)
      );
    }
  });
});
 */