UNPKG

@halsystems/red-bacnet

Version:
186 lines (160 loc) 7.38 kB
'use strict' require('./_alias.js'); const bacnet = require('@root/ext/node-bacstack/dist/index.js') const baEnum = bacnet.enum; const { BaseJob } = require('@root/common/job/core.js'); const { bacnetDeviceSchema } = require('@root/common/schema.js'); const { EVENT_I_AM, EVENT_UPDATE_STATUS, EVENT_ERROR, EVENT_OUTPUT } = require('@root/common/core/constant.js'); const { errMsg, getErrMsg } = require('@root/common/func.js'); const { ERR_GENERIC, ERR_SCHEMA_VALIDATION } = require('@root/common/core/constant.js'); // ---------------------------------- type def ---------------------------------- /** * @typedef {import('events').EventEmitter} EventEmitter * @typedef {import('@root/ext/node-bacstack/dist/index.js').Client} BacnetClient */ // ---------------------------------- export ---------------------------------- module.exports = { DiscoverDeviceJob: class DiscoverDeviceJob extends BaseJob { knownDevices = new Set(); discoverList = []; discoveredDevices = []; /** * Construct a DiscoverDeviceJob. * @param {BacnetClient} client - Bacnet client to use for the discovery. * @param {EventEmitter} eventEmitter - Status and error event emitter. * @param {number} network - Network to discover devices on, eg: 65535 / 1 / 1001 * @param {number} lowLimit - Low limit of the network range. * @param {number} highLimit - High limit of the network range. * @param {number} whoIsTimeout - Who is request timeout in milliseconds. */ constructor( client, eventEmitter, network, lowLimit, highLimit, whoIsTimeout, name = 'discover device' ) { super(); this.client = client this.eventEmitter = eventEmitter this.network = network this.lowLimit = lowLimit this.highLimit = highLimit this.whoIsTimeout = whoIsTimeout this.name = name } async onStart() { this.client.on(EVENT_I_AM, this.#iAmHandler.bind(this)) } async onStop() { this.client.removeListener(EVENT_I_AM, this.#iAmHandler.bind(this)) } async execute() { // discover device job will be reuse this.knownDevices.clear() this.discoverList = []; this.discoveredDevices = []; this.#updateProgress(0) return new Promise((resolve, reject) => { try { this.client.whoIs({ net: this.network, lowLimit: this.lowLimit, highLimit: this.highLimit }) setTimeout(() => { this.#updateProgress(10) this.#readDevices().then(() => { let validatedDevices = []; for (let i = 0; i < this.discoveredDevices.length; i++) { const device = this.discoveredDevices[i]; const { error, value: result } = bacnetDeviceSchema.validate(device); if (error) { this.eventEmitter.emit(EVENT_ERROR, errMsg( this.name, `BACnet device schema validation error (${device.deviceId})`, error )) this.eventEmitter.emit(EVENT_ERROR, errMsg( this.name, `${ERR_SCHEMA_VALIDATION} (${device.deviceId})`, error )) continue; } validatedDevices.push(result); } this.eventEmitter.emit(EVENT_OUTPUT, validatedDevices); this.#updateProgress(100) resolve(); }).catch(error => { this.#updateProgress(100) reject(error); }); }, this.whoIsTimeout); } catch (err) { this.eventEmitter.emit(EVENT_ERROR, errMsg(this.name, ERR_GENERIC, getErrMsg(err))) resolve(); } }); } /** * Reads device names from the list of discovered devices. * @returns {Promise<void>} */ async #readDevices() { const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); const readDeviceName = (d, addressSet) => { return new Promise((resolve) => { this.client.readProperty(addressSet, { type: baEnum.ObjectType.DEVICE, instance: d.deviceId }, baEnum.PropertyIdentifier.OBJECT_NAME, { maxApdu: d.maxApdu }, (err, value) => { const deviceInfo = { deviceId: d.deviceId, network: d.net, ipAddress: d.address, macAddress: d.adr, segmentation: d.segmentation, maxApdu: d.maxApdu, vendorId: d.vendorId }; if (!err) { deviceInfo.deviceName = value?.values?.[0].value; } resolve(deviceInfo); } ); }); }; for (let i = 0; i < this.discoverList.length; i++) { const d = this.discoverList[i]; let addressSet = d.address; if (d.adr !== undefined && d.net !== undefined) { addressSet = { ip: d.address, adr: d.adr, net: d.net }; } const deviceInfo = await readDeviceName(d, addressSet); this.discoveredDevices.push(deviceInfo); // throttle to avoid bursts await delay(75); // tweak between 50–150ms this.#updateProgress( Math.min(90, Math.round((80 / this.discoverList.length) * (i + 1) + 10)) ); } this.#updateProgress(90) } #iAmHandler(device) { const deviceId = device.deviceId const address = device.address const segmentation = device.segmentation const maxApdu = device.maxApdu const vendorId = device.vendorId const adr = device.adr const net = device.net if (this.knownDevices.has(deviceId)) return; this.discoverList.push({ deviceId: deviceId, address: address, segmentation: segmentation, maxApdu: maxApdu, vendorId: vendorId, adr: adr, net: net }); this.knownDevices.add(deviceId); } #updateProgress(progress) { this.eventEmitter.emit(EVENT_UPDATE_STATUS, progress); } }, }