UNPKG

@bitpoolos/edge-bacnet

Version:

A bacnet gateway for node-red

703 lines (641 loc) 35 kB
const bacnet = require('./resources/node-bacstack-ts/dist/index.js'); const pjson = require('./package.json'); const baEnum = bacnet.enum; const { Store_Config_Server, Read_Config_Sync_Server } = require('./common'); const { EventEmitter } = require("events"); /** * Class representing a BACnet Server. * * This class initializes a BACnet server with specified client, device ID, and Node-Red version. * It provides methods to set device name, add objects, retrieve objects, clear server points, clear server point, and get server points. * * Simulates a BACnet IP device on a regular IP network * * @constructor * @param {Object} client - The BACnet client object. * @param {number} deviceId - The ID of the device. * @param {string} nodeRedVersion - The version of Node-Red. */ class BacnetServer extends EventEmitter { constructor(client, deviceId, nodeRedVersion) { super(); let that = this; that.bacnetClient = client; // object identifier init that.objectIdNumber = {}; that.objectIdNumber[baEnum.ObjectType.ANALOG_VALUE] = 0; that.objectIdNumber[baEnum.ObjectType.CHARACTERSTRING_VALUE] = 0; that.objectIdNumber[baEnum.ObjectType.BINARY_VALUE] = 0; that.nodeRedVersion = nodeRedVersion; that.deviceId = deviceId; that.vendorId = 1401; that.objectList = [ { value: { type: baEnum.ObjectType.DEVICE, instance: that.deviceId }, type: 12 } ]; that.objectStore = { [baEnum.ObjectType.DEVICE]: { [baEnum.PropertyIdentifier.OBJECT_IDENTIFIER]: [{ value: { type: baEnum.ObjectType.DEVICE, instance: that.deviceId }, type: 12 }], [baEnum.PropertyIdentifier.OBJECT_LIST]: that.objectList, [baEnum.PropertyIdentifier.OBJECT_NAME]: [{ value: 'Bitpool Edge BACnet Gateway', type: 7 }], [baEnum.PropertyIdentifier.OBJECT_TYPE]: [{ value: 8, type: 9 }], [baEnum.PropertyIdentifier.DESCRIPTION]: [{ value: 'Bitpool Edge BACnet gateway', type: 7 }], [baEnum.PropertyIdentifier.SYSTEM_STATUS]: [{ value: 0, type: 9 }], [baEnum.PropertyIdentifier.VENDOR_NAME]: [{ value: "Bitpool", type: 7 }], [baEnum.PropertyIdentifier.VENDOR_IDENTIFIER]: [{ value: that.vendorId, type: 2 }], [baEnum.PropertyIdentifier.MODEL_NAME]: [{ value: "bitpool-edge", type: 7 }], [baEnum.PropertyIdentifier.FIRMWARE_REVISION]: [{ value: "Node-Red " + that.nodeRedVersion, type: 7 }], [baEnum.PropertyIdentifier.PROTOCOL_REVISION]: [{ value: 19, type: 2 }], [baEnum.PropertyIdentifier.PROTOCOL_VERSION]: [{ value: 0, type: 2 }], [baEnum.PropertyIdentifier.APPLICATION_SOFTWARE_VERSION]: [{ value: pjson.version, type: 7 }], [baEnum.PropertyIdentifier.PROTOCOL_SERVICES_SUPPORTED]: [{ value: { value: [0, 10, 0, 32, 32], bitsUsed: 40 }, type: 8 }], [baEnum.PropertyIdentifier.MAX_APDU_LENGTH_ACCEPTED]: [{ value: 1476, type: 2 }], [baEnum.PropertyIdentifier.SEGMENTATION_SUPPORTED]: [{ value: 0, type: 9 }], [baEnum.PropertyIdentifier.APDU_TIMEOUT]: [{ value: that.bacnetClient.config.apduTimeout, type: 2 }], [baEnum.PropertyIdentifier.NUMBER_OF_APDU_RETRIES]: [{ value: 3, type: 2 }], [baEnum.PropertyIdentifier.DEVICE_ADDRESS_BINDING]: [{ value: 0, type: 12 }], [baEnum.PropertyIdentifier.DATABASE_REVISION]: [{ value: 19, type: 2 }], [baEnum.PropertyIdentifier.PROPERTY_LIST]: [ { value: baEnum.PropertyIdentifier.OBJECT_IDENTIFIER, type: 9 }, { value: baEnum.PropertyIdentifier.OBJECT_LIST, type: 9 }, { value: baEnum.PropertyIdentifier.OBJECT_NAME, type: 9 }, { value: baEnum.PropertyIdentifier.OBJECT_TYPE, type: 9 }, { value: baEnum.PropertyIdentifier.DESCRIPTION, type: 9 }, { value: baEnum.PropertyIdentifier.SYSTEM_STATUS, type: 9 }, { value: baEnum.PropertyIdentifier.VENDOR_NAME, type: 9 }, { value: baEnum.PropertyIdentifier.VENDOR_IDENTIFIER, type: 9 }, { value: baEnum.PropertyIdentifier.MODEL_NAME, type: 9 }, { value: baEnum.PropertyIdentifier.FIRMWARE_REVISION, type: 9 }, { value: baEnum.PropertyIdentifier.PROTOCOL_REVISION, type: 9 }, { value: baEnum.PropertyIdentifier.PROTOCOL_VERSION, type: 9 }, { value: baEnum.PropertyIdentifier.APPLICATION_SOFTWARE_VERSION, type: 9 }, { value: baEnum.PropertyIdentifier.PROTOCOL_SERVICES_SUPPORTED, type: 9 }, { value: baEnum.PropertyIdentifier.PROTOCOL_OBJECT_TYPES_SUPPORTED, type: 9 }, { value: baEnum.PropertyIdentifier.MAX_APDU_LENGTH_ACCEPTED, type: 9 }, { value: baEnum.PropertyIdentifier.SEGMENTATION_SUPPORTED, type: 9 }, { value: baEnum.PropertyIdentifier.APDU_TIMEOUT, type: 9 }, { value: baEnum.PropertyIdentifier.NUMBER_OF_APDU_RETRIES, type: 9 }, { value: baEnum.PropertyIdentifier.DEVICE_ADDRESS_BINDING, type: 9 }, { value: baEnum.PropertyIdentifier.DATABASE_REVISION, type: 9 }, ], }, [baEnum.ObjectType.ANALOG_VALUE]: [], [baEnum.ObjectType.CHARACTERSTRING_VALUE]: [], [baEnum.ObjectType.BINARY_VALUE]: [] }; try { let cachedData = JSON.parse(Read_Config_Sync_Server()); if (typeof cachedData == "object") { if (cachedData.objectList) { that.objectList = cachedData.objectList; that.objectStore[baEnum.ObjectType.DEVICE][baEnum.PropertyIdentifier.OBJECT_LIST] = that.objectList; } if (cachedData.objectStore) { that.objectStore[baEnum.ObjectType.ANALOG_VALUE] = cachedData.objectStore[baEnum.ObjectType.ANALOG_VALUE]; that.objectStore[baEnum.ObjectType.CHARACTERSTRING_VALUE] = cachedData.objectStore[baEnum.ObjectType.CHARACTERSTRING_VALUE]; that.objectStore[baEnum.ObjectType.BINARY_VALUE] = cachedData.objectStore[baEnum.ObjectType.BINARY_VALUE]; } } } catch (error) { //do nothing } that.bacnetClient.client.on('whoIs', (device) => { that.bacnetClient.client.iAmResponse(that.deviceId, baEnum.Segmentation.SEGMENTED_BOTH, that.vendorId); that.lastWhoIsRecived = Date.now(); }); that.bacnetClient.client.on('readPropertyMultiple', (data) => { let senderAddress = data.address; let requestProps = data.request.properties; let responseObject = []; try { if (requestProps) { for (let i = 0; i < requestProps.length; i++) { let prop = requestProps[i].properties[0].id; let type = requestProps[i].objectId.type; let instance = requestProps[i].objectId.instance; let foundObject = that.getObjectMultiple(type, prop, instance, requestProps[i].properties); if (foundObject !== null && foundObject !== undefined && foundObject !== "undefined") { responseObject.push({ objectId: { type: type, instance: instance }, values: foundObject }); } if (i == requestProps.length - 1) { if (responseObject.length > 0) { that.bacnetClient.client.readPropertyMultipleResponse(senderAddress, data.invokeId, responseObject); } else { that.bacnetClient.client.errorResponse( data.address, baEnum.ConfirmedServiceChoice.READ_PROPERTY_MULTIPLE, data.invokeId, baEnum.ErrorClass.PROPERTY, baEnum.ErrorCode.UNKNOWN_PROPERTY ); } } } } } catch (e) { that.bacnetClient.client.errorResponse( data.address, baEnum.ConfirmedServiceChoice.READ_PROPERTY_MULTIPLE, data.invokeId, baEnum.ErrorClass.PROPERTY, baEnum.ErrorCode.UNKNOWN_PROPERTY ); } }); that.bacnetClient.client.on('readProperty', (data) => { try { let objectId = data.request.objectId.type; let objectInstance = data.request.objectId.instance; let propId = data.request.property.id.toString(); let responseObj = that.getObject(objectId, propId, objectInstance); if (propId == baEnum.PropertyIdentifier.OBJECT_LIST) { if (data.request.property.index !== 0xFFFFFFFF) { responseObj = responseObj[data.request.property.index]; } } if (responseObj !== null && responseObj !== undefined && typeof responseObj !== "undefined") { that.bacnetClient.client.readPropertyResponse(data.address, data.invokeId, data.request.objectId, data.request.property, responseObj); } else { that.bacnetClient.client.errorResponse( data.address, baEnum.ConfirmedServiceChoice.READ_PROPERTY, data.invokeId, baEnum.ErrorClass.PROPERTY, baEnum.ErrorCode.UNKNOWN_PROPERTY ); } } catch (e) { //console.log("Local BACnet device readProperty error: ", e); } }); that.bacnetClient.client.on('writeProperty', (data) => { let objectId = data.request.objectId.type; let objectInstance = data.request.objectId.instance; let propId = data.request.value.property.id.toString(); let newValue = data.request.value.value[0].value; if (!that.modifyObject(objectId, propId, objectInstance, newValue)) { that.bacnetClient.errorResponse(data.address, data.service, data.invokeId, bacnet.enum.ErrorClass.OBJECT, bacnet.enum.ErrorCode.UNKNOWN_OBJECT); } that.getServerPoints(objectId, objectInstance).then(function (result) { that.bacnetClient.client.simpleAckResponse(data.address, data.service, data.invokeId); that.emit("writeProperty", result[0].name, newValue); }).catch(function (error) { that.bacnetClient.errorResponse(data.address, data.service, data.invokeId, bacnet.enum.ErrorClass.OBJECT, bacnet.enum.ErrorCode.UNKNOWN_OBJECT); that.logOut("Error getting server points: ", error); }); }); that.bacnetClient.client.on('createObject', (data) => { var defaultValue; switch (data.request.values[0].value[0].type) { case baEnum.ObjectType.CHARACTERSTRING_VALUE: defaultValue = ""; break; case baEnum.ObjectType.BINARY_VALUE: defaultValue = 0; break; case baEnum.ObjectType.ANALOG_VALUE: defaultValue = false; break; default: defaultValue = 0; break; } that.addObject(data.request.values[0].value[0].value, defaultValue); that.bacnetClient.client.simpleAckResponse(data.address, data.service, data.invokeId); }); that.bacnetClient.client.on('deleteObject', (data) => { var type; switch (data.request.objectType) { case baEnum.ObjectType.CHARACTERSTRING_VALUE: type = 'SV'; break; case baEnum.ObjectType.BINARY_VALUE: type = 'BV'; break; case baEnum.ObjectType.ANALOG_VALUE: type = 'AV'; break; default: type = 'AV'; break; } var req = { body: { type: type, instance: data.request.instance } }; that.clearServerPoint(req).then(function (result) { that.bacnetClient.client.simpleAckResponse(data.address, data.service, data.invokeId); }).catch(function (error) { console.log(error) }); }); //do initial iAm broadcast when BACnet server starts that.bacnetClient.client.iAmResponse(that.deviceId, baEnum.Segmentation.SEGMENTED_BOTH, that.vendorId); that.setMaxListeners(2); } /** * Set the name of the device. * * @param {string} nodeName - The new name for the device. */ setDeviceName(nodeName) { let that = this; if (typeof nodeName == "string" && nodeName !== "") { that.objectStore[baEnum.ObjectType.DEVICE][baEnum.PropertyIdentifier.OBJECT_NAME][0].value = nodeName; } } /** * Adds a new object to the BacnetServer's object store based on the provided name and payload. * * @param {string} name - The name of the object to be added. * @param {number|boolean|string|object} payload - The payload of the object to be added. * @returns {void} */ addObject(name, payload) { let that = this; let objectType = that.getBacnetObjectType(payload.value ?? payload); if (name && objectType) { let instanceNumber; if (name.includes('|')) { // split name, assign last part to instanceNumber and the rest to name let nameParts = name.split('|'); instanceNumber = nameParts.pop(); name = nameParts.join('|'); } let formattedName = name.replaceAll('.', '_').replaceAll('/', '_'); const getCommonProperties = (type, valueType) => ({ [baEnum.PropertyIdentifier.OBJECT_NAME]: [{ value: formattedName, type: 7 }], [baEnum.PropertyIdentifier.OBJECT_TYPE]: [{ value: type, type: 9 }], [baEnum.PropertyIdentifier.DESCRIPTION]: [{ value: payload.description ?? '', type: 7 }], [baEnum.PropertyIdentifier.OBJECT_IDENTIFIER]: [{ value: { type: type, instance: that.getObjectIdentifier(type, instanceNumber) }, type: 12 }], [baEnum.PropertyIdentifier.PRESENT_VALUE]: [{ value: payload.value ?? payload, type: valueType }], [baEnum.PropertyIdentifier.STATUS_FLAGS]: [{ value: payload.statusFlags ?? 0, type: 8 }], [baEnum.PropertyIdentifier.EVENT_STATE]: [{ value: payload.eventState ?? 0, type: 9 }], [baEnum.PropertyIdentifier.OUT_OF_SERVICE]: [{ value: payload.outOfService ?? 0, type: 9 }], }); const addObjectToStore = (type, valueType, extraProperties = {}) => { let foundIndex = that.objectStore[type].findIndex(ele => ele[baEnum.PropertyIdentifier.OBJECT_NAME][0].value == formattedName); if (foundIndex == -1) { let newObject = { ...getCommonProperties(type, valueType), ...extraProperties }; that.objectStore[type].push(newObject); that.objectList.push({ value: { type: type, instance: newObject[baEnum.PropertyIdentifier.OBJECT_IDENTIFIER][0].value.instance }, type: 12 }); that.objectStore[baEnum.ObjectType.DEVICE][baEnum.PropertyIdentifier.OBJECT_LIST] = that.objectList; } else { let foundObject = that.objectStore[type][foundIndex]; foundObject[baEnum.PropertyIdentifier.PRESENT_VALUE][0].value = payload.value ?? payload; that.objectStore[baEnum.ObjectType.DEVICE][baEnum.PropertyIdentifier.OBJECT_LIST] = that.objectList; } }; if (objectType === "number") { addObjectToStore(baEnum.ObjectType.ANALOG_VALUE, 4, { [baEnum.PropertyIdentifier.UNITS]: [{ value: payload.units ?? 95, type: 9 }], [baEnum.PropertyIdentifier.MAX_PRES_VALUE]: [{ value: payload.value ?? payload, type: 4 }], [baEnum.PropertyIdentifier.MIN_PRES_VALUE]: [{ value: payload.value ?? payload, type: 4 }], [baEnum.PropertyIdentifier.RESOLUTION]: [{ value: payload.resolution ?? 0, type: 4 }], [baEnum.PropertyIdentifier.PRIORITY_ARRAY]: [{ value: payload.priorityArray ?? 0, type: 9 }], [baEnum.PropertyIdentifier.PROPERTY_LIST]: [ { value: baEnum.PropertyIdentifier.OBJECT_NAME, type: 9 }, { value: baEnum.PropertyIdentifier.OBJECT_TYPE, type: 9 }, { value: baEnum.PropertyIdentifier.DESCRIPTION, type: 9 }, { value: baEnum.PropertyIdentifier.OBJECT_IDENTIFIER, type: 9 }, { value: baEnum.PropertyIdentifier.PRESENT_VALUE, type: 9 }, { value: baEnum.PropertyIdentifier.STATUS_FLAGS, type: 9 }, { value: baEnum.PropertyIdentifier.EVENT_STATE, type: 9 }, { value: baEnum.PropertyIdentifier.OUT_OF_SERVICE, type: 9 }, { value: baEnum.PropertyIdentifier.UNITS, type: 9 }, { value: baEnum.PropertyIdentifier.PRIORITY_ARRAY, type: 9 }, { value: baEnum.PropertyIdentifier.MAX_PRES_VALUE, type: 9 }, { value: baEnum.PropertyIdentifier.MIN_PRES_VALUE, type: 9 }, { value: baEnum.PropertyIdentifier.RESOLUTION, type: 9 }, ] }); } else if (objectType === "boolean") { addObjectToStore(baEnum.ObjectType.BINARY_VALUE, 1, { [baEnum.PropertyIdentifier.ACTIVE_TEXT]: [{ value: 'ACTIVE', type: 7 }], [baEnum.PropertyIdentifier.INACTIVE_TEXT]: [{ value: 'INACTIVE', type: 7 }] }); } else if (objectType === "string") { addObjectToStore(baEnum.ObjectType.CHARACTERSTRING_VALUE, 7, { [baEnum.PropertyIdentifier.UNITS]: [{ value: payload.units ?? 95, type: 9 }] }); } } Store_Config_Server(JSON.stringify({ objectList: that.objectList, objectStore: that.objectStore })); } /** * Retrieves a specific property of an object based on the object ID, property ID, and instance number. * * @param {number} objectId - The ID of the object type. * @param {number} propId - The ID of the property to retrieve. * @param {number} instance - The instance number of the object. * @returns {any} The requested property value if found, otherwise null. */ getObject(objectId, propId, instance) { let that = this; let objectGroup = that.objectStore[objectId]; if (Array.isArray(objectGroup)) { for (let i = 0; i < objectGroup.length; i++) { let object = objectGroup[i]; if (object[baEnum.PropertyIdentifier.OBJECT_IDENTIFIER][0].value.instance == instance) { let requestedProperty = object[propId]; if (requestedProperty !== null && requestedProperty !== undefined && typeof requestedProperty !== "undefined") { return requestedProperty; } } } } else { return objectGroup[propId]; } return null; } /** * Retrieves a specific property of an object based on the object ID, property ID, and instance number and modify his value. * * @param {number} objectId - The ID of the object type. * @param {number} propId - The ID of the property to retrieve. * @param {number} instance - The instance number of the object. * @param {number} newValue - The the new value for update. * @returns {any} The requested property value if found, otherwise null. */ modifyObject(objectId, propId, instance, newValue) { //TODO factorise with getObject let that = this; let objectGroup = that.objectStore[objectId]; if (Array.isArray(objectGroup)) { for (let i = 0; i < objectGroup.length; i++) { let object = objectGroup[i]; if (object[baEnum.PropertyIdentifier.OBJECT_IDENTIFIER][0].value.instance == instance) { let requestedProperty = object[propId]; if (requestedProperty !== null && requestedProperty !== undefined && typeof requestedProperty !== "undefined") { that.objectStore[objectId][i][propId][0].value = newValue; return requestedProperty; } } } } else { return objectGroup[propId]; } return null; } /** * Retrieves the properties of a specific object instance from the objectStore based on the provided parameters. * * @param {number} objectId - The type of the object to retrieve. * @param {number} propId - The property identifier to retrieve. * @param {number} instance - The instance number of the object to retrieve. * @param {Array} properties - An array of additional properties to retrieve along with the main property. * @returns {Array|null} - An array of properties with values for the specified object instance, or null if not found. */ getObjectMultiple(objectId, propId, instance, properties) { let that = this; let objectGroup = that.objectStore[objectId]; try { if (Array.isArray(objectGroup)) { for (let i = 0; i < objectGroup.length; i++) { let object = objectGroup[i]; if (object[baEnum.PropertyIdentifier.OBJECT_IDENTIFIER][0].value.instance == instance) { if (propId == baEnum.PropertyIdentifier.ALL) { let propList = []; let keys = Object.keys(object); keys.forEach(function (key) { propList.push({ property: { id: key, index: 0xFFFFFFFF }, value: object[key] }); }); return propList; } else if (properties && properties.length > 1) { let propList = []; properties.forEach(function (p) { if (object[p.id]) { propList.push({ property: { id: p.id, index: 0xFFFFFFFF }, value: object[p.id] }); } }); return propList; } else { return [{ property: { id: propId, index: 0xFFFFFFFF }, value: object[propId] }]; } } } } else { if (propId == baEnum.PropertyIdentifier.ALL) { let propList = []; let keys = Object.keys(objectGroup); keys.forEach(function (key) { propList.push({ property: { id: key, index: 0xFFFFFFFF }, value: objectGroup[key] }); }); return propList; } else if (properties && properties.length > 1) { let propList = []; properties.forEach(function (p) { if (objectGroup[p.id]) { propList.push({ property: { id: p.id, index: 0xFFFFFFFF }, value: objectGroup[p.id] }); } }); return propList; } else { return [{ property: { id: propId, index: 0xFFFFFFFF }, value: objectGroup[propId] }]; } } } catch (e) { //do nothing } return null; } /** * Clear all server points by resetting the object lists and object store. * This method resets the object list for the device, clears the character string values, * and clears the analog values. It also resets the object id numbers for analog, character string, and binary values. * Finally, it stores the updated object list and object store in the server configuration. */ clearServerPoints() { let that = this; that.objectList = [ { value: { type: baEnum.ObjectType.DEVICE, instance: that.deviceId }, type: 12 } ]; that.objectStore[baEnum.ObjectType.DEVICE][baEnum.PropertyIdentifier.OBJECT_LIST] = that.objectList; that.objectStore[baEnum.ObjectType.CHARACTERSTRING_VALUE] = []; that.objectStore[baEnum.ObjectType.ANALOG_VALUE] = []; that.objectIdNumber = {}; that.objectIdNumber[baEnum.ObjectType.ANALOG_VALUE] = 0; that.objectIdNumber[baEnum.ObjectType.CHARACTERSTRING_VALUE] = 0; that.objectIdNumber[baEnum.ObjectType.BINARY_VALUE] = 0; Store_Config_Server(JSON.stringify({ objectList: that.objectList, objectStore: that.objectStore })); } /** * Removes a server point from the objectStore and objectList based on the provided JSON data. * * @param {Object} json - The JSON data containing information about the server point to be removed. * @param {string} json.body.type - The type of the server point ('SV' for CharacterString, 'BV' for BinaryValue, default is AnalogValue). * @param {number} json.body.instance - The instance number of the server point to be removed. * @returns {Promise<boolean>} A Promise that resolves to true if the server point is successfully removed, otherwise rejects with an error. */ clearServerPoint(json) { let that = this; return new Promise(async function (resolve, reject) { try { let type; switch (json.body.type) { case 'SV': type = baEnum.ObjectType.CHARACTERSTRING_VALUE; break; case 'BV': type = baEnum.ObjectType.BINARY_VALUE; break; default: type = baEnum.ObjectType.ANALOG_VALUE; break; } // remove object from objectStore let objectGroup = that.objectStore[type]; if (Array.isArray(objectGroup)) { for (let i = 0; i < objectGroup.length; i++) { let object = objectGroup[i]; if (object[baEnum.PropertyIdentifier.OBJECT_IDENTIFIER][0].value.instance == json.body.instance) { that.objectStore[type].splice(i, 1); break; } } } else { delete that.objectStore[type]; } // remove object from objectList let objectIndex = that.objectList.findIndex(ele => ele.value.instance == json.body.instance && ele.value.type == type); if (objectIndex !== -1) { that.objectList.splice(objectIndex, 1); } // update objectList in device object that.objectStore[baEnum.ObjectType.DEVICE][baEnum.PropertyIdentifier.OBJECT_LIST] = that.objectList; Store_Config_Server(JSON.stringify({ objectList: that.objectList, objectStore: that.objectStore })); resolve(true); } catch (e) { reject(e); } }); } /** * Retrieves all server points from the BacnetServer objectStore. * Points include Analog Value, Character String Value, and Binary Value objects. * Each point is represented as an object with properties: * - name: The name of the object. * - type: The type of the object (AV for Analog Value, SV for Character String Value, BV for Binary Value). * - instance: The instance number of the object. * * @returns {Promise<Array>} A promise that resolves with an array of points sorted by instance number. * @throws {Error} If an error occurs during the retrieval process. */ getServerPoints(typeFilter = null, instanceFilter = null) { let that = this; let points = []; return new Promise(async function (resolve, reject) { try { // iterate analog value objects if (that.objectStore[baEnum.ObjectType.ANALOG_VALUE] && that.objectStore[baEnum.ObjectType.ANALOG_VALUE].length > 0) { that.objectStore[baEnum.ObjectType.ANALOG_VALUE].forEach((point) => { let instance = point[baEnum.PropertyIdentifier.OBJECT_IDENTIFIER][0].value.instance; let objectName = point[baEnum.PropertyIdentifier.OBJECT_NAME][0].value; if (typeFilter === null && instanceFilter === null) { points.push({ name: objectName, type: "AV", instance }); } else { if ((typeFilter === baEnum.ObjectType.ANALOG_VALUE) && (instanceFilter === instance)) { points.push({ name: objectName, type: "AV", instance }); } } }); } // iterate character string value objects if (that.objectStore[baEnum.ObjectType.CHARACTERSTRING_VALUE] && that.objectStore[baEnum.ObjectType.CHARACTERSTRING_VALUE].length > 0) { that.objectStore[baEnum.ObjectType.CHARACTERSTRING_VALUE].forEach((point) => { let instance = point[baEnum.PropertyIdentifier.OBJECT_IDENTIFIER][0].value.instance; let objectName = point[baEnum.PropertyIdentifier.OBJECT_NAME][0].value; if (typeFilter === null && instanceFilter === null) { points.push({ name: objectName, type: "SV", instance }); } else { if ((typeFilter === baEnum.ObjectType.CHARACTERSTRING_VALUE) && (instanceFilter === instance)) { points.push({ name: objectName, type: "SV", instance }); } } }); } // iterate binary value objects if (that.objectStore[baEnum.ObjectType.BINARY_VALUE] && that.objectStore[baEnum.ObjectType.BINARY_VALUE].length > 0) { that.objectStore[baEnum.ObjectType.BINARY_VALUE].forEach((point) => { let instance = point[baEnum.PropertyIdentifier.OBJECT_IDENTIFIER][0].value.instance; let objectName = point[baEnum.PropertyIdentifier.OBJECT_NAME][0].value; if (typeFilter === null && instanceFilter === null) { points.push({ name: objectName, type: "BV", instance }); } else { if ((typeFilter === baEnum.ObjectType.BINARY_VALUE) && (instanceFilter === instance)) { points.push({ name: objectName, type: "BV", instance }); } } }); } resolve(points.sort((a, b) => (a.instance > b.instance) ? 1 : -1)); } catch (e) { reject(e); } }); } /** * Determines the BACnet object type based on the provided value. * * @param {any} value - The value to determine the BACnet object type for. * @returns {string|null} The BACnet object type as a string ('string', 'number', 'boolean') or null if the type is not recognized. */ getBacnetObjectType(value) { let type = typeof value; switch (type) { case "string": return "string" case "number": return "number" case "boolean": return "boolean" case "object": return "object" default: return null } } /** * Returns the object identifier for the given type and instance number. * * @param {string} type - The type of the object. * @param {number} instanceNumber - The instance number of the object. * @returns {number} The object identifier. */ getObjectIdentifier(type, instanceNumber) { let that = this; // manual instance numbering if (instanceNumber) { that.objectIdNumber[type] = instanceNumber + 1; return instanceNumber; } // auto instance numbering let objectId = that.objectIdNumber[type]; that.objectIdNumber[type]++; return objectId; } } module.exports = { BacnetServer };