UNPKG

homebridge-hsp

Version:

A plugin to control your HSP pellet stove with homebridge.

422 lines 19.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const node_fetch_1 = require("node-fetch"); const node_fetch_2 = __importDefault(require("node-fetch")); const md5_1 = __importDefault(require("md5")); class HspPlatformAccessory { constructor(platform, accessory) { this.platform = platform; this.accessory = accessory; this.state = { On: false, Brightness: 0, Cooling: 100, }; this.url = 'localhost'; this.msg = { payload: { start: false, weekProgramStart: false, mode: 'unknown', isTemp: 0.0, setTemp: 0.0, ecoMode: false, nonce: 'unknown', error: false, meta: { softwareVersion: 'unknown', language: 'unknown', type: 'HSP-1/2', serialNumber: '0000000', }, pgi: false, ignitions: -1, onTime: -1, consumption: -1, maintenance: -1, cleaning: -1, zone: 0, }, }; this.url = this.platform.config.host; const interval = this.platform.config.interval * 1000; // set accessory information this.accessory.getService(this.platform.Service.AccessoryInformation) .setCharacteristic(this.platform.Characteristic.Manufacturer, 'Haas+Sohn') .setCharacteristic(this.platform.Characteristic.Model, this.platform.config.type) //'HSP 2.17 Home II') .setCharacteristic(this.platform.Characteristic.SerialNumber, this.platform.config.serial); this.runService = this.accessory.getService(this.platform.Service.Switch) || this.accessory.addService(this.platform.Service.Switch); this.runService.setCharacteristic(this.platform.Characteristic.Name, 'Ofen eingeschalten'); this.runService.getCharacteristic(this.platform.Characteristic.On) .on('set', this.setRunningOn.bind(this)) .on('get', this.getRunningOn.bind(this)); this.actualTempService = this.accessory.getService('Raumtemperatur') || this.accessory.addService(this.platform.Service.TemperatureSensor, 'Raumtemperatur', 'HSP-isTemp'); this.actualTempService.getCharacteristic(this.platform.Characteristic.CurrentTemperature) .on('get', this.getActualTemperature.bind(this)); this.stateService = this.accessory.getService('Status') || this.accessory.addService(this.platform.Service.Lightbulb, 'Status', 'HSP-state'); this.stateService.getCharacteristic(this.platform.Characteristic.Brightness) .on('set', this.setStateBrightness.bind(this)) .on('get', this.getStateBrightness.bind(this)); this.stateService.getCharacteristic(this.platform.Characteristic.On) .on('set', this.setState.bind(this)) .on('get', this.getState.bind(this)); this.weekPrgService = this.accessory.getService('Wochenprogramm') || this.accessory.addService(this.platform.Service.Switch, 'Wochenprogramm', 'HSP-weekProgrammStart'); this.weekPrgService.getCharacteristic(this.platform.Characteristic.On) .on('set', this.setWeekProgrammOn.bind(this)) .on('get', this.getWeekProgrammOn.bind(this)); this.ecoModeService = this.accessory.getService('Eco-Mode') || this.accessory.addService(this.platform.Service.Switch, 'Eco-Mode', 'HSP-ecoMode'); this.ecoModeService.getCharacteristic(this.platform.Characteristic.On) .on('set', this.setEcoModeOn.bind(this)) .on('get', this.getEcoModeOn.bind(this)); this.setTempService = this.accessory.getService('Solltemperatur') || this.accessory.addService(this.platform.Service.InputSource, 'Solltemperatur', 'HSP-setTemp'); this.setTempService.getCharacteristic(this.platform.Characteristic.ConfiguredName) .on('get', this.handleConfiguredNameGet.bind(this)) .on('set', this.handleConfiguredNameSet.bind(this)); this.setTempService.getCharacteristic(this.platform.Characteristic.InputSourceType) .on('get', this.handleInputSourceTypeGet.bind(this)); this.setTempService.getCharacteristic(this.platform.Characteristic.IsConfigured) .on('get', this.handleIsConfiguredGet.bind(this)); this.setTempService.getCharacteristic(this.platform.Characteristic.Name) .on('get', this.handleNameGet.bind(this)); this.setTempService.getCharacteristic(this.platform.Characteristic.CurrentVisibilityState) .on('get', this.handleCurrentVisibilityStateGet.bind(this)); this.cleaningService = this.accessory.getService('Reinigung') || this.accessory.addService(this.platform.Service.BatteryService, 'Reinigung', 'HSP-cleaning'); this.cleaningService.getCharacteristic(this.platform.Characteristic.BatteryLevel) .on('get', this.handleCleaningLevelGet.bind(this)); this.cleaningService.getCharacteristic(this.platform.Characteristic.ChargingState) .on('get', this.handleCleaningChargingStateGet.bind(this)); this.cleaningService.getCharacteristic(this.platform.Characteristic.StatusLowBattery) .on('get', this.handleCleaningStatusLowGet.bind(this)); this.cleaningService.getCharacteristic(this.platform.Characteristic.Name) .on('get', this.handleCleaningNameGet.bind(this)); this.maintenanceService = this.accessory.getService('Wartung') || this.accessory.addService(this.platform.Service.BatteryService, 'Wartung', 'HSP-maintenance'); this.maintenanceService.getCharacteristic(this.platform.Characteristic.BatteryLevel) .on('get', this.handleMaintenanceLevelGet.bind(this)); this.maintenanceService.getCharacteristic(this.platform.Characteristic.ChargingState) .on('get', this.handleMaintenanceChargingStateGet.bind(this)); this.maintenanceService.getCharacteristic(this.platform.Characteristic.StatusLowBattery) .on('get', this.handleMaintenanceStatusLowGet.bind(this)); this.maintenanceService.getCharacteristic(this.platform.Characteristic.Name) .on('get', this.handleMaintenanceNameGet.bind(this)); /** * Updating characteristics values asynchronously. * */ setInterval(async () => { this.fetchInformation(); }, interval); } async fetchInformation() { //const response = await fetch(`http://${this.url}/status.cgi`); //const data = await response.json(); await node_fetch_2.default(`http://${this.url}/status.cgi`) .then(response => { if (response.ok) { response.json().then((data) => { //let changed = this.msg.payload.weekProgramStart!=data.wprg; //this.platform.log.debug('Weekprogramm changed',changed); this.hspUpdatePayload(data); //this.platform.log.debug('response:',this.msg.payload); // push the new value to HomeKit this.actualTempService.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.msg.payload.isTemp.toFixed(1)); this.setTempService.updateCharacteristic(this.platform.Characteristic.InputSourceType.ConfiguredName, this.msg.payload.setTemp.toFixed(0)); this.runService.updateCharacteristic(this.platform.Characteristic.On, this.msg.payload.start); this.weekPrgService.updateCharacteristic(this.platform.Characteristic.On, this.msg.payload.weekProgramStart); this.ecoModeService.updateCharacteristic(this.platform.Characteristic.On, this.msg.payload.ecoMode); this.stateService.updateCharacteristic(this.platform.Characteristic.Brightness, this.getHeatingState()); this.platform.log.debug('Temperature (ACTUAL/SET):', this.msg.payload.isTemp.toFixed(1), this.msg.payload.setTemp.toFixed(1)); }); } else { this.platform.log.debug('RESPONSE is broken. Waiting for next request.'); } }).catch(error => { this.platform.log.debug('FETCH-ERROR from url. Waiting for next request...', error.message); }); } /** * REQUIRED - This must return an array of the services you want to expose. * This method must be named "getServices". */ getServices() { return [ this.runService, this.weekPrgService, this.ecoModeService, this.actualTempService, this.stateService, this.setTempService, ]; } async setWeekProgrammOn(value, callback) { if (this.msg.payload.weekProgramStart !== value) { this.msg.payload.weekProgramStart = value; this.platform.log.debug('Set WeekProgrammStart On ->', value); const nonce = await this.hspGetNonce(); //this.platform.log.debug("nonce: ",nonce); const hash = this.hspCalculatePin(nonce, this.platform.config.pin); const setData = {}; setData['wprg'] = value; const dataToSend = JSON.stringify(setData); const header = this.hspCreateRequestHeader(this.url, hash); const response = await node_fetch_2.default(`http://${this.url}/status.cgi`, { headers: header, method: 'POST', body: dataToSend, }); const data = await response.json(); this.hspUpdatePayload(data); } callback(null); } getWeekProgrammOn(callback) { const isOn = this.msg.payload.weekProgramStart; this.platform.log.debug('Get WeekProgrammStart On ->', isOn); callback(null, isOn); } async setEcoModeOn(value, callback) { if (this.msg.payload.weekProgramStart !== value) { this.msg.payload.ecoMode = value; this.platform.log.debug('Set Eco-Mode On ->', value); const nonce = await this.hspGetNonce(); //this.platform.log.debug("nonce: ",nonce); const hash = this.hspCalculatePin(nonce, this.platform.config.pin); const setData = {}; setData['eco_mode'] = value; const dataToSend = JSON.stringify(setData); const header = this.hspCreateRequestHeader(this.url, hash); const response = await node_fetch_2.default(`http://${this.url}/status.cgi`, { headers: header, method: 'POST', body: dataToSend, }); const data = await response.json(); this.hspUpdatePayload(data); } callback(null); } getEcoModeOn(callback) { const isOn = this.msg.payload.ecoMode; this.platform.log.debug('Get Eco-Mode On ->', isOn); callback(null, isOn); } async setRunningOn(value, callback) { // implement your own code to turn your device on/off if (this.msg.payload.start !== value) { this.msg.payload.start = value; this.platform.log.debug('Set Running On ->', value); const nonce = await this.hspGetNonce(); //this.platform.log.debug("nonce: ",nonce); const hash = this.hspCalculatePin(nonce, this.platform.config.pin); const setData = {}; setData['prg'] = value; const dataToSend = JSON.stringify(setData); const header = this.hspCreateRequestHeader(this.url, hash); const response = await node_fetch_2.default(`http://${this.url}/status.cgi`, { headers: header, method: 'POST', body: dataToSend, }); const data = await response.json(); this.hspUpdatePayload(data); } callback(null); } getRunningOn(callback) { // implement your own code to check if the device is on const isOn = this.msg.payload.start; this.platform.log.debug('Get Running Running On ->', isOn); callback(null, isOn); } setStateBrightness(value, callback) { this.platform.log.debug('Cannot set state with homekit'); callback(1); } getHeatingState() { switch (this.msg.payload.mode) { case 'start': if (this.msg.payload.zone !== undefined) { this.state.Brightness = (95.0 / 20.0) * this.msg.payload.zone; } else { this.state.Brightness = 11; } this.state.Cooling = 100; break; case 'heating': this.state.Brightness = 100; break; case 'cooling': this.state.Cooling -= 1; this.state.Cooling = this.state.Cooling > 0 ? this.state.Cooling : 1; this.state.Brightness = this.state.Cooling; break; case 'standby': default: this.state.Brightness = 0; this.state.Cooling = 0; break; } this.platform.log.debug('HEATING STATE: ', this.msg.payload.mode, this.msg.payload.zone, Math.round(this.state.Brightness)); return Math.round(this.state.Brightness); } getStateBrightness(callback) { callback(null, this.getHeatingState()); } setState(value, callback) { this.platform.log.debug('Cannot set state with homekit'); callback(1); } getState(callback) { callback(null, this.getHeatingState() > 0 ? 1 : 0); } getActualTemperature(callback) { callback(null, this.msg.payload.isTemp); } handleConfiguredNameGet(callback) { this.platform.log.debug('Triggered GET ConfiguredName'); // set this to a valid value for ConfiguredName const currentValue = this.msg.payload.setTemp.toString(); callback(null, currentValue); } async handleConfiguredNameSet(value, callback) { this.platform.log.debug('Triggered SET ConfiguredName:', value); if (Number.isInteger(parseInt(value)) && this.msg.payload.setTemp !== value && value > 15) { this.msg.payload.setTemp = value; this.platform.log.debug('Set Temperature On ->', value); const nonce = await this.hspGetNonce(); const hash = this.hspCalculatePin(nonce, this.platform.config.pin); const setData = {}; setData['sp_temp'] = parseInt(value); const dataToSend = JSON.stringify(setData); const header = this.hspCreateRequestHeader(this.url, hash); const response = await node_fetch_2.default(`http://${this.url}/status.cgi`, { headers: header, method: 'POST', body: dataToSend, }); const data = await response.json(); this.hspUpdatePayload(data); callback(null); } else { callback(new Error('Input is not a correct number.')); } } handleInputSourceTypeGet(callback) { callback(null, this.platform.Characteristic.InputSourceType.OTHER); } handleIsConfiguredGet(callback) { callback(null, this.platform.Characteristic.IsConfigured.CONFIGURED); } handleNameGet(callback) { this.platform.log.debug('Triggered GET Name'); const currentValue = 'Soll-Temperature'; callback(null, currentValue); } handleCurrentVisibilityStateGet(callback) { this.platform.log.debug('Triggered GET CurrentVisibilityState'); const currentValue = 1; callback(null, currentValue); } handleFilterChangeIndicationGet(callback) { this.platform.log.debug('Triggered GET FilterChangeIndication'); const currentValue = 1; callback(null, currentValue); } handleCleaningLevelGet(callback) { //Cleaning has to be done after every 20h const minutes_left = (this.msg.payload.cleaning > (60 * 20)) ? 1200 : this.msg.payload.cleaning; callback(null, Math.round(minutes_left / 12)); } handleCleaningChargingStateGet(callback) { callback(null, this.platform.Characteristic.ChargingState.NOT_CHARGING); } handleCleaningStatusLowGet(callback) { callback(null, this.msg.payload.cleaning < 120 ? this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL); } handleCleaningNameGet(callback) { callback(null, 'Reinigung Brennerraum'); } handleMaintenanceLevelGet(callback) { //Maintenance has to be done after every 1000kg const minutes_left = (this.msg.payload.maintenance > 1000) ? 1000 : this.msg.payload.cleaning; callback(null, Math.round(minutes_left / 10)); } handleMaintenanceChargingStateGet(callback) { callback(null, this.platform.Characteristic.ChargingState.NOT_CHARGING); } handleMaintenanceStatusLowGet(callback) { callback(null, this.msg.payload.maintenance < 10 ? this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_LOW : this.platform.Characteristic.StatusLowBattery.BATTERY_LEVEL_NORMAL); } handleMaintenanceNameGet(callback) { callback(null, 'Wartung Pelletofen'); } /** * HSP POST functions */ hspUpdatePayload(data) { this.msg.payload = { start: data.prg, weekProgramStart: data.wprg, mode: data.mode, isTemp: data.is_temp, setTemp: data.sp_temp, ecoMode: data.eco_mode, nonce: data.meta.nonce, error: data.error.length <= 0 ? false : data.error, meta: { softwareVersion: data.meta.sw_version, language: data.meta.language, type: data.meta.typ, serialNumber: data.meta.sn, }, pgi: data.pgi, ignitions: data.ignitions, onTime: data.on_time, consumption: data.consumption, maintenance: data.maintenance_in, cleaning: data.cleaning_in, zone: data.zone, }; } async hspGetNonce() { const response = await node_fetch_2.default(`http://${this.url}/status.cgi`); const data = await response.json(); return data.meta.nonce; } hspCalculatePin(nonce, pin) { return md5_1.default(nonce + md5_1.default(pin)); } hspCreateRequestHeader(url, hash) { return new node_fetch_1.Headers({ 'Host': url, 'Accept': '*/*', 'Proxy-Connection': 'keep-alive', 'X-BACKEND-IP': 'https://app.hsp.com', 'Accept-Language': 'de-DE;q=1.0, en-DE;q=0.9', 'Accept-Encoding': 'gzip;q=1.0, compress;q=0.5', 'token': '32bytes', 'Content-Type': 'application/json', 'User-Agent': 'ios', 'Connection': 'keep-alive', 'X-HS-PIN': hash, }); } } exports.default = HspPlatformAccessory; //# sourceMappingURL=platformAccessory.js.map