UNPKG

@pmouli/isy-matter-server

Version:

Service to expose an ISY device as a Matter Border router

907 lines (899 loc) 38.2 kB
import axios from 'axios'; import { EventEmitter } from 'events'; import { format, Logger, loggers } from 'winston'; import WebSocket from 'ws'; import { Parser } from 'xml2js'; import { parseBooleans, parseNumbers } from 'xml2js/lib/processors.js'; import { DeviceFactory } from './Devices/DeviceFactory.js'; import { ELKAlarmPanelDevice } from './Devices/Elk/ElkAlarmPanelDevice.js'; import { EventType } from './Events/EventType.js'; import { ISYVariable } from './ISYVariable.js'; import { ISYScene } from './Scenes/ISYScene.js'; import { VariableType } from './VariableType.js'; import * as Utils from './Utils.js'; import { XMLParser } from 'fast-xml-parser'; import path from 'path'; import { UnitOfMeasure } from './Definitions/index.js'; import { GenericNode } from './Devices/GenericNode.js'; import { ISYError } from './ISYError.js'; import { isPrimitive, writeDebugFile } from './Utils.js'; class ISYInitializationError extends ISYError { step; constructor(messageOrError, step) { super(messageOrError); this.name = 'ISYInitializationError'; this.step = step; } } const defaultParserOptions = { explicitArray: false, mergeAttrs: true, attrValueProcessors: [parseNumbers, parseBooleans], valueProcessors: [parseNumbers, parseBooleans], tagNameProcessors: [(tagName) => (tagName === 'st' || tagName === 'cmd' || tagName === 'nodeDef' ? '' : tagName)] }; const defaultXMLParserOptions = { parseAttributeValue: true, allowBooleanAttributes: true, attributeNamePrefix: '', attributesGroupName: false, ignoreAttributes: false, // updateTag(tagName, jPath, attrs) { // //if(tagName === 'st' || tagName === 'cmd' || tagName === 'nodeDef') // // return false; // //return tagName; // }, textNodeName: '_', commentPropName: '$comment', cdataPropName: '$cdata', ignoreDeclaration: true, tagValueProcessor: (tagName, tagValue, jPath, hasAttributes, isLeafNode) => { if (tagValue === '') return null; return tagValue; }, isArray(tagName, jPath, isLeafNode, isAttribute) { if (tagName === 'property') return true; return false; } }; axios.defaults.transitional.forcedJSONParsing = false; const parser = new Parser(defaultParserOptions); export let Controls = {}; export class ISY extends EventEmitter { // #region Properties (30) credentials; devices = new Map(); deviceMap = new Map(); displayNameFormat; elkEnabled; enableWebSocket; folderMap = new Map(); host; nodeMap = new Map(); port; protocol; sceneList = new Map(); storagePath; variableList = new Map(); wsprotocol = 'ws'; zoneMap = new Map(); static instance; configInfo; elkAlarmPanel; guardianTimer; id; lastActivity; logger; model; nodesLoaded = false; productId = 5226; productName = 'eisy'; firmwareVersion; vendorName = 'Universal Devices, Inc.'; webSocket; apiVersion; socketPath; xmlParser; axiosOptions; guardianInterval; static async create(config, logger, storagePath) { if (!ISY.instance) { ISY.instance = new ISY(config, logger, storagePath); await ISY.instance.initialize(); } return ISY.instance; } // #endregion Properties (30) // #region Constructors (1) constructor(config, logger = new Logger(), storagePath) { super(); this.enableWebSocket = config.enableWebSocket ?? true; this.storagePath = storagePath ?? './'; this.displayNameFormat = config.displayNameFormat ?? '${location ?? folder} ${spokenName ?? name}'; this.host = config.host; this.port = config.port; this.guardianInterval = config.webSocketOptions?.guardianInterval ?? 60000; this.credentials = { username: config.username, password: config.password }; this.protocol = config.protocol; this.wsprotocol = config.protocol === 'https' ? 'wss' : 'ws'; this.socketPath = config.socketPath; this.axiosOptions = { validateStatus: (status) => status >= 200 && status < 300 }; if (this.socketPath) { this.axiosOptions.socketPath = this.socketPath; this.axiosOptions.baseURL = 'http://dummy/'; //Workaround for Bug with Axios 1.7.5+ } else { (this.axiosOptions.baseURL = `${this.protocol}://${this.host}:${this.port}`), (this.axiosOptions.auth = { username: this.credentials.username, password: this.credentials.password }); } this.webSocketOptions = { origin: 'com.universal-devices.websockets.isy' }; if (this.socketPath) { this.webSocketOptions.socketPath = this.socketPath; } else { this.webSocketOptions.auth = `${this.credentials.username}:${this.credentials.password}`; } this.xmlParser = new XMLParser({ ...defaultXMLParserOptions }); //this.elkEnabled = config.elkEnabled ?? false; this.nodesLoaded = false; var fopts = format((info) => { info.message = JSON.stringify(info.message); return info; })({ label: 'ISY' }); this.logger = loggers.add('isy', { transports: logger.transports, levels: logger.levels, format: format.label({ label: 'ISY' }) }); this.guardianTimer = null; if (this.elkEnabled) { this.elkAlarmPanel = new ELKAlarmPanelDevice(this, 1); } ISY.instance = this; } [Symbol.dispose]() { if (ISY.instance === this) { ISY.instance = null; } this.close(); this.logger.info('ISY instance disposed.'); } [Symbol.asyncDispose]() { return new Promise((resolve) => { try { this.webSocket?.close(); this.logger.info('WebSocket connection closed during async dispose.'); } catch (e) { this.logger.error(`Error during async dispose: ${e.message}`); } finally { if (ISY.instance === this) { ISY.instance = null; } this.logger.info('ISY instance asynchronously disposed.'); resolve(); } }); } /*[Symbol.asyncDispose](): PromiseLike<void> { this.webSocket?.close(); return Promise.resolve(); }*/ // #endregion Constructors (1) // #region Public Getters And Setters (2) get address() { return `${this.host}:${this.port}`; } get isDebugEnabled() { return this.logger?.isDebugEnabled(); } // #endregion Public Getters And Setters (2) // #region Public Methods (24) /*[Symbol.dispose](): void { if (ISY.instance === this) { ISY.instance = null; } this.close(); }*/ /*public override emit(event: 'InitializeCompleted' | 'NodeAdded' | 'NodeRemoved' | 'NodeChanged', node?: ISYNode<any, any, any, any>): boolean { return super.emit(event, node); }*/ getDevice(address, parentsOnly = false) { let s = this.devices.get(address); if (!parentsOnly) { if (s === null) { s = this.devices[`${address.substr(0, address.length - 1)} 1`]; } } else { while (s.parentAddress !== undefined && s.parentAddress !== s.address && s.parentAddress !== null) { s = this.devices[s.parentAddress]; } } return s; } getElkAlarmPanel() { return this.elkAlarmPanel; } getNode(address, parentsOnly = false) { let s = this.nodeMap.get(address); if (!parentsOnly) { if (s === null) { s = this.nodeMap[`${address.substr(0, address.length - 1)} 1`]; } } else { while (s.parentAddress !== undefined && s.parentAddress !== s.address && s.parentAddress !== null) { s = this.nodeMap[s.parentAddress]; } } return s; } getScene(address) { return this.sceneList.get(address); } getVariable(type, id) { const key = this.#createVariableKey(type, id); if (this.variableList.has(key)) { return this.variableList[key]; } return null; } getVariableList() { return this.variableList; } async handleInitializeError(step, reason) { this.logger.error(`Error initializing ISY (${step}): ${Utils.logStringify(reason)}`); return Promise.reject(reason); } handleWebSocketMessage(event) { this.lastActivity = Date.now(); let that = this; parser.parseString(event.data, (err, res) => { if (err) { that.logger.error(`Error parsing ISY WebSocket message: ${err} - ${event.data}`); return; } try { const evt = res?.Event; if (evt === undefined || evt.control === undefined) { return; } let actionValue = 0; if (evt.action instanceof Object) { actionValue = Number(evt.action._); } else if (typeof evt.action === 'string') { actionValue = Number(evt.action); } const stringControl = Number(evt.control?.replace('_', '')); switch (stringControl) { case EventType.Elk: if (actionValue === 2) { this.elkAlarmPanel.handleEvent(event); } else if (actionValue === 3) { const zeElement = evt.eventInfo.ze; const zoneId = zeElement.zone; const zoneDevice = this.zoneMap[zoneId]; if (zoneDevice !== null) { if (zoneDevice.handleEvent(event)) { this.nodeChangedHandler(zoneDevice); } } } break; case EventType.Trigger: if (actionValue === 6) { const varNode = evt.eventInfo.var; const id = varNode.id; const type = varNode.type; this.getVariable(type, id)?.handleEvent(evt); } break; case EventType.Heartbeat: this.logger.debug(`Received ${EventType[EventType.Heartbeat]} Signal from ISY: ${JSON.stringify(evt)}`); break; case EventType.SystemStatusChanged: case EventType.ProgressReport: this.logger.silly(`${EventType[stringControl]} Message: ${JSON.stringify(evt)}`); break; default: if (evt.node !== '' && evt.node !== undefined && evt.node !== null) { // const impactedNode = this.getNode(evt.node); if (impactedNode !== undefined && impactedNode !== null) { try { impactedNode.handleEvent(evt); } catch (e) { this.logger.error(`Error handling message for ${impactedNode.name}: ${e.message}`); } } else { this.logger.silly(`${stringControl} Message for Unidentified Device: ${JSON.stringify(evt)}`); } } else { /*else if (stringControl === EventType.NodeChanged) { this.logger.debug(`Received Node Change Event: ${JSON.stringify(evt)}. These are currently unsupported.`);*/ this.logger.debug(`${EventType[stringControl]} Message: ${JSON.stringify(evt)}`); } break; } } catch (e) { this.logger.error(`Error handling WebSocket message: ${e.message} - ${JSON.stringify(event)}`); } }); } async initialize() { const that = this; try { await this.loadConfig(); await this.loadNodes(); await this.refreshStatuses(); await this.loadVariables(VariableType.Integer); await this.loadVariables(VariableType.State); await this.#finishInitialize(true); return true; } catch (e) { if (e instanceof ISYInitializationError) { if (e.step === 'variables') { this.logger.warn(`Error loading variables: ${e.message}`); await this.#finishInitialize(true); return true; } this.logger.error(`Error initializing ISY during (${e.step}): ${e.message}`); return false; } else { this.logger.error(`Error initializing ISY: ${e.message}`); return false; } } finally { if (this.nodesLoaded !== true) { await that.#finishInitialize(false); } } } webSocketOptions; async initializeWebSocket() { if (!this.enableWebSocket) { this.logger.warn('WebSocket is disabled. Skipping initialization.'); return; } try { const that = this; //const auth = `Basic ${Buffer.from(`${this.credentials.username}:${this.credentials.password}`).toString('base64')}`; let address = `${this.wsprotocol}://${this.address}/rest/subscribe`; if (this.socketPath) { address = `ws+unix:${this.socketPath}:/rest/subscribe`; } this.logger.info(`Opening webSocket: ${address}`); this.logger.info('Using the following websocket options: ' + JSON.stringify(this.webSocketOptions)); if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { try { this.webSocket.close(); } catch (e) { this.logger.warn(`Error closing existing websocket: ${e.message}`); } } let p = new Promise((resolve, reject) => { let webSocket = new WebSocket(`${address}`, ['ISYSUB'], this.webSocketOptions); //this.webSocket.onmessage = (event) => {this.handleWebSocketMessage() webSocket .on('open', () => { this.logger.info('Websocket connection open'); resolve(webSocket); }) .on('message', (data, b) => { that.logger.silly(`Received message: ${Utils.logStringify(data, 1)}`); that.handleWebSocketMessage({ data: data }); }) .on('error', (err, response) => { that.logger.warn(`Websocket subscription error: ${err}`); reject(new ISYInitializationError('Websocket subscription error', 'websocket')); }) .on('fail', (data, response) => { that.logger.warn(`Websocket subscription failure: ${data}`); reject(new Error('Websocket subscription failure')); }) .on('abort', () => { that.logger.warn('Websocket subscription aborted.'); throw new Error('Websocket subscription aborted.'); }) .on('timeout', (ms) => { that.logger.warn(`Websocket subscription timed out after ${ms} milliseconds.`); reject(new Error('Timeout contacting ISY')); //throw new Error('Timeout contacting ISY'); }) .on('close', (code, reason) => { that.logger.warn(`Websocket subscription closed: ${code} - ${reason}`); if (that.guardianTimer) { clearInterval(that.guardianTimer); that.guardianTimer = null; } that.webSocket = null; }); }); this.webSocket = await p; } catch (e) { throw new ISYInitializationError(e, 'websocket'); } } async loadConfig() { try { this.logger.info('Loading ISY Config'); const configuration = (await this.sendRequest('config')).configuration; if (this.isDebugEnabled) { writeDebugFile(JSON.stringify(configuration), 'ISYConfig.json', this.logger, this.storagePath); } const controls = configuration.controls; this.model = configuration.deviceSpecs.model; this.firmwareVersion = configuration.app_full_version; this.vendorName = configuration.deviceSpecs.make; this.productId = configuration.product.id; this.productName = configuration.product.desc; this.id = configuration.root.id; // this.logger.info(result.configuration); if (controls !== undefined) { // this.logger.info(controls.control); // var arr = new Array(controls.control); for (const ctl of controls.control) { // this.logger.info(ctl); Controls[ctl.name] = ctl; } } return configuration; } catch (e) { throw new ISYInitializationError(e, 'config'); } } async loadNodes() { try { this.logger.info('Loading ISY Nodes'); const result = await this.sendRequest('nodes'); if (this.isDebugEnabled) await writeDebugFile(JSON.stringify(result), 'ISYNodes.json', this.logger, this.storagePath); await this.#readFolderNodes(result); await this.#readDeviceNodes(result); await this.#readSceneNodes(result); return result; } catch (e) { if (e instanceof ISYInitializationError) { throw e; } else { throw new ISYInitializationError(e, 'loadNodes'); } } } async loadVariables(type) { try { const that = this; this.logger.info(`Loading ISY Variables of type: ${type}`); let defs = await this.sendRequest(`vars/definitions/${type}`); this.#createVariables(type, defs); let vals = await this.sendRequest(`vars/get/${type}`); this.#setVariableValues(vals); } catch { this.logger.warn('error loading variables of type: ' + VariableType[type]); } } nodeChangedHandler(node, propertyName = null) { const that = this; if (this.nodesLoaded) { // this.logger.info(`Node: ${node.address} changed`); // if (this.changeCallback !== undefined && this.changeCallback !== null) { // t//his.changeCallback(that, node, propertyName); // } } } /*public override on(event: 'initializeCompleted', listener: () => void): this; public override on(event: 'nodeAdded' | 'nodeRemoved' | 'nodeChanged', listener: (node?: ISYNode<any, any, any, any>) => void): this; override on(event: 'initializeCompleted' | 'nodeAdded' | 'nodeRemoved' | 'nodeChanged', listener: (node?: ISYNode<any, any, any, any>) => void): this { return super.on('nodeAdded',listener) }*/ async refreshStatuses() { try { this.logger.info('Refreshing ISY Node Statuses'); const that = this; const result = await that.sendRequest('status'); if (that.isDebugEnabled) { await writeDebugFile(result, 'ISYStatus.json', this.logger, this.storagePath); } //this.logger.debug(result); for (const node of result.nodes.node) { let device = that.getNode(node.id); if (device !== null && device !== undefined) { device.parseResult(node); } } } catch (e) { throw new Error(`Error refreshing statuses: ${JSON.stringify(e.message)}`); } } async sendGetVariable(id, type, handleResult) { const uriToUse = `vars/get/${type}/${id}`; return this.sendRequest(uriToUse).then((p) => handleResult(p.val, p.init)); } async sendISYCommand(path) { // const uriToUse = `${this.protocol}://${this.address}/rest/${path}`; this.logger.info(`Sending command...${path}`); return this.sendRequest(path); } #formatParameter(name, value) { if (value === undefined) return ''; if (Utils.hasUOM(value)) { const [val, uom] = value; if (val === undefined || val === null) return ''; return `${name}.uom${uom}=${this.#formatParamValue(val)}`; } return `${name}=${this.#formatParamValue(value)}`; } #formatDefaultParameter(value) { if (value === undefined) return ''; if (Utils.hasUOM(value)) { const [val, uom] = value; if (val === undefined || val === null) return ''; return `${this.#formatParamValue(val, uom)}/${uom}`; } return `${this.#formatParamValue(value)}`; } #formatParamValue(param, uom) { if (typeof param === 'string') return param; if (typeof param === 'number') return param.toString(); if (typeof param === 'boolean') { if (uom == UnitOfMeasure.OffOn) return param ? '100' : '0'; if (uom == UnitOfMeasure.Percent) return param ? '100' : '0'; if (uom == UnitOfMeasure.LevelFrom0To255) return param ? '255' : '0'; return param ? '1' : '0'; } } async sendNodeCommand(node, command, defaultParameter, parameters) { let uriToUse = `nodes/${node.address}/cmd/${command}?`; if (defaultParameter !== null && defaultParameter !== undefined) { if (Array.isArray(defaultParameter) || isPrimitive(defaultParameter)) { uriToUse += `/${this.#formatDefaultParameter(defaultParameter)}?`; } } if (parameters !== null && parameters !== undefined) { if (typeof parameters === 'object' && !Array.isArray(parameters)) { var q = parameters; for (const paramName in q) { let s = q[paramName]; if (paramName === 'value' || paramName === 'default') { uriToUse += `/${this.#formatDefaultParameter(s)}?`; continue; } else { let p = this.#formatParameter(paramName, s); if (p === '') continue; uriToUse += `${p}&`; } } //uriToUse += `/${q[((p : Record<string,number|number>) => `${p[]}/${p.paramValue}` ).join('/')}`; } else { uriToUse += `/${this.#formatDefaultParameter(parameters)}`; } } uriToUse = uriToUse.endsWith('?') ? uriToUse.slice(0, -1) : uriToUse.endsWith('&') ? uriToUse.slice(0, -1) : uriToUse; uriToUse = uriToUse.replace('?/', '/'); this.logger.info(`${node.name}: sending ${command} command: ${uriToUse}`); return this.sendRequest(uriToUse, { requestLogLevel: 'info' }); } async sendRequest(url, options = { trailingSlash: true }) { const requestLogLevel = options.requestLogLevel ?? 'debug'; const responseLogLevel = options.responseLogLevel ?? 'silly'; url = `${url}${options.trailingSlash && !url.endsWith('/') ? '/' : ''}`; const finalUrl = `${this.protocol}://${this.address}/rest/${url}`; const reqOps = { ...this.axiosOptions, ...options }; this.logger.log(requestLogLevel, `Sending request to ${path.join('/rest', url)} with options: ${Utils.logStringify(reqOps, 0)}`); /*{ auth: { username: this.credentials.username, password: this.credentials.password }, baseURL: `${this.protocol}://${this.address}`, }*/ try { delete reqOps.trailingSlash; delete reqOps.requestLogLevel; const response = await axios.get(reqOps.socketPath ? path.join('/rest', url) : finalUrl, reqOps); if (response.data) { if (response.headers['content-type'].toString().includes('xml')) { let altParser = this.xmlParser; if (options.parserOptions) { if (typeof options.parserOptions === 'object') { this.logger.info('Using custom parser options'); altParser = new XMLParser({ ...defaultXMLParserOptions, ...options.parserOptions }); } } // {curParser = new Parser({ ...defaultParserOptions, ...options.parserOptions }); //var altParser = new XMLParser({ ...defaultXMLParserOptions, ...options.parserOptions }); var s = altParser.parse(response.data); this.logger.log(responseLogLevel ?? 'debug', `Response: ${JSON.stringify(s)}`); return s; } else if (response.headers['content-type'].toString().includes('json')) { this.logger.log(responseLogLevel, `Response: ${JSON.stringify(response.data)}`); return JSON.parse(response.data); } else { this.logger.log(responseLogLevel, `Response Header: ${JSON.stringify(response.headers)} Response: ${JSON.stringify(response.data)}`); return response.data; } } } catch (error) { if (options.throwOnError) { throw error; } else { if (options.errorLogLevel) { this.logger.log(options.errorLogLevel, `Error sending request to ISY: ${error?.message}`); } else { this.logger.error(`Error sending request to ISY: ${error?.message}`); } } } } async sendSetVariable(id, type, value, handleResult) { const uriToUse = `/rest/vars/set/${type}/${id}/${value}`; this.logger.info(`Sending ISY command...${uriToUse}`); return this.sendRequest(uriToUse); } #variableChangedHandler(variable) { this.logger.info(`Variable: ${variable.id} (${variable.type}) changed`); } close() { try { this.webSocket?.close(); } catch (e) { this.logger.error(`Error closing websocket: ${e.message}`); } } // #endregion Public Methods (24) // #region Private Methods (11) #checkForFailure(response) { return response === null || response instanceof Error || (response.RestResponse !== undefined && response.RestResponse.status !== 200); } #createVariableKey(type, id) { return `${type}:${id}`; } #createVariables(type, result) { for (const variable of result.CList.e) { const id = Number(variable.id); const name = variable.name; const newVariable = new ISYVariable(this, id, name, type); this.variableList.set(this.#createVariableKey(type, id), newVariable); } } async #finishInitialize(success) { if (!this.nodesLoaded) { this.nodesLoaded = true; //initializeCompleted(); if (success) { // if (this.elkEnabled) { // this.deviceList[this.elkAlarmPanel.address] = this.elkAlarmPanel; // } if (this.enableWebSocket) { await this.initializeWebSocket(); this.guardianTimer = setInterval(this.#guardian.bind(this), this.guardianInterval); } this.emit('initializeCompleted'); } } } async #guardian() { const timeNow = Date.now(); if (timeNow - this.lastActivity > this.guardianInterval) { this.logger.info('Guardian: Detected no activity in more then 60 seconds. Reinitializing web sockets'); await this.refreshStatuses(); await this.initializeWebSocket(); this.guardianTimer.refresh(); this.logger.info('Guardian: WebSocket reinitialized'); this.lastActivity = Date.now(); } } async #readDeviceNodes(obj) { try { this.logger.info('Reading Device Nodes'); for (const nodeInfo of obj.nodes.node) { try { this.logger.debug(`Loading Device Node: ${JSON.stringify(nodeInfo)}`); if (nodeInfo.enabled) { let newDevice = await DeviceFactory.create(this, nodeInfo); if (!newDevice && nodeInfo.pnode == nodeInfo.address) { this.logger.warn(`Device type resolution failed for ${nodeInfo.name} with type: ${nodeInfo.type} and nodedef: ${nodeInfo.nodeDefId}`); newDevice = new GenericNode(this, nodeInfo); } if (newDevice) { if (this.devices.set) this.devices.set(newDevice.address, newDevice); } /* if (!this.deviceMap.has(nodeInfo.pnode)) { const address = nodeInfo.address; this.deviceMap[nodeInfo.pnode] = { address }; } else { this.deviceMap[nodeInfo.pnode].push(nodeInfo.address); } let newDevice = null; // let deviceTypeInfo = this.isyTypeToTypeName(device.type, device.address); // this.logger.info(JSON.stringify(deviceTypeInfo)); const enabled = nodeInfo.enabled ?? true; const d = await NodeFactory.get(nodeInfo); const m = DeviceFactory.getDeviceDetails(nodeInfo); const cls = m.class ?? d; nodeInfo.property = Array.isArray(nodeInfo.property) ? nodeInfo.property : [nodeInfo.property]; nodeInfo.state = nodeInfo.property.reduce((acc, p) => { if (p && p?.id) { p.name = p.name == '' ? undefined : p.name; acc[p.id] = p; } return acc; }, {}); if (cls) { try { newDevice = new cls(this, nodeInfo); } catch (e) { this.logger.error(`Error creating device ${nodeInfo.name} with type ${nodeInfo.type} and nodedef ${nodeInfo.nodeDefId}: ${e.message}`); continue; } //newDevice = new cls(this, nodeInfo) as ISYDeviceNode<any, any, any, any>; } if (m && newDevice) { newDevice.productName = m.name; newDevice.model = `(${m.modelNumber}) ${m.name} v.${m.version}`; newDevice.modelNumber = m.modelNumber; newDevice.version = m.version; } if (enabled) { if (newDevice === null) { this.logger.warn(`Device type resolution failed for ${nodeInfo.name} with type: ${nodeInfo.type} and nodedef: ${nodeInfo.nodeDefId}`); newDevice = new GenericNode(this, nodeInfo); } else if (newDevice !== null) { if (m.unsupported) { this.logger.warn('Device not currently supported: ' + JSON.stringify(nodeInfo) + ' /n It has been mapped to: ' + d.name); } try { //await newDevice.refreshNotes(); } catch (e) { this.logger.debug('No notes found.'); } // if (!newDevice.hidden) { // } // this.deviceList.push(newDevice); } else { } this.nodeMap.set(newDevice.address, CompositeDevice.isComposite(newDevice) ? newDevice.root : newDevice); if (CompositeDevice.isComposite(newDevice)) { if (this.deviceList.set) this.deviceList.set(newDevice.address, newDevice); } */ } else { this.logger.info(`Ignoring disabled device: ${nodeInfo.name}`); } } catch (e) { this.logger.error(`Error loading device node: ${e.message}`); } } await DeviceFactory.addChildNodes(this); this.logger.info(`${this.nodeMap.size} nodes added.`); for (const node of this.nodeMap.values()) { try { //await node.refreshNotes(); } catch (e) { this.logger.silly('No notes found.'); } } /*for (const node of this.nodeMap.values()) { if (node.parentAddress !== node.address) { let parent = this.deviceList.get(node.parentAddress) as unknown as CompositeDevice<any, any>; if (parent && CompositeDevice.isComposite(parent)) parent.addNode(node); } else if (!this.deviceList.has(node.address)) { this.deviceList.set(node.address, node as unknown as ISYDevice<any, any, any, any>); } }*/ this.logger.info(`${this.devices.size} unique devices added.`); } catch (e) { throw new ISYInitializationError(e, 'readDevices'); } } async #readFolderNodes(result) { try { this.logger.info('Reading Folder Nodes'); if (result?.nodes?.folder) { for (const folder of result.nodes.folder) { this.logger.debug(`Loading Folder Node: ${JSON.stringify(folder)}`); this.folderMap.set(folder.address, folder.name); } } } catch (e) { throw new ISYInitializationError(e, 'readFolders'); } } async #readSceneNodes(result) { try { this.logger.info('Loading Scene Nodes'); for (const scene of result.nodes?.group) { if (scene.name === 'ISY' || scene.name === 'IoX' || scene.name === 'Auto DR') { continue; } // Skip ISY & Auto DR Scenes const newScene = new ISYScene(this, scene); try { //await newScene.refreshNotes(); } catch (e) { this.logger.debug('No notes found.'); } this.sceneList.set(newScene.address, newScene); } this.logger.info(`${this.sceneList.size} scenes added.`); } catch (e) { throw new ISYInitializationError(e, 'readScenes'); } } #setVariableValues(result) { for (const vals of result.vars.var) { const type = Number(vals.type); const id = Number(vals.id); const variable = this.getVariable(type, id); if (variable) { variable.init = vals.init; variable.value = vals.val; variable.lastChanged = new Date(vals.ts); } } } } export { CompositeDevice } from './Devices/CompositeDevice.js'; export { ISYDeviceNode as DeviceNode } from './Devices/ISYDeviceNode.js'; export { ISYNode as Node } from './ISYNode.js'; export * from './Converters.js'; export * from './Definitions/index.js'; export * as Devices from './Devices/index.js'; export * from './ISYError.js'; export { ISYError } from './ISYError.js'; export { ISYVariable as Variable } from './ISYVariable.js'; export * from './Model/index.js'; export { ISYScene as Scene } from './Scenes/ISYScene.js'; export * from './Utils.js'; export { VariableType } from './VariableType.js'; //# sourceMappingURL=ISY.js.map