UNPKG

ts-bacnet

Version:

The BACnet protocol library written in pure JavaScript.

566 lines (555 loc) 24.8 kB
"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) ); } }); }); */