homebridge-hsp
Version:
A plugin to control your HSP pellet stove with homebridge.
422 lines • 19.8 kB
JavaScript
"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