homebridge-nibe
Version:
Homebridge plugin for Nibe services
266 lines (265 loc) • 9.78 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MyUplinkApiFetcher = void 0;
const axios_1 = __importDefault(require("axios"));
const events_1 = require("events");
const Cache_1 = require("../util/Cache");
const moment_1 = __importDefault(require("moment"));
const consts = {
baseUrl: 'https://api.myuplink.com',
scope: 'READSYSTEM WRITESYSTEM',
grantType: 'client_credentials',
timeout: 45000,
userAgent: 'homebridge-nibe',
renewBeforeExpiry: 5 * 60 * 1000,
allowedParameters: [40067, 40004, 44362, 40013, 40014, 40008, 40025, 40026, 40075, 40183, 48132, 43437],
};
class MyUplinkApiFetcher extends events_1.EventEmitter {
constructor(options, log) {
super();
this.cache = new Cache_1.Cache();
this.currentlySetting = [];
this.options = options;
this.log = log;
axios_1.default.defaults.baseURL = consts.baseUrl;
axios_1.default.defaults.headers.common['user-agent'] = consts.userAgent;
axios_1.default.defaults.timeout = consts.timeout;
}
start() {
if (this.interval != null) {
return;
}
this.active = false;
const exec = () => {
if (this.active) {
return;
}
this.active = true;
this.fetch().then(() => {
this.active = false;
});
};
this.interval = setInterval(exec, this.options.interval * 1000);
exec();
}
stop() {
if (this.interval == null) {
return;
}
clearInterval(this.interval);
this.interval = null;
}
async fetch() {
this.log.debug('Fetch data.');
try {
if (this.isTokenExpired()) {
this.log.debug('Token is expired / expires soon - refreshing');
const token = await this.getToken();
this.setSession(token);
}
if (this.systems == null) {
this.systems = await this.fetchSystems();
}
for (const system of this.systems) {
const subscriptions = await this.fetchPremiumSubscriptions(system.systemId);
for (const device of system.devices) {
const deviceInfo = await this.fetchDeviceInfo(device.id);
try {
const parameters = await this.fetchData(device);
const data = MyUplinkApiFetcher.mapData(system, subscriptions, device, deviceInfo, parameters);
if (data) {
this.log.debug(`Prepared data:\n${JSON.stringify(data)}`);
this._onData(data);
}
}
catch (error) {
this._onError(error);
}
}
}
this.log.debug('All data fetched.');
}
catch (error) {
this._onError(error);
}
}
async getToken() {
var _a;
this.log.debug('token()');
const body = {
client_id: this.options.clientId,
client_secret: this.options.clientSecret,
grant_type: consts.grantType,
scope: consts.scope,
};
const url = '/oauth/token';
try {
const now = Date.now();
const { data } = await axios_1.default.post(url, new URLSearchParams(body).toString(), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
if (this.options.showApiResponse) {
this.log.info('Nibe data from ' + url + ': ' + JSON.stringify(data));
}
const expiresIn = (_a = data.expires_in) !== null && _a !== void 0 ? _a : 3600;
data.expires_at = now + expiresIn * 1000;
return data;
}
catch (error) {
throw this.checkError(url, error);
}
}
async fetchSystems() {
this.log.debug('Fetch units.');
const response = await this.cache.get('/v2/systems/me', 30, 'MINUTES', async () => {
return await this.getFromMyUplink('/v2/systems/me');
});
this.log.debug(`${response.systems.length} units fetched.`);
return response.systems;
}
async fetchDeviceInfo(id) {
this.log.debug('Fetch device info.');
return await this.getFromMyUplink(`/v2/devices/${id}`);
}
async fetchPremiumSubscriptions(id) {
this.log.debug('Fetch premium subscriptions info.');
const response = await this.cache.get(`/v2/systems/${id}/subscriptions`, 10, 'MINUTES', async () => {
return await this.getFromMyUplink(`/v2/systems/${id}/subscriptions`);
});
if (!response) {
return [];
}
return response.subscriptions
.filter(s => (0, moment_1.default)(s.validUntil).isAfter((0, moment_1.default)()))
.map(s => s.type);
}
async fetchData(device) {
this.log.debug('Fetch units.');
const response = await this.getFromMyUplink(`/v2/devices/${device.id}/points`, {
parameters: consts.allowedParameters.join(','),
});
this.log.debug(`${response.length} parameters fetched.`);
return response;
}
static mapData(system, subscriptions, device, deviceInfo, response) {
var _a, _b;
return {
system: {
systemId: system.systemId,
name: system.name,
premiumSubscriptions: subscriptions,
},
device: {
id: device.id,
name: device.product.name,
serialNumber: device.product.serialNumber,
firmwareUpdateAvailable: ((_a = deviceInfo.firmware) === null || _a === void 0 ? void 0 : _a.currentFwVersion) !== ((_b = deviceInfo.firmware) === null || _b === void 0 ? void 0 : _b.desiredFwVersion),
},
parameters: response.map(p => {
return {
id: p.parameterId,
name: p.parameterName,
unit: p.parameterUnit,
value: p.value,
};
}),
};
}
async getFromMyUplink(url, params = {}) {
this.log.debug(`GET ${url}, params: ${JSON.stringify(params)}`);
try {
const { data } = await axios_1.default.get(url, {
headers: {
Authorization: 'Bearer ' + this.getSession('access_token'),
},
params,
});
if (this.options.showApiResponse) {
this.log.info('Nibe data from ' + url + ': ' + JSON.stringify(data));
}
return data;
}
catch (error) {
throw this.checkError(url, error);
}
}
async setValue(deviceId, paramId, value) {
const key = deviceId + paramId + JSON.stringify(value);
if (this.currentlySetting[key]) {
return;
}
this.active = true;
this.currentlySetting[key] = true;
const url = `/v2/devices/${deviceId}/points`;
const body = {};
body[paramId] = value;
this.log.debug(`PUT ${url}, params: ${JSON.stringify(body)}`);
try {
axios_1.default.patch(url, body, {
headers: {
Authorization: 'Bearer ' + this.getSession('access_token'),
},
}).then(result => {
if (this.options.showApiResponse) {
this.log.info('Nibe data from ' + url + ': ' + JSON.stringify(result.data));
}
}).finally(() => {
delete this.currentlySetting[key];
this.active = false;
this.fetch();
});
}
catch (error) {
this.log.error(`error from ${url}: ${JSON.stringify(error)}`);
}
}
checkError(url, error) {
this.log.error(`error from ${url}`);
if (axios_1.default.isAxiosError(error)) {
const axiosError = error;
if (axiosError.response != null) {
if (axiosError.response.status === 401) {
this.clearSession();
}
if (axiosError.response.data != null) {
const responseText = JSON.stringify(axiosError.response.data, null, ' ');
const errorMessage = `${axiosError.response.statusText}: ${responseText}`;
return new Error(errorMessage);
}
else {
return new Error(axiosError.response.statusText);
}
}
}
return error;
}
getSession(key) {
this.log.debug('Get session.');
return this.auth ? this.auth[key] : null;
}
setSession(auth) {
this.log.debug('Set session.');
this.auth = auth;
}
clearSession() {
this.log.debug('Clear session.');
this.setSession({});
}
isTokenExpired() {
const expired = (Number(this.getSession('expires_at')) || 0) < Date.now() + consts.renewBeforeExpiry;
this.log.debug('Is token expired: ' + expired);
return expired;
}
_onData(data) {
this.emit('data', data);
}
_onError(error) {
this.emit('error', error);
}
}
exports.MyUplinkApiFetcher = MyUplinkApiFetcher;