homebridge-fibaro-home-center
Version:
Homebridge plugin for Fibaro Home Center (2, 2 Lite, 3, 3 Lite, Yubii Home).
315 lines (279 loc) • 12.5 kB
text/typescript
// pollerupdate.ts
import * as constants from './constants';
import { Utils } from './utils';
export class Poller {
private pollingUpdateRunning: boolean = false;
private lastPoll: number = 0;
private timeout: NodeJS.Timeout | null = null;
private shouldProcessUpdates: boolean = true;
constructor(private platform, private pollerPeriod: number) { }
async poll() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
if (this.pollingUpdateRunning) {
return;
}
this.pollingUpdateRunning = true;
try {
const { body: updates } = await this.platform.fibaroClient.refreshStates(this.lastPoll);
if (!this.shouldProcessUpdates) {
this.pollingUpdateRunning = false;
return;
}
if (this.platform.config.logsLevel > 1) {
this.platform.log.debug('Executing poller...');
}
if (updates.last !== undefined) {
this.lastPoll = updates.last;
}
if (updates.events && updates.events.length > 0) {
updates.events.forEach(this.handleEvent.bind(this));
}
if (updates.changes && updates.changes.length > 0) {
updates.changes.forEach(this.handleChange.bind(this));
}
// Manage global variable switches and dimmers
await Promise.all([
this.manageGlobalVariableDevice(this.platform.config.switchglobalvariables, 'G'),
this.manageGlobalVariableDevice(this.platform.config.dimmerglobalvariables, 'D'),
]);
// Manage Security System state
if (this.platform.config.securitysystem === 'enabled') {
await this.manageSecuritySystem();
}
this.pollingUpdateRunning = false;
this.restartPoll(this.pollerPeriod * 1000);
} catch (e) {
this.handlePollingError(e);
}
}
private handleEvent(event) {
const { type, data } = event;
const deviceId = data.deviceId || 'N/A';
switch (type) {
case 'SceneActivationEvent': {
const sceneId = data.sceneId || 'N/A';
// Create a change object in order to reuse the manageValue function
const change = { id: deviceId, value: sceneId };
this.manageValue(change);
this.platform.log.info(`Processed event scene-${sceneId} of type ${type} from device ${deviceId}`);
break;
}
case 'CentralSceneEvent': {
// key number pressed, starting from 1
const keyId = data.keyId || 'N/A';
// key pressed mode: "Pressed" for single press, "Pressed2" for double press, "HeldDown" for long press
const keyAttribute = data.keyAttribute || 'N/A';
// Create a change object in order to reuse the manageValue function
const change = { id: deviceId, value: keyAttribute, button: keyId };
this.manageValue(change);
this.platform.log.info(`Processed event ${keyAttribute} of type ${type} from device ${deviceId} key ${keyId}`);
break;
}
default:
return;
}
}
private handleChange(change) {
if (change.value !== undefined || change.value2 !== undefined) {
this.manageValue(change);
} else if (change['ui.startStopActivitySwitch.value'] !== undefined) {
this.manageValue({ ...change, value: change['ui.startStopActivitySwitch.value'] });
} else if (change.color !== undefined) {
this.manageColor(change);
} else if (change.thermostatMode !== undefined) {
this.manageOperatingMode(change);
} else if (change.heatingThermostatSetpoint !== undefined) {
this.manageHeatingThermostatSetpoint(change);
} else if (change.heatingThermostatSetpointFuture !== undefined) {
this.manageHeatingThermostatSetpointFuture(change);
}
}
private handlePollingError(e) {
this.platform.log.error('Error fetching updates: ', e);
if (e === 400) {
this.lastPoll = 0;
}
this.pollingUpdateRunning = false;
this.restartPoll(60 * 1000);
this.platform.log.error('Next try in 1 minute');
}
restartPoll(delay: number) {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
if (delay > 0) {
this.timeout = setTimeout(() => {
this.shouldProcessUpdates = true;
this.poll();
}, delay);
}
}
cancelPoll() {
if (this.timeout) {
clearTimeout(this.timeout);
this.timeout = null;
}
this.shouldProcessUpdates = false;
}
private manageValue(change) {
this.platform.updateSubscriptions.forEach(subscription => {
if (!(subscription.service instanceof this.platform.Service.Battery) && subscription.characteristic.displayName !== 'Name') {
const { property, id, characteristic, service } = subscription;
if (parseInt(id) === change.id &&
((property === 'value' && change.value !== undefined) || (property === 'value2' && change.value2 !== undefined))) {
if (this.platform.config.FibaroTemperatureUnit === constants.CONFIG_FIBARO_TEMPERATURE_UNIT_FAHRENHEIT &&
characteristic.displayName === 'Current Temperature') {
change.value = (change.value - 32) * 5 / 9;
}
if (service.remoteButtonNumber) {
if (characteristic.constructor !== this.platform.Characteristic.ProgrammableSwitchEvent) {
return;
}
if (service.isRemoteControllerSceneActivation) {
const buttonNumber = service.remoteButtonNumber;
const eventNumber = change.value;
// Each button handles two consecutive events starting from button 1. Skip if not in range.
if (!(eventNumber >= (buttonNumber - 1) * 2 + 1 && eventNumber <= buttonNumber * 2)) {
return;
}
} else if (service.isRemoteControllerCentralScene) {
if (service.remoteButtonNumber !== change.button) {
return;
}
}
}
const getFunction = this.platform.getFunctions!.getFunctionsMapping.get(characteristic.constructor);
if (getFunction) {
const IDs = service.subtype.split('-');
getFunction.call(this.platform.getFunctions, characteristic, service, IDs, change);
this.logValueChange(subscription, characteristic);
}
}
}
});
}
private logValueChange(subscription, characteristic) {
if (this.platform.config.logsLevel >= 1) {
let val1 = '', val2 = '';
switch (characteristic.displayName) {
case 'Current Temperature':
val1 = characteristic.value.toFixed(1);
val2 = this.platform.config.FibaroTemperatureUnit === constants.CONFIG_FIBARO_TEMPERATURE_UNIT_FAHRENHEIT ?
constants.CONFIG_FIBARO_TEMPERATURE_UNIT_FAHRENHEIT : constants.CONFIG_FIBARO_TEMPERATURE_UNIT_CELSIUS;
break;
case 'Current Relative Humidity':
case 'Brightness':
case 'Current Position':
case 'Target Position':
val1 = characteristic.value.toFixed(0);
val2 = '%';
break;
case 'Current Ambient Light Level':
val1 = characteristic.value.toFixed(0);
val2 = 'lux';
break;
case 'On':
val1 = characteristic.value === true || characteristic.value === 'turnOn' ? 'On' : 'Off';
break;
case 'Motion Detected':
val1 = characteristic.value ? 'Motion detected' : 'No motion';
break;
default:
val1 = characteristic.value;
}
this.platform.log.info(`${subscription.service.displayName} [${subscription.id}]:`, `${val1}`, `${val2}`);
}
}
async manageGlobalVariableDevice(param: string, type: string) {
if (param !== '') {
const globalVariables = param.split(',');
for (const globalVariable of globalVariables) {
const value = (await this.platform.fibaroClient.getGlobalVariable(globalVariable)).body;
// Determine the service type based on the 'type' parameter
const serviceType = type === 'G' ? this.platform.Service.Switch : this.platform.Service.Lightbulb;
const service = this.platform.findServiceByName(globalVariable, serviceType);
if (service) {
// Iterate through all characteristics of the service
service.characteristics.forEach(characteristic => {
// Get the function to call dynamically
const getFunction = this.platform.getFunctions!.getFunctionsMapping.get(characteristic.constructor);
if (getFunction) {
// Call the function dynamically
getFunction.call(this.platform.getFunctions, characteristic, service, null, value);
}
});
}
}
}
}
async manageSecuritySystem() {
const securitySystemStatus = (await this.platform.fibaroClient.getGlobalVariable(constants.SECURITY_SYSTEM_GLOBAL_VARIABLE)).body;
const service = this.platform.findServiceByName('FibaroSecuritySystem', this.platform.Service.SecuritySystem);
if (service !== undefined) {
const state = this.platform.getFunctions.CurrentSecuritySystemStateMapping.get(securitySystemStatus.value);
if (state !== undefined) {
const characteristic = service.getCharacteristic(this.platform.Characteristic.SecuritySystemCurrentState);
if (characteristic.value !== state) {
characteristic.updateValue(state);
}
}
}
}
manageColor(change) {
this.platform.updateSubscriptions.forEach(subscription => {
if (parseInt(subscription.id) === change.id && subscription.property === 'color') {
const hsv = Utils.updateHomeKitColorFromHomeCenter(change.color);
const { characteristic } = subscription;
if (characteristic.constructor === this.platform.Characteristic.On) {
characteristic.updateValue(hsv.v === 0 ? false : true);
} else if (characteristic.constructor === this.platform.Characteristic.Hue) {
characteristic.updateValue(Math.round(hsv.h));
} else if (characteristic.constructor === this.platform.Characteristic.Saturation) {
characteristic.updateValue(Math.round(hsv.s));
} else if (characteristic.constructor === this.platform.Characteristic.Brightness) {
characteristic.updateValue(Math.round(hsv.v));
}
}
});
}
manageOperatingMode(change) {
this.platform.updateSubscriptions.forEach(subscription => {
if (parseInt(subscription.id) === change.id && subscription.property === 'mode') {
this.platform.log.info('Updating value for device: ',
`${subscription.id} parameter: ${subscription.characteristic.displayName}, value: ${change.thermostatMode}`);
const getFunction = this.platform.getFunctions!.getFunctionsMapping.get(subscription.characteristic.constructor);
if (getFunction) {
getFunction.call(this.platform.getFunctions, subscription.characteristic, subscription.service, null, change);
}
}
});
}
manageHeatingThermostatSetpoint(change) {
this.platform.updateSubscriptions.forEach(subscription => {
if (parseInt(subscription.id) === change.id && subscription.property === 'currenttemperature') {
this.platform.log.info('Updating value for device: ',
`${subscription.id} parameter: ${subscription.characteristic.displayName}, value: ${change.heatingThermostatSetpoint}`);
const getFunction = this.platform.getFunctions!.getFunctionsMapping.get(subscription.characteristic.constructor);
if (getFunction) {
getFunction.call(this.platform.getFunctions, subscription.characteristic, subscription.service, null, change);
}
}
});
}
manageHeatingThermostatSetpointFuture(change) {
this.platform.updateSubscriptions.forEach(subscription => {
if (parseInt(subscription.id) === change.id && subscription.property === 'targettemperature') {
this.platform.log.info('Updating value for device: ',
`${subscription.id} parameter: ${subscription.characteristic.displayName}, value: ${change.heatingThermostatSetpointFuture}`);
const getFunction = this.platform.getFunctions!.getFunctionsMapping.get(subscription.characteristic.constructor);
if (getFunction) {
getFunction.call(this.platform.getFunctions, subscription.characteristic, subscription.service, null, change);
}
}
});
}
}