homebridge-fujitsu-airstage
Version:
A Homebridge plugin to control devices that use the Fujitsu Airstage API.
511 lines (434 loc) • 16.6 kB
JavaScript
'use strict';
const ConfigManager = require('./config-manager');
const PlatformAccessoryManager = require('./platform-accessory-manager');
const accessories = require('./accessories');
const airstage = require('./airstage');
const settings = require('./settings');
class Platform {
accessories = [];
lanDeviceIds = [];
airstageCloudClient = null;
airstageLanClient = null;
constructor(log, config, api, withSetInterval = true) {
this.log = log;
this.config = config;
this.api = api;
// Polyfill for Homebridge < 1.8.0
if (!this.log.success) {
this.log.success = this.log.info;
}
// Initialize platform Airstage clients and accessories
this._init(withSetInterval);
}
_init(withSetInterval) {
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
this.configManager = new ConfigManager(this.config, this.api);
this.accessoryManager = new PlatformAccessoryManager(this);
const tokens = this.configManager.getTokens();
if (this.config.enableCloudControl) {
this.airstageCloudClient = new airstage.cloud.Client(
this.config.region,
this.config.country,
this.config.language,
this.config.email || null,
this.config.password || null,
null,
null,
tokens.accessToken || null,
tokens.accessTokenExpiry || null,
tokens.refreshToken || null
);
if (withSetInterval) {
const cloudPollingInterval = ((this.config.cloudPollingInterval * 1000) * 60);
if (cloudPollingInterval > 0) {
setInterval(
this._refreshAirstageCloudClientCache.bind(this),
cloudPollingInterval
);
}
setInterval(
this._refreshAirstageCloudClientTokenOrAuthenticate.bind(this),
(50 * 60 * 1000) // 50 minutes
);
}
}
if (this.config.enableLanControl) {
this.airstageLanClient = new airstage.lan.Client(
this.config.lanDevices || [],
this.config.lanTemperatureScale
);
if (withSetInterval) {
const lanPollingIntervalUnits = this.config.lanPollingIntervalUnits ?? 'm';
const lanPollingIntervalValue = this.config.lanPollingInterval;
let lanPollingInterval = 0;
if (lanPollingIntervalUnits === 'm') {
// Minutes
lanPollingInterval = (lanPollingIntervalValue * 60 * 1000);
} else if (lanPollingIntervalUnits === 's') {
// Seconds
lanPollingInterval = (lanPollingIntervalValue * 1000);
}
if (lanPollingInterval > 0) {
setInterval(
this._refreshAirstageLanClientCache.bind(this),
lanPollingInterval
);
}
}
}
this.api.on('didFinishLaunching', this.discoverDevices.bind(this));
}
configureAccessory(accessory) {
this.accessories.push(accessory);
}
discoverDevices(callback = null) {
if (this.config.enableLanControl) {
this._discoverAirstageLanDevices((function(error) {
if (error) {
if (callback !== null) {
return callback(error);
}
}
if (this.config.enableCloudControl) {
this._discoverAirstageCloudDevices((function(error) {
if (error) {
if (callback !== null) {
return callback(error);
}
}
if (callback !== null) {
callback(null);
}
}).bind(this));
} else {
if (callback !== null) {
callback(null);
}
}
}).bind(this));
} else if (this.config.enableCloudControl) {
this._discoverAirstageCloudDevices((function(error) {
if (error) {
if (callback !== null) {
return callback(error);
}
}
if (callback !== null) {
callback(null);
}
}).bind(this));
} else {
if (callback !== null) {
callback(null);
}
}
}
_discoverAirstageLanDevices(callback = null) {
this.airstageLanClient.getDevices(null, (function(error, devices) {
if (error) {
if (callback !== null) {
callback(error);
}
return this.log.error('Error when attempting to communicate with Airstage LAN:', error);
}
const deviceIds = Object.keys(devices.parameters);
this.lanDeviceIds = deviceIds;
this._configureAirstageLanDevices(callback);
}).bind(this));
}
_configureAirstageLanDevices(callback = null) {
this.airstageLanClient.getDevices(null, (function(error, devices) {
if (error) {
if (callback !== null) {
callback(error);
}
return this.log.error('Error when attempting to communicate with Airstage LAN:', error);
}
const deviceIds = Object.keys(devices.parameters);
deviceIds.forEach(function(deviceId) {
const deviceParameters = devices.parameters[deviceId];
this.airstageLanClient.getName(deviceId, (function(error, deviceName) {
if (error !== null) {
this.log.error('Error when attempting to communicate with Airstage LAN:', error);
// Fallback to using the device ID for the display name
deviceName = deviceId;
}
const model = deviceParameters[airstage.constants.PARAMETER_MODEL] || 'Airstage';
this._configureAirstageDevice(
deviceId,
deviceName,
model
);
}).bind(this));
}, this);
if (callback !== null) {
callback(null);
}
}).bind(this));
}
_discoverAirstageCloudDevices(callback = null) {
this.airstageCloudClient.refreshTokenOrAuthenticate((function(error) {
if (error) {
if (callback !== null) {
callback(error);
}
if (error === 'Invalid access token') {
this._unsetAccessTokenInConfig();
}
return this.log.error('Error when attempting to authenticate with Airstage Cloud:', error);
}
this._updateConfigWithAccessToken();
this._configureAirstageCloudDevices(callback);
}).bind(this));
}
_updateConfigWithAccessToken() {
this.configManager.saveTokens(
this.airstageCloudClient.getAccessToken(),
this.airstageCloudClient.getAccessTokenExpiry(),
this.airstageCloudClient.getRefreshToken()
);
}
_unsetAccessTokenInConfig() {
this.configManager.saveTokens(null, null, null);
}
_configureAirstageCloudDevices(callback) {
this.airstageCloudClient.getUserMetadata((function(error) {
if (error) {
if (callback !== null) {
callback(error);
}
return this.log.error('Error when attempting to communicate with Airstage Cloud:', error);
}
this.airstageCloudClient.getDevices(null, (function(error, devices) {
if (error) {
if (callback !== null) {
callback(error);
}
return this.log.error('Error when attempting to communicate with Airstage Cloud:', error);
}
const deviceIds = Object.keys(devices.parameters);
deviceIds.forEach(function(deviceId) {
const deviceMetadata = devices.metadata[deviceId];
const deviceParameters = devices.parameters[deviceId];
const deviceName = deviceMetadata.deviceName;
const model = deviceParameters[airstage.constants.PARAMETER_MODEL] || 'Airstage';
if (this.lanDeviceIds.includes(deviceId) === false) {
this._configureAirstageDevice(
deviceId,
deviceName,
model
);
}
}, this);
if (callback !== null) {
callback(null);
}
}).bind(this));
}).bind(this));
}
_configureAirstageDevice(
deviceId,
deviceName,
model
) {
// Heater Cooler
if (this.config.enableHeaterCooler) {
this.accessoryManager.registerHeaterCoolerAccessory(
deviceId,
deviceName,
model
);
} else {
this.accessoryManager.unregisterHeaterCoolerAccessory(
deviceId,
deviceName
);
}
// Thermostat
if (this.config.enableThermostat) {
this.accessoryManager.registerThermostatAccessory(
deviceId,
deviceName,
model
);
} else {
this.accessoryManager.unregisterThermostatAccessory(
deviceId,
deviceName
);
}
// Fan
if (this.config.enableFan) {
this.accessoryManager.registerFanAccessory(
deviceId,
deviceName,
model
);
} else {
this.accessoryManager.unregisterFanAccessory(
deviceId,
deviceName
);
}
// Deprecated: the VerticalSlatsAccessory was replaced by the
// VerticalAirflowDirectionAccessory
this.accessoryManager.unregisterVerticalSlatsAccessory(
deviceId,
deviceName
);
// Vertical airflow direction
if (this.config.enableVerticalAirflowDirection) {
this.accessoryManager.registerVerticalAirflowDirectionAccessory(
deviceId,
deviceName,
model
);
} else {
this.accessoryManager.unregisterVerticalAirflowDirectionAccessory(
deviceId,
deviceName
);
}
// "Auto Fan Speed" switch
if (this.config.enableAutoFanSpeedSwitch) {
this.accessoryManager.registerAutoFanSpeedSwitchAccessory(
deviceId,
deviceName,
model
);
} else {
this.accessoryManager.unregisterAutoFanSpeedSwitchAccessory(
deviceId,
deviceName
);
}
// "Dry Mode" switch
if (this.config.enableDryModeSwitch) {
this.accessoryManager.registerDryModeSwitchAccessory(
deviceId,
deviceName,
model
);
} else {
this.accessoryManager.unregisterDryModeSwitchAccessory(
deviceId,
deviceName
);
}
// "Economy" switch
if (this.config.enableEconomySwitch) {
this.accessoryManager.registerEconomySwitchAccessory(
deviceId,
deviceName,
model
);
} else {
this.accessoryManager.unregisterEconomySwitchAccessory(
deviceId,
deviceName
);
}
// "Energy Saving Fan" switch
if (this.config.enableEnergySavingFanSwitch) {
this.accessoryManager.registerEnergySavingFanSwitchAccessory(
deviceId,
deviceName,
model
);
} else {
this.accessoryManager.unregisterEnergySavingFanSwitchAccessory(
deviceId,
deviceName
);
}
// "Fan Mode" switch
if (this.config.enableFanModeSwitch) {
this.accessoryManager.registerFanModeSwitchAccessory(
deviceId,
deviceName,
model
);
} else {
this.accessoryManager.unregisterFanModeSwitchAccessory(
deviceId,
deviceName
);
}
// "Minimum Heat Mode" switch
if (this.config.enableMinimumHeatModeSwitch) {
this.accessoryManager.registerMinimumHeatModeSwitchAccessory(
deviceId,
deviceName,
model
);
} else {
this.accessoryManager.unregisterMinimumHeatModeSwitchAccessory(
deviceId,
deviceName
);
}
// "Powerful" switch
if (this.config.enablePowerfulSwitch) {
this.accessoryManager.registerPowerfulSwitchAccessory(
deviceId,
deviceName,
model
);
} else {
this.accessoryManager.unregisterPowerfulSwitchAccessory(
deviceId,
deviceName
);
}
}
_refreshAirstageCloudClientTokenOrAuthenticate() {
this.airstageCloudClient.refreshTokenOrAuthenticate((function(error) {
if (error) {
if (error === 'Invalid access token') {
this._unsetAccessTokenInConfig();
}
return this.log.error('Error when attempting to authenticate with Airstage Cloud:', error);
}
this._updateConfigWithAccessToken();
this.log.debug('Refreshed Airstage Cloud authentication');
}).bind(this));
}
_refreshAirstageCloudClientCache() {
this.airstageCloudClient.refreshUserMetadataCache(
(function(error) {
if (error) {
return this.log.error('Error when attempting to communicate with Airstage Cloud:', error);
}
this.log.debug('Refreshed Airstage Cloud client user metadata cache');
this.airstageCloudClient.refreshDeviceCache(
(function(error, devices) {
if (error) {
return this.log.error('Error when attempting to communicate with Airstage Cloud:', error);
}
this.log.debug('Refreshed Airstage Cloud client device cache');
const deviceIds = Object.keys(devices.parameters);
deviceIds.forEach(function(deviceId) {
this.accessoryManager.refreshAllAccessoryCharacteristics(deviceId);
}, this);
}).bind(this)
);
}).bind(this)
);
}
_refreshAirstageLanClientCache() {
this.airstageLanClient.refreshDeviceCache(
(function(error, devices) {
if (error) {
return this.log.error('Error when attempting to communicate with Airstage LAN:', error);
}
this.log.debug('Refreshed Airstage LAN client device cache');
const deviceIds = Object.keys(devices.parameters);
deviceIds.forEach(function(deviceId) {
this.accessoryManager.refreshAllAccessoryCharacteristics(deviceId);
}, this);
}).bind(this)
);
}
}
module.exports = Platform;