UNPKG

iobroker.hmip

Version:

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

714 lines (630 loc) 26.6 kB
'use strict'; const sha512 = require('js-sha512'); const { v4: uuidv4 } = require('uuid'); const webSocket = require('ws'); const axios = require('axios'); class HmCloudAPI { constructor(configDataOrApId, pin) { if (configDataOrApId !== undefined) { this.parseConfigData(configDataOrApId, pin); } this.eventRaised = null; } parseConfigData(configDataOrApId, pin, deviceId) { if (typeof configDataOrApId === 'string') { this._accessPointSgtin = configDataOrApId.replace(/[^a-fA-F0-9 ]/g, ''); this._clientAuthToken = sha512(`${this._accessPointSgtin}jiLpVitHvWnIGD1yo7MA`).toUpperCase(); this._authToken = ''; this._clientId = ''; this._urlREST = ''; this._urlWebSocket = ''; this._deviceId = deviceId || uuidv4(); this._pin = pin; } else { this._authToken = configDataOrApId.authToken; this._clientAuthToken = configDataOrApId.clientAuthToken; this._clientId = configDataOrApId.clientId; this._accessPointSgtin = configDataOrApId.accessPointSgtin.replace(/[^a-fA-F0-9 ]/g, ''); this._pin = configDataOrApId.pin; this._deviceId = configDataOrApId.deviceId || uuidv4(); } this._clientCharacteristics = { clientCharacteristics: { apiVersion: '12', applicationIdentifier: 'iobroker', applicationVersion: '1.0', deviceManufacturer: 'none', deviceType: 'Computer', language: 'en_US', osType: 'Linux', osVersion: 'NT', }, id: this._accessPointSgtin, }; } getSaveData() { return { authToken: this._authToken, clientAuthToken: this._clientAuthToken, clientId: this._clientId, accessPointSgtin: this._accessPointSgtin, pin: this._pin, deviceId: this._deviceId, }; } async getHomematicHosts() { let res; try { const response = await axios.post( 'https://lookup.homematic.com:48335/getHost', this._clientCharacteristics, ); res = response.data; } catch (err) { this.requestError && this.requestError(err); } if (res && typeof res === 'object') { this._urlREST = res.urlREST; this._urlWebSocket = res.urlWebSocket; if (this._urlWebSocket.startsWith('http')) { this._urlWebSocket = `ws${this._urlWebSocket.substring(4)}`; // make sure it is ws:// or wss:// } } if (!this._urlREST || !this._urlWebSocket) { throw new Error('Could not get host details. Please check the SGTIN.'); } } // =========== API for Token generation =========== async auth1connectionRequest(deviceName = 'hmipnodejs') { const headers = { 'content-type': 'application/json', accept: 'application/json', VERSION: '12', CLIENTAUTH: this._clientAuthToken, 'ACCESSPOINT-ID': this._accessPointSgtin, }; if (this._pin) { headers['PIN'] = this._pin; } const body = { deviceId: this._deviceId, deviceName, sgtin: this._accessPointSgtin, }; try { const response = await axios.post(`${this._urlREST}/hmip/auth/connectionRequest`, body, { headers, validateStatus: status => status < 400, }); return response.data; } catch (err) { this.requestError && this.requestError(err); return null; } } async auth2isRequestAcknowledged() { const headers = { 'content-type': 'application/json', accept: 'application/json', VERSION: '12', CLIENTAUTH: this._clientAuthToken, 'ACCESSPOINT-ID': this._accessPointSgtin, }; const body = { deviceId: this._deviceId, accessPointId: this._accessPointSgtin }; try { await axios.post(`${this._urlREST}/hmip/auth/isRequestAcknowledged`, body, { headers, validateStatus: status => status === 200, }); return true; } catch (err) { if (err.response && err.response.status !== 400) { this.requestError && this.requestError(err); } return false; } } async auth3requestAuthToken() { let headers = { 'content-type': 'application/json', accept: 'application/json', VERSION: '12', CLIENTAUTH: this._clientAuthToken, 'ACCESSPOINT-ID': this._accessPointSgtin, }; let body = { deviceId: this._deviceId }; let res; try { let response = await axios.post(`${this._urlREST}/hmip/auth/requestAuthToken`, body, { headers, validateStatus: status => status < 400, }); res = response.data; this._authToken = res.authToken; body = { deviceId: this._deviceId, authToken: this._authToken, }; response = await axios.post(`${this._urlREST}/hmip/auth/confirmAuthToken`, body, { headers, validateStatus: status => status < 400, }); res = response.data; this._clientId = res.clientId; } catch (err) { this.requestError && this.requestError(err); } } async callRestApi(path, data) { let headers = { 'content-type': 'application/json', accept: 'application/json', VERSION: '12', AUTHTOKEN: this._authToken, CLIENTAUTH: this._clientAuthToken, 'ACCESSPOINT-ID': this._accessPointSgtin, }; try { const response = await axios.post(`${this._urlREST}/hmip/${path}`, data, { headers }); return response.data; } catch (err) { this.requestError && this.requestError(err); } } // =========== API for HM =========== async loadCurrentConfig() { let state = await this.callRestApi('home/getCurrentState', this._clientCharacteristics); if (state) { this.home = state.home; this.groups = state.groups; this.clients = state.clients; this.devices = state.devices; } else { throw new Error('No current State received'); } } // =========== Event Handling =========== dispose() { this.isClosed = true; if (this._ws) { this._ws.close(); } if (this._connectTimeout) { clearTimeout(this._connectTimeout); } if (this._pingInterval) { clearInterval(this._pingInterval); } } connectWebsocket() { if (this._pingInterval) { clearInterval(this._pingInterval); this._pingInterval = null; } this._ws = new webSocket(this._urlWebSocket, { headers: { AUTHTOKEN: this._authToken, CLIENTAUTH: this._clientAuthToken, 'ACCESSPOINT-ID': this._accessPointSgtin, }, perMessageDeflate: false, }); this._ws.on('open', () => { this.opened && this.opened(); this._pingInterval && clearInterval(this._pingInterval); this._pingInterval = setInterval(() => this._ws.ping(() => {}), 5000); }); this._ws.on('close', (code, reason) => { this.closed && this.closed(code, reason.toString('utf8')); if (this._pingInterval) { clearInterval(this._pingInterval); this._pingInterval = null; } if (!this.isClosed) { this._connectTimeout && clearTimeout(this._connectTimeout); this._connectTimeout = setTimeout(() => { this._connectTimeout = null; this.connectWebsocket(); }, 10000); } }); this._ws.on('error', error => { this.errored && this.errored(error); if (this._pingInterval) { clearInterval(this._pingInterval); this._pingInterval = null; } if (!this.isClosed) { this._connectTimeout && clearTimeout(this._connectTimeout); this._connectTimeout = setTimeout(() => { this._connectTimeout = null; this.connectWebsocket(); }, 10000); } }); this._ws.on('unexpected-response', (request, response) => { this.unexpectedResponse && this.unexpectedResponse(request, response); if (this._pingInterval) { clearInterval(this._pingInterval); this._pingInterval = null; } if (!this.isClosed) { this._connectTimeout && clearTimeout(this._connectTimeout); this._connectTimeout = setTimeout(() => { this._connectTimeout = null; this.connectWebsocket(); }, 10000); } }); this._ws.on('message', d => { const dString = d.toString('utf8'); this.dataReceived && this.dataReceived(dString); const data = JSON.parse(dString); this._parseEventdata(data); }); this._ws.on('ping', () => this.dataReceived && this.dataReceived('ping')); this._ws.on('pong', () => this.dataReceived && this.dataReceived('pong')); } _parseEventdata(data) { for (let i in data.events) { let ev = data.events[i]; switch (ev.pushEventType) { case 'DEVICE_ADDED': case 'DEVICE_CHANGED': if (ev.device) { this.devices[ev.device.id] = ev.device; } break; case 'GROUP_ADDED': case 'GROUP_CHANGED': if (ev.group) { this.groups[ev.group.id] = ev.group; } break; case 'CLIENT_ADDED': case 'CLIENT_CHANGED': if (ev.client) { this.clients[ev.client.id] = ev.client; } break; case 'DEVICE_REMOVED': ev.device && delete this.devices[ev.device.id]; break; case 'GROUP_REMOVED': ev.group && delete this.clients[ev.group.id]; break; case 'CLIENT_REMOVED': ev.client && delete this.groups[ev.client.id]; break; case 'HOME_CHANGED': this.home = ev.home; break; } this.eventRaised && this.eventRaised(ev); } } // =========== API for HM Devices =========== // boolean async deviceControlSetSwitchState(deviceId, on, channelIndex = 1) { const data = { deviceId, on, channelIndex, }; await this.callRestApi('device/control/setSwitchState', data); } // door commands as number: 1 = open; 2 = stop; 3 = close; 4 = ventilation position // DoorState // CLOSED = auto() // OPEN = auto() // VENTILATION_POSITION = auto() // POSITION_UNKNOWN = auto() // // DoorCommand // OPEN = auto() // STOP = auto() // CLOSE = auto() // PARTIAL_OPEN = auto() async deviceControlSendDoorCommand(deviceId, doorCommand, channelIndex = 1) { let data = { deviceId, channelIndex, doorCommand }; await this.callRestApi('device/control/sendDoorCommand', data); } async deviceControlSetLockState(deviceId, lockState, pin, channelIndex = 1) { let data = { deviceId, channelIndex, authorizationPin: pin.toString(), targetLockState: lockState }; await this.callRestApi('device/control/setLockState', data); } async deviceControlResetEnergyCounter(deviceId, channelIndex = 1) { let data = { deviceId, channelIndex }; await this.callRestApi('device/control/resetEnergyCounter', data); } async deviceConfigurationSetOperationLock(deviceId, operationLock, channelIndex = 1) { let data = { deviceId, channelIndex, operationLock: operationLock }; await this.callRestApi('device/configuration/setOperationLock', data); } // ClimateControlDisplay // ACTUAL = auto() // SETPOINT = auto() // ACTUAL_HUMIDITY = auto() async deviceConfigurationSetClimateControlDisplay(deviceId, display, channelIndex = 1) { let data = { deviceId, channelIndex, display }; await this.callRestApi('device/configuration/setClimateControlDisplay', data); } // float 0.0-1.0 async deviceConfigurationSetMinimumFloorHeatingValvePosition( deviceId, minimumFloorHeatingValvePosition, channelIndex = 1, ) { let data = { deviceId, channelIndex, minimumFloorHeatingValvePosition }; await this.callRestApi('device/configuration/setMinimumFloorHeatingValvePosition', data); } // float 0.0-1.0?? async deviceControlSetDimLevel(deviceId, dimLevel, channelIndex = 1) { let data = { deviceId, channelIndex, dimLevel }; await this.callRestApi('device/control/setDimLevel', data); } // float 0.0-1.0?? async deviceControlSetRgbDimLevel(deviceId, rgb, dimLevel, channelIndex = 1) { let data = { deviceId, channelIndex, simpleRGBColorState: rgb, dimLevel }; await this.callRestApi('device/control/setSimpleRGBColorDimLevel', data); } // float 0.0-1.0?? // not used right now async deviceControlSetRgbDimLevelWithTime(deviceId, rgb, dimLevel, onTime, rampTime, channelIndex = 1) { let data = { deviceId, channelIndex, simpleRGBColorState: rgb, dimLevel, onTime, rampTime }; await this.callRestApi('device/control/setSimpleRGBColorDimLevelWithTime', data); } // float 0.0-1.0?? async deviceControlOpticalSignalBehaviour(deviceId, rgb, dimLevel, channelIndex = 2, opticalSignalBehaviour) { let data = { deviceId, channelIndex, dimLevel, simpleRGBColorState: rgb, opticalSignalBehaviour }; await this.callRestApi('device/control/setOpticalSignal', data); } // float 0.0 = open - 1.0 = closed async deviceControlSetShutterLevel(deviceId, shutterLevel, channelIndex = 1) { let data = { deviceId, channelIndex, shutterLevel }; await this.callRestApi('device/control/setShutterLevel', data); } async deviceControlStartImpulse(deviceId, channelIndex = 1) { let data = { deviceId, channelIndex }; await this.callRestApi('device/control/startImpulse', data); } // float 0.0 = open - 1.0 = closed async deviceControlSetSlatsLevel(deviceId, slatsLevel, shutterLevel, channelIndex = 1) { let data = { deviceId, channelIndex, slatsLevel, shutterLevel }; await this.callRestApi('device/control/setSlatsLevel', data); } async deviceControlStop(deviceId, channelIndex = 1) { let data = { deviceId, channelIndex }; await this.callRestApi('device/control/stop', data); } async deviceControlSetPrimaryShadingLevel(deviceId, primaryShadingLevel, channelIndex = 1) { let data = { deviceId, channelIndex, primaryShadingLevel: primaryShadingLevel }; await this.callRestApi('device/control/setPrimaryShadingLevel', data); } async deviceControlSetSecondaryShadingLevel( deviceId, primaryShadingLevel, secondaryShadingLevel, channelIndex = 1, ) { let data = { deviceId, channelIndex, primaryShadingLevel, secondaryShadingLevel }; await this.callRestApi('device/control/setSecondaryShadingLevel', data); } // AcousticAlarmSignal // DISABLE_ACOUSTIC_SIGNAL = auto() // FREQUENCY_RISING = auto() // FREQUENCY_FALLING = auto() // FREQUENCY_RISING_AND_FALLING = auto() // FREQUENCY_ALTERNATING_LOW_HIGH = auto() // FREQUENCY_ALTERNATING_LOW_MID_HIGH = auto() // FREQUENCY_HIGHON_OFF = auto() // FREQUENCY_HIGHON_LONGOFF = auto() // FREQUENCY_LOWON_OFF_HIGHON_OFF = auto() // FREQUENCY_LOWON_LONGOFF_HIGHON_LONGOFF = auto() // LOW_BATTERY = auto() // DISARMED = auto() // INTERNALLY_ARMED = auto() // EXTERNALLY_ARMED = auto() // DELAYED_INTERNALLY_ARMED = auto() // DELAYED_EXTERNALLY_ARMED = auto() // EVENT = auto() // ERROR = auto() async deviceConfigurationSetAcousticAlarmSignal(deviceId, acousticAlarmSignal, channelIndex = 1) { let data = { deviceId, acousticAlarmSignal, channelIndex }; await this.callRestApi('device/configuration/setAcousticAlarmSignal', data); } // AcousticAlarmTiming // PERMANENT = auto() // THREE_MINUTES = auto() // SIX_MINUTES = auto() // ONCE_PER_MINUTE = auto() async deviceConfigurationSetAcousticAlarmTiming(deviceId, acousticAlarmTiming, channelIndex = 1) { let data = { deviceId, acousticAlarmTiming, channelIndex }; await this.callRestApi('device/configuration/setAcousticAlarmTiming', data); } // WaterAlarmTrigger // NO_ALARM = auto() // MOISTURE_DETECTION = auto() // WATER_DETECTION = auto() // WATER_MOISTURE_DETECTION = auto() async deviceConfigurationSetAcousticWaterAlarmTrigger(deviceId, acousticWaterAlarmTrigger, channelIndex = 1) { let data = { deviceId, acousticWaterAlarmTrigger, channelIndex }; await this.callRestApi('device/configuration/setAcousticWaterAlarmTrigger', data); } // WaterAlarmTrigger // NO_ALARM = auto() // MOISTURE_DETECTION = auto() // WATER_DETECTION = auto() // WATER_MOISTURE_DETECTION = auto() async deviceConfigurationSetInAppWaterAlarmTrigger(deviceId, inAppWaterAlarmTrigger, channelIndex = 1) { let data = { deviceId, inAppWaterAlarmTrigger, channelIndex }; await this.callRestApi('device/configuration/setInAppWaterAlarmTrigger', data); } // WaterAlarmTrigger // NO_ALARM = auto() // MOISTURE_DETECTION = auto() // WATER_DETECTION = auto() // WATER_MOISTURE_DETECTION = auto() async deviceConfigurationSetSirenWaterAlarmTrigger(deviceId, sirenWaterAlarmTrigger, channelIndex = 1) { let data = { deviceId, sirenWaterAlarmTrigger, channelIndex }; await this.callRestApi('device/configuration/setSirenWaterAlarmTrigger', data); } // AccelerationSensorMode // ANY_MOTION = auto() // FLAT_DECT = auto() async deviceConfigurationSetAccelerationSensorMode(deviceId, accelerationSensorMode, channelIndex = 1) { let data = { deviceId, accelerationSensorMode, channelIndex }; await this.callRestApi('device/configuration/setAccelerationSensorMode', data); } // AccelerationSensorNeutralPosition // HORIZONTAL = auto() // VERTICAL = auto() async deviceConfigurationSetAccelerationSensorNeutralPosition( deviceId, accelerationSensorNeutralPosition, channelIndex = 1, ) { let data = { deviceId, accelerationSensorNeutralPosition, channelIndex }; await this.callRestApi('device/configuration/setAccelerationSensorNeutralPosition', data); } // accelerationSensorTriggerAngle = int async deviceConfigurationSetAccelerationSensorTriggerAngle( deviceId, accelerationSensorTriggerAngle, channelIndex = 1, ) { let data = { deviceId, accelerationSensorTriggerAngle, channelIndex }; await this.callRestApi('device/configuration/setAccelerationSensorTriggerAngle', data); } // AccelerationSensorSensitivity // SENSOR_RANGE_16G = auto() // SENSOR_RANGE_8G = auto() // SENSOR_RANGE_4G = auto() // SENSOR_RANGE_2G = auto() // SENSOR_RANGE_2G_PLUS_SENS = auto() // SENSOR_RANGE_2G_2PLUS_SENSE = auto() async deviceConfigurationSetAccelerationSensorSensitivity( deviceId, accelerationSensorSensitivity, channelIndex = 1, ) { let data = { deviceId, accelerationSensorSensitivity, channelIndex }; await this.callRestApi('device/configuration/setAccelerationSensorSensitivity', data); } // accelerationSensorEventFilterPeriod = float async deviceConfigurationSetAccelerationSensorEventFilterPeriod( deviceId, accelerationSensorEventFilterPeriod, channelIndex = 1, ) { let data = { deviceId, accelerationSensorEventFilterPeriod, channelIndex }; await this.callRestApi('device/configuration/setAccelerationSensorEventFilterPeriod', data); } // NotificationSoundType // SOUND_NO_SOUND = auto() // SOUND_SHORT = auto() // SOUND_SHORT_SHORT = auto() // SOUND_LONG = auto() async deviceConfigurationSetNotificationSoundTyp(deviceId, notificationSoundType, isHighToLow, channelIndex = 1) { let data = { deviceId, notificationSoundType, isHighToLow, channelIndex }; await this.callRestApi('device/configuration/setNotificationSoundTyp', data); } async deviceConfigurationSetRouterModuleEnabled(deviceId, routerModuleEnabled, channelIndex = 1) { let data = { deviceId, routerModuleEnabled, channelIndex }; await this.callRestApi('device/configuration/setRouterModuleEnabled', data); } async deviceDeleteDevice(deviceId) { let data = { deviceId }; await this.callRestApi('device/deleteDevice', data); } async deviceSetDeviceLabel(deviceId, label) { let data = { deviceId, label }; await this.callRestApi('device/setDeviceLabel', data); } async deviceIsUpdateApplicable(deviceId) { let data = { deviceId }; await this.callRestApi('device/isUpdateApplicable', data); } async deviceAuthorizeUpdate(deviceId) { let data = { deviceId }; await this.callRestApi('device/authorizeUpdate', data); } // =========== API for HM Groups =========== async groupHeatingSetPointTemperature(groupId, setPointTemperature) { let data = { groupId, setPointTemperature }; await this.callRestApi('group/heating/setSetPointTemperature', data); } async groupHeatingSetBoostDuration(groupId, boostDuration) { let data = { groupId, boostDuration }; await this.callRestApi('group/heating/setBoostDuration', data); } async groupHeatingSetBoost(groupId, boost) { let data = { groupId, boost }; await this.callRestApi('group/heating/setBoost', data); } async groupHeatingSetControlMode(groupId, controlMode) { let data = { groupId, controlMode }; //AUTOMATIC,MANUAL await this.callRestApi('group/heating/setControlMode', data); } async groupHeatingSetActiveProfile(groupId, profileIndex) { let data = { groupId, profileIndex }; await this.callRestApi('group/heating/setActiveProfile', data); } async groupSwitchingAlarmSetOnTime(groupId, onTime) { let data = { groupId, onTime }; await this.callRestApi('group/switching/alarm/setOnTime', data); } async groupSwitchingAlarmTestSignalOptical(groupId, signalOptical) { let data = { groupId, signalOptical }; await this.callRestApi('group/switching/alarm/testSignalOptical', data); } async groupSwitchingAlarmSetSignalOptical(groupId, signalOptical) { let data = { groupId, signalOptical }; await this.callRestApi('group/switching/alarm/setSignalOptical', data); } async groupSwitchingAlarmTestSignalAcoustic(groupId, signalAcoustic) { let data = { groupId, signalAcoustic }; await this.callRestApi('group/switching/alarm/testSignalAcoustic', data); } async groupSwitchingAlarmSetSignalAcoustic(groupId, signalAcoustic) { let data = { groupId, signalAcoustic }; await this.callRestApi('group/switching/alarm/setSignalAcoustic', data); } // =========== API for HM Clients =========== async clientDeleteClient(clientId) { let data = { clientId }; await this.callRestApi('client/deleteClient', data); } // =========== API for HM Home =========== async homeHeatingActivateAbsenceWithPeriod(endTime) { let data = { endTime }; await this.callRestApi('home/heating/activateAbsenceWithPeriod', data); } async homeHeatingActivateAbsenceWithDuration(duration) { let data = { duration }; await this.callRestApi('home/heating/activateAbsenceWithDuration', data); } async homeHeatingActivateAbsencePermanent() { await this.callRestApi('home/heating/activateAbsencePermanent'); } async homeHeatingDeactivateAbsence() { await this.callRestApi('home/heating/deactivateAbsence'); } async homeHeatingActivateVacation(temperature, endtime) { let data = { temperature, endtime }; await this.callRestApi('home/heating/activateVacation', data); } async homeHeatingDeactivateVacation() { await this.callRestApi('home/heating/deactivateVacation'); } async homeSetIntrusionAlertThroughSmokeDetectors(intrusionAlertThroughSmokeDetectors) { let data = { intrusionAlertThroughSmokeDetectors }; await this.callRestApi('home/security/setIntrusionAlertThroughSmokeDetectors', data); } async homeSetZonesActivation(internal, external) { let data = { zonesActivation: { INTERNAL: internal, EXTERNAL: external } }; await this.callRestApi('home/security/setZonesActivation', data); } } module.exports = HmCloudAPI;