salus-it600-pack-v2
Version:
Improved Salus iT600 integration library with UG600 gateway support and better error handling.
212 lines (179 loc) • 8.25 kB
JavaScript
"use strict";
const https = require('https')
var parse = require('fast-json-parse')
const baseUrl = "eu.premium.salusconnect.io";
const loginUrl = "/users/sign_in.json?";
const apiVersion = "/apiv1";
const devicesUrl = "/devices.json?";
const prop = "ep_9:sIT600TH:";
const propTemperature = prop + "LocalTemperature_x100";
const propHumidity = prop + "SunnySetpoint_x100";
const propHeatingSetpoint = prop + "HeatingSetpoint_x100";
const propRunningMode = prop + "RunningState";
const propState = prop + "HoldType";
const propUpdateTargetTemperature = prop + "SetHeatingSetpoint_x100";
const propUpdateState = prop + "SetHoldType";
class Index {
constructor({username, password, thermostatModels=[]}) {
this.username = username;
this.password = password;
this.thermostatModels = thermostatModels.map(function (e) { return e.toUpperCase().replace(/^VS(10|20)\w+/, "IT600THERMHW") });
}
async getToken() {
await this.login();
function Token(value, creationDay) {
this.value = value;
this.creationDay = creationDay;
}
return new Token(this.token, new Date().getDate());
}
login() {
return new Promise((resolve, reject) => {
const data = JSON.stringify({ user: { email: this.username, password: this.password } });
const options = {
host: baseUrl,
port: 443,
path: loginUrl + this.appendTimestamp(),
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length
}
};
const req = https.request(options, res => {
let body = '';
res.on('data', chunk => {
body += chunk;
});
res.on('end', () => {
const parsed = parse(body);
if (!parsed || !parsed.value || !parsed.value.access_token) {
console.error("[salus-it600-pack-v2] Login failed – access_token not found.");
console.error("Raw response:\n", body);
return reject(new Error("Login failed – missing access_token"));
}
this.token = parsed.value.access_token;
resolve(this.token);
});
});
req.on('error', error => {
console.error("[salus-it600-pack-v2] HTTPS request error during login:", error);
reject(error);
});
req.write(data);
req.end();
});
}
async getDevices(token) {
if (token !== null) {
const allDevices = await this.getData(token, apiVersion + devicesUrl + this.appendTimestamp());
function Item(id, name, current, target, heating, humidity, state) {
this.id = id;
this.name = name;
this.current = current;
this.target = target;
this.heating = heating;
this.humidity = humidity;
this.state = state;
}
const result = [];
try {
for (const e of allDevices.value) {
const device = e.device;
if (this.thermostatModels.includes(device.oem_model.toUpperCase())) {
const current = this.getData(token, apiVersion + "/dsns/" + device.dsn + "/properties/" + propTemperature + ".json?" + this.appendTimestamp());
const target = this.getData(token, apiVersion + "/dsns/" + device.dsn + "/properties/" + propHeatingSetpoint + ".json?" + this.appendTimestamp());
const heating = this.getData(token, apiVersion + "/dsns/" + device.dsn + "/properties/" + propRunningMode + ".json?" + this.appendTimestamp());
const humidity = this.getData(token, apiVersion + "/dsns/" + device.dsn + "/properties/" + propHumidity + ".json?" + this.appendTimestamp());
const state = this.getData(token, apiVersion + "/dsns/" + device.dsn + "/properties/" + propState + ".json?" + this.appendTimestamp());
await Promise.all([current, target, heating, humidity, state]).then((values) => {
result.push(new Item(device.dsn, device.product_name, values[0].value.property.value / 100, values[1].value.property.value / 100, values[2].value.property.value != 0 ? true : false, values[3].value.property.value, values[4].value.property.value));
});
}
}
return result;
} catch (error) {
console.error(error);
}
} else {
console.warn("Salus login failed");
}
}
async getDeviceCurrentTemperature(token, id) {
return (await this.getData(token, apiVersion + "/dsns/" + id + "/properties/" + propTemperature + ".json?" + this.appendTimestamp())).value.property.value / 100;
}
async getDeviceTargetTemperature(token, id) {
return (await this.getData(token, apiVersion + "/dsns/" + id + "/properties/" + propHeatingSetpoint + ".json?" + this.appendTimestamp())).value.property.value / 100;
}
async getDeviceHeating(token, id) {
return (await this.getData(token, apiVersion + "/dsns/" + id + "/properties/" + propRunningMode + ".json?" + this.appendTimestamp())).value.property.value !== 0 ? true : false;
}
async getDeviceCurrentRelativeHumidity(token, id) {
return (await this.getData(token, apiVersion + "/dsns/" + id + "/properties/" + propHumidity + ".json?" + this.appendTimestamp())).value.property.value;
}
async getDeviceState(token, id) {
return (await this.getData(token, apiVersion + "/dsns/" + id + "/properties/" + propState + ".json?" + this.appendTimestamp())).value.property.value;
}
getData(token, path) {
return new Promise((resolve, reject) => {
const options = {
host: baseUrl,
port: 443,
path: path,
method: 'GET',
headers: {
'Authorization': 'Bearer ' + token
}
}
const req = https.request(options, res => {
res.on('data', d => {
resolve(parse(d.toString()));
})
})
req.on('error', error => {
reject(error);
})
req.end()
})
}
setData(token, path, data) {
return new Promise((resolve, reject) => {
const options = {
host: baseUrl,
port: 443,
path: path,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length,
'Authorization': 'Bearer ' + token
}
}
const req = https.request(options, res => {
resolve(res.statusCode);
})
req.on('error', error => {
console.error(error)
})
req.write(data)
req.end()
})
}
async updateTemperature(token, id, temperature) {
const data = JSON.stringify({"datapoint": {"value": temperature * 100}});
return (await this.setData(token, apiVersion + "/dsns/" + id + "/properties/" + propUpdateTargetTemperature + "/datapoints.json?" + this.appendTimestamp(), data))
}
async updateState(token, id, state) {
/*
standby = 7
hold = 2
auto/ program = 0
*/
const data = JSON.stringify({"datapoint": {"value": state}});
return (await this.setData(token, apiVersion + "/dsns/" + id + "/properties/" + propUpdateState + "/datapoints.json?" + this.appendTimestamp(), data))
}
appendTimestamp() {
return "timestamp=" + new Date().getTime();
}
}
module.exports = Index;