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
JavaScript
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':