homebridge-velux-active
Version:
Homebridge plugin for controlling velux blinds and windows
193 lines • 8.6 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.VeluxActivePlatform = void 0;
const node_fetch_1 = __importDefault(require("node-fetch"));
const settings_1 = require("./settings");
const VeluxAccessory_1 = require("./VeluxAccessory");
class VeluxActivePlatform {
constructor(log, config, api) {
this.log = log;
this.config = config;
this.api = api;
this.baseUrl = 'https://app.velux-active.com/';
this.clientId = '5931426da127d981e76bdd3f'; // clientId of the app - not personal
this.clientSecret = '6ae2d89d15e767ae5c56b456b452d319'; // clientSecret of the app - not personal
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
// this is used to track restored cached accessories
this.accessories = [];
this.apiToken = '';
this.refreshToken = '';
this.homeId = '';
this.devices = [];
this.lastTokenRefresh = undefined;
this.tokenWillExpire = undefined;
this.lastDeviceUpdate = undefined;
this.refreshing = false;
this.log.debug('Finished initializing platform:', this.config.name);
this.api.on('didFinishLaunching', async () => {
log.debug('Executed didFinishLaunching callback');
let success = false;
let retries = 0;
do {
const retrievedApiKey = await this.retrieveApiKey(config.username, config.password);
if (retrievedApiKey) {
const retrievedHome = await this.retrieveHomeId();
if (retrievedHome) {
success = await this.retrieveDevicesStatus();
}
}
await this.delay(5000);
retries = retries + 1;
} while (!success && retries <= 3);
this.log.info('Init complete, creating devices');
this.createDevices();
});
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async setTokens(response) {
const result = await response.json();
this.apiToken = result.access_token;
this.refreshToken = result.refresh_token;
this.lastTokenRefresh = new Date();
this.tokenWillExpire = new Date();
this.tokenWillExpire.setSeconds(this.tokenWillExpire.getSeconds() + result.expires_in);
this.log.info('Token will expire: ', this.tokenWillExpire);
}
configureAccessory(accessory) {
this.log.info('Loading accessory from cache:', accessory.displayName);
this.accessories.push(accessory);
}
async retrieveApiKey(username, password) {
var _a, _b;
if (this.tokenWillExpire !== undefined && (((_a = this.tokenWillExpire) === null || _a === void 0 ? void 0 : _a.getTime()) - new Date().getTime()) > 0) {
this.log.debug('Token still valid', new Date(), this.tokenWillExpire, (((_b = this.tokenWillExpire) === null || _b === void 0 ? void 0 : _b.getTime()) - new Date().getTime()) > 0);
return true;
}
this.log.debug('Getting new token');
try {
const encoded = encodeURIComponent(username);
const response = await (0, node_fetch_1.default)(this.baseUrl + 'oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `grant_type=password&client_id=${this.clientId}&client_secret=${this.clientSecret}&username=${encoded}&password=${password}&user_prefix=velux`
});
if (!response.ok) {
this.log.error(`Could not retrieve api key. Status ${response.status}`);
return false;
}
await this.setTokens(response);
this.log.info('Successfully retrieved api token');
return true;
}
catch (e) {
this.log.error('Something went wrong while trying to retrieve api key', e);
return false;
}
}
async retrieveNewToken() {
var _a, _b;
if (this.tokenWillExpire !== undefined && (((_a = this.tokenWillExpire) === null || _a === void 0 ? void 0 : _a.getTime()) - new Date().getTime()) > 0) {
this.log.debug('Token still valid', new Date(), this.tokenWillExpire, (((_b = this.tokenWillExpire) === null || _b === void 0 ? void 0 : _b.getTime()) - new Date().getTime()) > 0);
return true;
}
try {
const response = await (0, node_fetch_1.default)(this.baseUrl + 'oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `grant_type=refresh_token&refresh_token=${this.refreshToken}&client_id=${this.clientId}&client_secret=${this.clientSecret}`
});
await this.setTokens(response);
this.log.info('Successfully refreshed token');
return true;
}
catch (e) {
this.log.error('Could not refresh token');
return false;
}
}
async retrieveHomeId() {
try {
const response = await (0, node_fetch_1.default)(this.baseUrl + 'api/gethomedata', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `access_token=${this.apiToken}`
});
const result = await response.json();
this.homeId = result.body.homes[0].id;
this.log.info('Successfully retrieved home id');
return true;
}
catch (e) {
this.log.error('Could not retrieve home id');
return false;
}
}
async retrieveDevicesStatus(delayTime = 2000) {
if (this.refreshing) {
this.log.debug('already refreshing state');
await this.delay(500); // wait for the update
return true;
}
if (this.lastDeviceUpdate !== undefined && (new Date().getTime() - this.lastDeviceUpdate.getTime() < delayTime)) {
this.log.debug('Using cached state as it is not older than 2s');
return true;
}
this.refreshing = true;
try {
await this.retrieveNewToken();
this.lastDeviceUpdate = new Date();
const response = await (0, node_fetch_1.default)(this.baseUrl + 'api/homestatus', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `access_token=${this.apiToken}&home_id=${this.homeId}`
});
const result = await response.json();
this.log.debug('Got devices status', result);
this.devices = result.body.home.modules.filter(m => m.type === 'NXO');
this.log.debug('Successfully retrieved devices from velux');
return true;
}
catch (e) {
this.log.error('Could not retrieve devices from velux', e);
return false;
}
finally {
this.refreshing = false;
}
}
createDevices() {
this.devices.forEach(d => {
const uuid = this.api.hap.uuid.generate(d.id);
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
if (existingAccessory) {
this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
// eslint-disable-next-line no-new
new VeluxAccessory_1.VeluxAccessory(this, existingAccessory, d);
}
else {
this.log.info('Adding new accessory:', d.id);
// eslint-disable-next-line new-cap
const accessory = new this.api.platformAccessory(d.id, uuid);
// eslint-disable-next-line no-new
new VeluxAccessory_1.VeluxAccessory(this, accessory, d);
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
}
});
}
}
exports.VeluxActivePlatform = VeluxActivePlatform;
//# sourceMappingURL=platform.js.map