UNPKG

iobroker.hmip

Version:

ioBroker Adapter to use the Homematic Cloud IP AccessPoint via Hommeatic Cloud Rest API

1,062 lines (1,025 loc) 358 kB
const { Adapter } = require('@iobroker/adapter-core'); // Get common adapter utils const { v4: uuidv4 } = require('uuid'); const apiClass = require('./api/hmCloudAPI'); const adapterName = require('./package.json').name.split('.').pop(); class HmIpCloudAccesspointAdapter extends Adapter { constructor(options) { super({ ...options, name: adapterName }); this._api = new apiClass(); this._api.eventRaised = this._eventRaised.bind(this); // this._api.dataReceived = this._dataReceived.bind(this); this._api.opened = this._opened.bind(this); this._api.closed = this._closed.bind(this); this._api.errored = this._errored.bind(this); this._api.requestError = this._requestError.bind(this); this._api.unexpectedResponse = this._unexpectedResponse.bind(this); this.on('unload', this._unload); this.on('objectChange', this._objectChange); this.on('stateChange', this._stateChange); this.on('message', this._message); this.on('ready', this._ready); this._unloaded = false; this._requestTokenState = { state: 'idle' }; this.wsConnected = false; this.wsConnectionStableTimeout = null; this.wsConnectionErrorCounter = 0; this.sendUnknownInfos = {}; this.currentValues = {}; this.delayTimeouts = {}; this.initializedChannels = {}; this.reInitDataTimeout = null; } _unload(callback) { this._unloaded = true; this.expectWsError && clearTimeout(this.expectWsError); this.reInitTimeout && clearTimeout(this.reInitTimeout); this.reInitDataTimeout && clearTimeout(this.reInitDataTimeout); this._api.dispose(); try { this.log.info('cleaned everything up...'); callback(); } catch { callback(); } } _objectChange(id, obj) { this.log.info(`objectChange ${id} ${JSON.stringify(obj)}`); } async _message(msg) { this.log.debug(`message received - ${JSON.stringify(msg)}`); switch (msg.command) { case 'requestToken': this._requestTokenState = { state: 'startedTokenCreation' }; this.sendTo(msg.from, msg.command, this._requestTokenState, msg.callback); await this._startTokenRequest(msg); break; case 'requestTokenState': this.sendTo(msg.from, msg.command, this._requestTokenState, msg.callback); break; } } async _startTokenRequest(msg) { try { this.log.info('started token request'); let config = msg.message; this._api.parseConfigData(config.accessPointSgtin, config.pin, config.clientId); await this._api.getHomematicHosts(); this.log.info('auth step 1'); await this._api.auth1connectionRequest(config.deviceName); this.log.info('auth step 2'); while (!(await this._api.auth2isRequestAcknowledged()) && !this._unloaded) { this._requestTokenState = { state: 'waitForBlueButton' }; await new Promise(resolve => setTimeout(resolve, 2000)); } if (!this._unloaded) { this._requestTokenState = { state: 'confirmToken' }; this.log.info('auth step 3'); await this._api.auth3requestAuthToken(); let saveData = this._api.getSaveData(); saveData.state = 'tokenCreated'; this._requestTokenState = saveData; } } catch (err) { this._requestTokenState = { state: 'errorOccurred' }; this.log.error(`error requesting token: ${err}`); } } async _ready() { // set UUID if not set if (!this.config.deviceId) { const config = await this.getForeignObjectAsync(`system.adapter.${this.namespace}`); config.native.deviceId = uuidv4(); await this.setForeignObjectAsync(config._id, config); return; } this.reInitTimeout && clearTimeout(this.reInitTimeout); this.log.debug('ready'); await this.setState('info.connection', false, true); if (!this.Sentry && this.supportsFeature && this.supportsFeature('PLUGINS')) { const sentryInstance = this.getPluginInstance('sentry'); if (sentryInstance) { this.Sentry = sentryInstance.getSentryObject(); } } if ( this.config.accessPointSgtin && this.config.authToken && this.config.clientAuthToken && this.config.clientId ) { try { this._api.parseConfigData({ authToken: this.config.authToken, clientAuthToken: this.config.clientAuthToken, clientId: this.config.clientId, accessPointSgtin: this.config.accessPointSgtin, pin: this.config.pin, }); await this._api.getHomematicHosts(); await this._initData(); } catch (err) { this.log.error(`error starting Homematic: ${err}`); this.log.error('Try reconnect in 30s'); this.reInitTimeout && clearTimeout(this.reInitTimeout); this.reInitTimeout = setTimeout(() => { this.reInitTimeout = null; this._ready(); }, 30000); return; } this.log.debug('subscribeStates'); this.subscribeStates('*'); await this.setState('info.connection', true, true); this.log.info('hmip adapter connected and ready'); } else { this.log.info('token not yet created'); } } async _initData() { await this._api.loadCurrentConfig(); this.log.debug('createObjectsForDevices'); await this._createObjectsForDevices(); this.log.debug('createObjectsForGroups'); await this._createObjectsForGroups(); this.log.debug('createObjectsForClients'); await this._createObjectsForClients(); this.log.debug('createObjectsForHomes'); await this._createObjectsForHomes(); this.log.debug('connectWebsocket'); this._api.connectWebsocket(); this.log.debug('updateDeviceStates'); if (this._api.devices) { for (let d in this._api.devices) { if (!Object.prototype.hasOwnProperty.call(this._api.devices, d)) { continue; } await this._updateDeviceStates(this._api.devices[d]); } } else { this.log.debug('No devices'); } if (this._api.groups) { for (let g in this._api.groups) { if (!Object.prototype.hasOwnProperty.call(this._api.groups, g)) { continue; } await this._updateGroupStates(this._api.groups[g]); } } else { this.log.debug('No groups'); } if (this._api.clients) { for (let c in this._api.clients) { if (!Object.prototype.hasOwnProperty.call(this._api.clients, c)) { continue; } await this._updateClientStates(this._api.clients[c]); } } else { this.log.debug('No clients'); } if (this._api.home) { await this._updateHomeStates(this._api.home); } else { this.log.debug('No home'); } } round(value, step) { step = step || 1.0; const inv = 1.0 / step; return Math.round(value * inv) / inv; } async _doStateChange(id, o, state) { try { switch (o.native.parameter) { case 'switchState': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceControlSetSwitchState(o.native.id, state.val, o.native.channel); break; case 'sendDoorCommand': //door commands as number: 1 = open; 2 = stop; 3 = close; 4 = ventilation position switch (state.val) { case 0: //state.val = 'OPEN'; break; case 1: //state.val = 'STOP'; break; case 2: //state.val = 'CLOSE'; break; case 3: //state.val = 'VENTILATION_POSITION'; break; break; // Send as before default: this.log.info('Ignore invalid value for doorCommand.'); return; } await this._api.deviceControlSendDoorCommand(o.native.id, state.val, o.native.channel); break; case 'setLockState': { //door commands as number: 1 = open; 2 = locked; 3 = unlocked switch (state.val) { case 1: state.val = 'OPEN'; break; case 2: state.val = 'LOCKED'; break; case 3: state.val = 'UNLOCKED'; break; default: this.log.info('Ignore invalid value for setLockState.'); return; } const pin = await this.getStateAsync(`devices.${o.native.id}.channels.${o.native.channel}.pin`); this.log.info(`Call setLockState for ${state.val} ${pin ? 'with' : 'without'} PIN`); await this._api.deviceControlSetLockState( o.native.id, state.val, pin ? pin.val : '', o.native.channel, ); } break; case 'resetEnergyCounter': await this._api.deviceControlResetEnergyCounter(o.native.id, o.native.channel); break; case 'startImpulse': await this._api.deviceControlStartImpulse(o.native.id, o.native.channel); break; case 'shutterlevel': if (typeof state.val === 'number' && state.val > 1) { state.val = state.val / 100; } if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceControlSetShutterLevel(o.native.id, state.val, o.native.channel); break; case 'slatsLevel': { let slats = await this.getStateAsync( `devices.${o.native.id}.channels.${o.native.channel}.slatsLevel`, ); let shutter = await this.getStateAsync( `devices.${o.native.id}.channels.${o.native.channel}.shutterLevel`, ); if (typeof slats.val === 'number' && slats.val > 1) { slats.val = slats.val / 100; } if (typeof shutter.val === 'number' && shutter.val > 1) { shutter.val = shutter.val / 100; } if ( slats.val === this.currentValues[`devices.${o.native.id}.channels.${o.native.channel}.slatsLevel`] && shutter.val === this.currentValues[`devices.${o.native.id}.channels.${o.native.channel}.shutterLevel`] ) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceControlSetSlatsLevel( o.native.id, slats.val, shutter.val, o.native.channel, ); } break; case 'setPrimaryShadingLevel': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceControlSetPrimaryShadingLevel(o.native.id, state.val, o.native.channel); break; case 'setSecondaryShadingLevel': { let primary = await this.getStateAsync( `devices.${o.native.id}.channels.${o.native.channel}.primaryShadingLevel`, ); let secondary = await this.getStateAsync( `devices.${o.native.id}.channels.${o.native.channel}.secondaryShadingLevel`, ); if ( primary.val === this.currentValues[ `devices.${o.native.id}.channels.${o.native.channel}.primaryShadingLevel` ] && secondary.val === this.currentValues[ `devices.${o.native.id}.channels.${o.native.channel}.secondaryShadingLevel` ] ) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceControlSetSecondaryShadingLevel( o.native.id, primary.val, secondary.val, o.native.channel, ); } break; case 'stop': await this._api.deviceControlStop(o.native.id, o.native.channel); break; case 'setPointTemperature': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } for (let id of o.native.id) { await this._api.groupHeatingSetPointTemperature(id, state.val); } break; case 'setBoost': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } for (let id of o.native.id) { await this._api.groupHeatingSetBoost(id, state.val); } break; case 'setBoostDuration': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } for (let id of o.native.id) { await this._api.groupHeatingSetBoostDuration(id, state.val); } break; case 'setActiveProfile': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } for (let id of o.native.id) { await this._api.groupHeatingSetActiveProfile(id, state.val); } break; case 'setControlMode': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } for (let id of o.native.id) { await this._api.groupHeatingSetControlMode(id, state.val); } break; case 'setOperationLock': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetOperationLock(o.native.id, state.val, o.native.channel); break; case 'setClimateControlDisplay': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetClimateControlDisplay( o.native.id, state.val, o.native.channel, ); break; case 'setMinimumFloorHeatingValvePosition': if (typeof state.val === 'number' && state.val > 1) { state.val = state.val / 100; } if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetMinimumFloorHeatingValvePosition( o.native.id, state.val, o.native.channel, ); break; case 'setDimLevel': if (typeof state.val === 'number' && state.val > 1) { state.val = state.val / 100; } if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceControlSetDimLevel(o.native.id, state.val, o.native.channel); break; case 'setRgbDimLevel': { let rgb = await this.getStateAsync( `devices.${o.native.id}.channels.${o.native.channel}.simpleRGBColorState`, ); let dimLevel = await this.getStateAsync( `devices.${o.native.id}.channels.${o.native.channel}.dimLevel`, ); if (dimLevel > 1) { dimLevel = dimLevel / 100; } if ( rgb.val === this.currentValues[ `devices.${o.native.id}.channels.${o.native.channel}.simpleRGBColorState` ] && dimLevel.val === this.currentValues[`devices.${o.native.id}.channels.${o.native.channel}.dimLevel`] ) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceControlSetRgbDimLevel( o.native.id, rgb.val, dimLevel.val, o.native.channel, ); } break; case 'setOpticalSignalBehaviour': { let rgb = await this.getStateAsync( `devices.${o.native.id}.channels.${o.native.channel}.simpleRGBColorState`, ); let dimLevel = await this.getStateAsync( `devices.${o.native.id}.channels.${o.native.channel}.dimLevel`, ); let opticalSignal = await this.getStateAsync( `devices.${o.native.id}.channels.${o.native.channel}.opticalSignalBehaviour`, ); if (dimLevel > 1) { dimLevel = dimLevel / 100; } if ( rgb.val === this.currentValues[ `devices.${o.native.id}.channels.${o.native.channel}.simpleRGBColorState` ] && dimLevel.val === this.currentValues[`devices.${o.native.id}.channels.${o.native.channel}.dimLevel`] ) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceControlOpticalSignalBehaviour( o.native.id, rgb.val, dimLevel.val, o.native.channel, opticalSignal.val, ); } break; case 'setAcousticAlarmSignal': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetAcousticAlarmSignal(o.native.id, state.val, o.native.channel); break; case 'setAcousticAlarmTiming': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetAcousticAlarmTiming(o.native.id, state.val, o.native.channel); break; case 'setAcousticWaterAlarmTrigger': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetAcousticWaterAlarmTrigger( o.native.id, state.val, o.native.channel, ); break; case 'setSirenWaterAlarmTrigger': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetSirenWaterAlarmTrigger( o.native.id, state.val, o.native.channel, ); break; case 'setAccelerationSensorMode': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetAccelerationSensorMode( o.native.id, state.val, o.native.channel, ); break; case 'setAccelerationSensorNeutralPosition': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetAccelerationSensorNeutralPosition( o.native.id, state.val, o.native.channel, ); break; case 'setAccelerationSensorTriggerAngle': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetAccelerationSensorTriggerAngle( o.native.id, state.val, o.native.channel, ); break; case 'setAccelerationSensorSensitivity': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetAccelerationSensorSensitivity( o.native.id, state.val, o.native.channel, ); break; case 'setAccelerationSensorEventFilterPeriod': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetAccelerationSensorEventFilterPeriod( o.native.id, state.val, o.native.channel, ); break; case 'setNotificationSoundTyp': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetNotificationSoundTyp( o.native.id, state.val, id.endsWith('HighToLow'), o.native.channel, ); break; case 'setRouterModuleEnabled': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.deviceConfigurationSetRouterModuleEnabled(o.native.id, state.val, o.native.channel); break; case 'changeOverDelay': //await this._api.deviceConfigurationChangeOverDelay(o.native.id, state.val, o.native.channel) break; case 'setAbsenceEndTime': await this._api.homeHeatingActivateAbsenceWithPeriod(state.val); break; case 'setAbsenceDuration': await this._api.homeHeatingActivateAbsenceWithDuration(state.val); break; case 'deactivateAbsence': await this._api.homeHeatingDeactivateAbsence(); break; case 'setAbsencePermanent': await this._api.homeHeatingActivateAbsencePermanent(); break; case 'setIntrusionAlertThroughSmokeDetectors': if (state.val === this.currentValues[id]) { this.log.info(`Value unchanged, do not send this value`); await this.secureSetStateAsync(id, this.currentValues[id], true); return; } await this._api.homeSetIntrusionAlertThroughSmokeDetectors(state.val); break; case 'activateVacation': { let vacTemp = await this.getStateAsync( `homes.${o.native.id}.functionalHomes.indoorClimate.vacationTemperature`, ).val; await this._api.homeHeatingActivateVacation(vacTemp, state.val); } break; case 'deactivateVacation': await this._api.homeHeatingDeactivateVacation(); break; case 'setSecurityZonesActivationNone': await this._api.homeSetZonesActivation(false, false); break; case 'setSecurityZonesActivationInternal': await this._api.homeSetZonesActivation(true, false); break; case 'setSecurityZonesActivationExternal': await this._api.homeSetZonesActivation(false, true); break; case 'setSecurityZonesActivationInternalAndExternal': await this._api.homeSetZonesActivation(true, true); break; case 'setOnTime': if (Array.isArray(o.native.id)) { for (let id of o.native.id) { await this._api.groupSwitchingAlarmSetOnTime(id, state.val); } } break; case 'testSignalOptical': if (Array.isArray(o.native.id)) { for (let id of o.native.id) { await this._api.groupSwitchingAlarmTestSignalOptical(id, state.val); } } break; case 'setSignalOptical': if (Array.isArray(o.native.id)) { for (let id of o.native.id) { await this._api.groupSwitchingAlarmSetSignalOptical(id, state.val); } } break; case 'testSignalAcoustic': if (Array.isArray(o.native.id)) { for (let id of o.native.id) { await this._api.groupSwitchingAlarmTestSignalAcoustic(id, state.val); } } break; case 'setSignalAcoustic': if (Array.isArray(o.native.id)) { for (let id of o.native.id) { await this._api.groupSwitchingAlarmSetSignalAcoustic(id, state.val); } } break; } } catch (err) { this.log.warn(`${o.native.parameter} - id ${o.native.id ? o.native.id : ''} - state change error: ${err}`); } } async _stateChange(id, state) { if (!id || !state || state.ack || this._unloaded) { return; } let o = await this.getObjectAsync(id); if (o && o.native && o.native.parameter) { if (o.native.step) { state.val = this.round(state.val, o.native.step); this.log.debug( `state change - ${o.native.parameter} - id ${o.native.id ? JSON.stringify(o.native.id) : ''} - value rounded to ${state.val} (step=${o.native.step} )`, ); } else { this.log.debug( `state change - ${o.native.parameter} - id ${o.native.id ? JSON.stringify(o.native.id) : ''} - value ${state.val}`, ); } if (o.native.debounce) { // if debounce and value is the same, ignore call if ( this.delayTimeouts[id] && this.delayTimeouts[id].timeout && this.delayTimeouts[id].lastVal === state.val ) { this.log.debug( `${o.native.parameter} - id ${o.native.id ? JSON.stringify(o.native.id) : ''} - Debounce waiting - value stable`, ); return; } } else { // if running timeout and not debounce, requests come in too fast if (this.delayTimeouts[id] && this.delayTimeouts[id].timeout) { this.log.info( `${o.native.parameter} - id ${o.native.id ? JSON.stringify(o.native.id) : ''} - Too fast value changes, change blocked!`, ); return; } } this.delayTimeouts[id] = this.delayTimeouts[id] || {}; // clear timeout if one is running if (this.delayTimeouts[id].timeout) { clearTimeout(this.delayTimeouts[id].timeout); delete this.delayTimeouts[id].timeout; } if (o.native.debounce) { // debounce, delay sending command this.delayTimeouts[id].lastVal = state.val; this.delayTimeouts[id].timeout = setTimeout( (id, o, state) => { this.delayTimeouts[id].timeout = null; this.log.debug( `${o.native.parameter} - id ${o.native.id ? JSON.stringify(o.native.id) : ''} - Send debounced value ${state.val} now to HMIP`, ); this._doStateChange(id, o, state); }, o.native.debounce, id, o, state, ); } else { this.delayTimeouts[id].timeout = setTimeout(() => { this.delayTimeouts[id].timeout = null; }, o.native.throttle || 1000); await this._doStateChange(id, o, state); } } } _dataReceived(data) { this.log.silly(`data received - ${data}`); } _opened() { this.log.info('ws connection opened'); this.wsConnected = true; this.wsConnectionStableTimeout && clearTimeout(this.wsConnectionStableTimeout); this.wsConnectionStableTimeout = setTimeout(() => { this.wsConnectionStableTimeout = null; this.wsConnectionErrorCounter = 0; }, 5000); // set null when connection is stable } _closed(code, reason, forced = false) { this.log.debug(`_onclose( ${code}, ${reason}, ${forced})`); if (this.wsConnectionStableTimeout || !this.wsConnected) { this.wsConnectionErrorCounter++; } else { this.wsConnectionErrorCounter = 0; } reason = reason ? reason.toString() : ''; if (!forced) { this.log.warn( `ws connection closed (${this.wsConnectionErrorCounter}) - code: ${code} - reason: ${reason}`, ); } this.wsConnected = false; this.expectWsError && clearTimeout(this.expectWsError); if (!forced && !this.reInitTimeout) { // When no error happens within 5 seconds, we refresh our self this.expectWsError = setTimeout(() => this._closed(code, reason, true), 5000); } if ((forced || this.wsConnectionErrorCounter > 6) && !this._unloaded) { this._api.dispose(); this.log.error(`close on websocket connection: ${code} - ${reason}`); this.log.error('Try reconnect in 30s'); this.reInitTimeout && clearTimeout(this.reInitTimeout); this.reInitTimeout = setTimeout(async () => { this.reInitTimeout = null; await this._ready(); }, 30000); } } _errored(error) { this.log.warn(`ws connection error (${this.wsConnectionErrorCounter}): ${error}`); const reason = error ? error.toString() : ''; if (!this.wsConnected) { this.wsConnectionErrorCounter++; } if (reason.includes('ECONNREFUSED') && !this._unloaded) { this._api.dispose(); this.log.error(`error on websocket connection: ${reason}`); this.log.error('Try reconnect in 30s'); this.reInitTimeout && clearTimeout(this.reInitTimeout); this.reInitTimeout = setTimeout(() => { this.reInitTimeout = null; this._ready(); }, 30000); } } _requestError(error) { if (error.response) { // The request was made and the server responded with a status code // that falls out of the range of 2xx this.log.warn(`Request error data: ${error.response.data}, (${JSON.stringify(error.response.data)})`); this.log.warn(`Request error status: ${error.response.status}`); } else if (error.request) { // The request was made but no response was received // `error.request` is an instance of XMLHttpRequest in the browser and an instance of // http.ClientRequest in node.js this.log.warn(`Request error: ${error.request}`); } else { // Something happened in setting up the request that triggered an Error this.log.warn(`Request error: ${error.message} (${error}, ${JSON.stringify(error)})`); } } _unexpectedResponse(req, res) { this.log.warn(`ws connection unexpected response: ${res.statusCode}`); } async _eventRaised(ev) { if (this._unloaded) { return; } switch (ev.pushEventType) { case 'DEVICE_ADDED': await this._createObjectsForDevice(ev.device); await this._updateDeviceStates(ev.device); break; case 'DEVICE_CHANGED': await this._updateDeviceStates(ev.device); break; case 'GROUP_ADDED': await this._createObjectsForGroup(ev.group); await this._updateGroupStates(ev.group); break; case 'GROUP_CHANGED': await this._updateGroupStates(ev.group); break; case 'CLIENT_ADDED': await this._createObjectsForClient(ev.client); await this._updateClientStates(ev.client); break; case 'CLIENT_CHANGED': await this._updateClientStates(ev.client); break; case 'DEVICE_REMOVED': break; case 'GROUP_REMOVED': break; case 'CLIENT_REMOVED': break; case 'HOME_CHANGED': if (ev && ev.home) { await this._updateHomeStates(ev.home); } else { this.log.warn(`No home in HOME_CHANGED: ${JSON.stringify(ev)}`); } break; case 'SECURITY_JOURNAL_CHANGED': if (ev && ev.home) { await this._updateHomeStates(ev.home); } else { this.log.debug(`Read Home for SECURITY_JOURNAL_CHANGED: ${JSON.stringify(ev)}`); const state = await this._api.callRestApi('home/getCurrentState', this._api._clientCharacteristics); state && state.home && (await this._updateHomeStates(state.home)); } break; case 'DEVICE_CHANNEL_EVENT': this.log.debug(`unhandled known event - ${JSON.stringify(ev)}`); break; default: this.log.warn(`unhandled event - ${JSON.stringify(ev)}`); } } async secureSetStateAsync(id, value, ack) { if (value && typeof value === 'object') { value = value.val; } if (value === undefined) { value = null; } await this.setStateAsync(id, value, ack); if (ack) { this.currentValues[`${this.namespace}.${id}`] = value; } } async _updateDeviceStates(device) { this.log.silly(`updateDeviceStates - ${device.type} - ${JSON.stringify(device)}`); let unknownChannelDetected = false; if (this.initializedChannels[`devices.${device.id}`]) { let promises = []; promises.push(this.secureSetStateAsync(`devices.${device.id}.info.type`, device.type, true)); promises.push(this.secureSetStateAsync(`devices.${device.id}.info.modelType`, device.modelType, true)); promises.push(this.secureSetStateAsync(`devices.${device.id}.info.label`, device.label, true)); promises.push( this.secureSetStateAsync(`devices.${device.id}.info.firmwareVersion`, device.firmwareVersion, true), ); promises.push(this.secureSetStateAsync(`devices.${device.id}.info.updateState`, device.updateState, true)); switch (device.type) { /*case 'PLUGABLE_SWITCH': { promises.push(this.secureSetStateAsync('devices.' + device.id + '.channels.1.on', device.functionalChannels['1'].on, true)); break; }*/ default: { break; } } for (let i in device.functionalChannels) { if (!Object.prototype.hasOwnProperty.call(device.functionalChannels, i)) { continue; } let fc = device.functionalChannels[i]; promises.push( this.secureSetStateAsync( `devices.${device.id}.channels.${i}.functionalChannelType`, fc.functionalChannelType, true, ), ); if (!this.initializedChannels[`devices.${device.id}.channels.${i}`]) { unknownChannelDetected = true; continue; } switch (fc.functionalChannelType) { case 'ENERGY_SENSORS_INTERFACE_CHANNEL': promises.push(...this._updateEnergySensorsInterfaceChannelStates(device, i)); break; case 'DEVICE_OPERATIONLOCK': promises.push(...this._updateDeviceOperationLockChannelStates(device, i)); break; case 'DEVICE_SABOTAGE': promises.push(...this._updateDeviceSabotageChannelStates(device, i)); break; case 'DEVICE_RECHARGEABLE_WITH_SABOTAGE': promises.push(...this._updateDeviceRechargeableWithSabotageChannelStates(device, i)); break; case 'ACCESS_CONTROLLER_CHANNEL': promises.push(...this._updateAccessControllerChannelStates(device, i)); break; case 'ACCESS_CONTROLLER_WIRED_CHANNEL': promises.push(...this._updateAccessControllerWiredChannelStates(device, i)); break; case 'PRESENCE_DETECTION_CHANNEL': promises.push(...this._updatePresenceDetectionChannelStates(device, i)); break; case 'PASSAGE_DETECTOR_CHANNEL': promises.push(...this._updatePassageDetectorChannelStates(device, i)); break; case 'DEVICE_GLOBAL_PUMP_CONTROL': promises.push(...this._updateDeviceGlobalPumpControlStates(device, i)); break; case 'FLOOR_TERMINAL_BLOCK_LOCAL_PUMP_CHANNEL': promises.push(...this._updateFloorTerminalBlockLockPumpChannelStates(device, i)); break; case 'FLOOR_TERMINAL_BLOCK_MECHANIC_CHANNEL': promises.push(...this._updateFloorTerminalBlockMechanicChannelStates(device, i)); break; case 'DEVICE_BASE_FLOOR_HEATING': promises.push(...this._updateDeviceBaseFloorHeatingChannelStates(device, i)); break; case 'DEVICE_INCORRECT_POSITIONED': promises.push(...this._updateDeviceIncorrectPositionedStates(device, i)); break; case 'CONTACT_INTERFACE_CHANNEL': promises.push(...this._updateContactInterfaceChannelStates(device, i)); break; case 'HEATING_THERMOSTAT_CHANNEL': promises.push(...this._updateHeatingThermostatChannelStates(device, i)); break; case 'SHUTTER_CONTACT_CHANNEL': promises.push(...this._updateShutterContactChannelStates(device, i)); break; case 'SMOKE_DETECTOR': promises.push(...this._updateSmokeDetectorChannelStates(device, i)); break; case 'DIMMER_CHANNEL': promises.push(...this._updateDimmerChannelStates(device, i)); break; case 'WATER_SENSOR_CHANNEL': promises.push(...this._updateWaterSensorChannelStates(device, i)); break; case 'TEMPERATURE_SENSOR_2_EXTERNAL_DELTA_CHANNEL': promises.push(...this._updateTemperatureSensor2ExternalDeltaChannelStates(device, i)); break; case 'SHADING_CHANNEL': promises.push(...this._updateShadingChannelStates(device, i)); break; case 'WEATHER_SENSOR_CHANNEL': promises.push(...this._updateWeatherSensorChannelStates(device, i)); break; case 'WEATHER_SENSOR_PLUS_CHANNEL': promises.push(...this._updateWeatherSensorPlusChannelStates(device, i)); break; case 'WEATHER_SENSOR_PRO_CHANNEL': promises.push(...this._updateWeatherSensorProChannelStates(device, i)); break; case 'SHUTTER_CHANNEL':