homebridge-warmup-wifi-thermostats
Version:
The Warump Wifi Thermostats plugin allows you to access and control Warmup Smart Wifi thermostats like the 6iE, 4iE and Element.
288 lines (253 loc) • 7.81 kB
JavaScript
import axios from 'axios';
const TOKEN_URL = 'https://api.warmup.com/apps/app/v1';
const APP_TOKEN = 'M=;He<Xtg"$}4N%5k{$:PD+WA"]D<;#PriteY|VTuA>_iyhs+vA"4lic{6-LqNM:';
const HEADER = {
'user-agent': 'WARMUP_APP',
'accept-encoding': 'br, gzip, deflate',
'accept': '*/*',
'Connection': 'keep-alive',
'content-type': 'application/json',
'app-token': APP_TOKEN,
'app-version': '1.8.1',
'accept-language': 'de-de',
};
let WarmupAccessToken = null;
let LocId = null;
export class WarmupThermostats {
constructor(options, callback) {
this.log = options.log;
this._username = options.username;
this._password = options.password;
this._location_name = options.location;
this._room_name = options.room;
this._target_temperature = options.target_temp;
this._refresh = options.refresh;
this._duration = options.duration;
this.room = [];
this.setup_finished = false;
this._generateAccessToken(() => {
this._getLocations(() => {
this.getStatus((err, rooms) => {
callback(null, rooms);
});
});
});
this.refreshInterval = setInterval(this.pollDevices.bind(this), (this._refresh * 1000) / 2);
}
pollDevices() {
this.getStatus(() => { });
}
_sendRequest(body, callback) {
axios({
method: 'POST',
url: TOKEN_URL,
timeout: 10000,
// strictSSL: false,
headers: HEADER,
data: body,
})
.then(response => {
if (response.status !== 200) {
const error = new Error(`HTTP Error: ${response.status}`);
this.log.error(error);
return callback(error);
}
// handle success
callback(null, response.data);
})
.catch(error => {
this.log.error(error);
callback(error);
});
}
_generateAccessToken(callback) {
const body = {
request: {
email: this._username,
password: this._password,
method: 'userLogin',
appId: 'WARMUP-APP-V001',
},
};
this._sendRequest(body, (err, json) => {
if (err) {
return callback(err);
}
WarmupAccessToken = json.response.token;
callback(null);
});
}
_getLocations(callback) {
if (!WarmupAccessToken) {
return callback(new Error('Missing access token.'));
}
const body = {
account: {
email: this._username,
token: WarmupAccessToken,
},
request: {
method: 'getLocations',
},
};
this._sendRequest(body, (err, json) => {
if (err) {
return callback(err);
}
LocId = json.response.locations[0]?.id;
callback(null);
});
}
getStatus(callback) {
if (!LocId || !WarmupAccessToken) {
return callback(new Error('Missing LocId or AccessToken.'));
}
const body = {
account: { email: this._username, token: WarmupAccessToken },
request: { method: 'getRooms', locId: LocId },
};
this._sendRequest(body, (err, json) => {
if (err) {
return callback(err);
}
const locMode = json.response.locMode; // e.g. "off", "frost", etc.
const rooms = json.response.rooms || [];
rooms.forEach(room => {
// If location itself is set to "off", force each room to "off"
if (locMode === 'off') {
room.runMode = 'off';
}
this.room[room.roomId] = room;
});
callback(null, rooms);
});
}
_setLocationMode(locationId, locMode, callback) {
const body = {
account: {
email: this._username,
token: WarmupAccessToken,
},
request: {
method: 'setModes',
values: {
locId: locationId,
locMode: locMode,
holEnd: '-',
holStart: '-',
holTemp: '-',
geoMode: '0',
},
},
};
// When setting off, add fixedTemp as an empty string
if (locMode === 'off') {
body.request.values.fixedTemp = '';
}
// Clear cached data for this location (if applicable)
this.room[locationId] = null;
this._sendRequest(body, (err, json) => {
if (err) {
return callback(err);
}
this.getStatus((err, rooms) => {
if (err) {
this.log.error('[ERROR _setLocationMode] Failed to refresh devices after state change:', err);
} else {
this.log.debug('[DEBUG _setLocationMode] Successfully refreshed devices after state change:', rooms);
}
});
callback(null, json);
});
}
_setTemperature(roomId, mode, temperature, callback) {
if (!WarmupAccessToken) {
return callback(new Error('Missing access token'));
}
const body = {
account: {
email: this._username,
token: WarmupAccessToken,
},
request: {
method: 'setProgramme',
roomId: roomId,
roomMode: mode, // "prog" (AUTO) or "fixed" (MANUAL)
},
};
// If a temperature is provided, include it. (API expects a string, in tenths of a degree, padded to three digits.)
if (temperature !== undefined && temperature !== null) {
body.request.fixed = {
fixedTemp: String(parseInt(temperature * 10, 10)).padStart(3, '0'),
};
}
this._sendRequest(body, (err, json) => {
if (err) {
return callback(err);
}
this.getStatus((err, rooms) => {
if (err) {
this.error('[ERROR _setTemperature] Failed to refresh devices after state change:', err);
} else {
this.log.debug('[DEBUG _setTemperature] Successfully refreshed devices after state change:', rooms);
}
});
callback(null, json);
});
}
setOverride(roomId, temperature, callback) {
const durationMinutes = this._duration || 60; // Default to 60 minutes if not configured
const until = new Date(Date.now() + durationMinutes * 60000).toISOString().slice(11, 16); // Time in HH:mm format
const body = {
account: {
email: this._username,
token: WarmupAccessToken,
},
request: {
method: 'setOverride',
rooms: [roomId],
type: 3, // Assumed constant for override
temp: String(parseInt(temperature * 10, 10)).padStart(3, '0'),
until: until,
},
};
this._sendRequest(body, (err, json) => {
if (err) {
return callback(err);
}
this.getStatus((err, rooms) => {
if (err) {
this.error('[ERROR setOverride] Failed to refresh devices after state change:', err);
} else {
this.log.info('[DEBUG setOverride] Successfully refreshed devices after state change:', rooms);
}
});
callback(null, json);
});
}
setTemperatureToAuto(roomId, callback) {
// Set room mode to AUTO; we use "prog" here.
return this._setTemperature(roomId, 'prog', null, callback);
}
setTemperatureToManual(roomId, callback) {
// Set room mode to manual but do not change the setpoint.
return this._setTemperature(roomId, 'fixed', null, callback);
}
setNewTemperature(roomId, newTemperature, callback) {
// Set room mode to manual AND update the fixed temperature.
return this._setTemperature(roomId, 'fixed', newTemperature, callback);
}
setLocationToFrost(locationId, callback) {
// Set the location mode to "frost"
this._setLocationMode(locationId, 'frost', callback);
}
setLocationToOff(locationId, callback) {
// Set the location mode to "off"
this.log.debug('Sending setLocationToOff for location', locationId);
this._setLocationMode(locationId, 'off', callback);
}
destroy() {
this.log.info('Destroying Warmup Thermostats');
clearInterval(this.refreshInterval);
}
}