homebridge-fibaro-home-center
Version:
Homebridge plugin for Fibaro Home Center (2, 2 Lite, 3, 3 Lite, Yubii Home).
448 lines • 21.6 kB
JavaScript
;
// setFunctions.ts
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SetFunctions = void 0;
require("reflect-metadata");
const utils_1 = require("./utils");
const constants_1 = require("./constants");
// Decorator function for mapping setCharacteristicValue to the Characteristic
function characteristicSetter(...characteristics) {
return function (target, propertyKey, descriptor) {
const existingMethods = Reflect.getMetadata('characteristicMethods', target) || [];
existingMethods.push([propertyKey, characteristics]);
Reflect.defineMetadata('characteristicMethods', existingMethods, target);
return descriptor;
};
}
// Throttle decorator
function throttle(timeWindow, inactivityThreshold, skipInterval = 0) {
return function (target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
// Map to store per-device state using device identifiers
const deviceStateMap = new Map();
descriptor.value = function (...args) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const device = this;
// Obtain the device identifier from IDs[0]
const IDs = args[4]; // IDs is the 5th argument
const deviceId = IDs && IDs[0];
if (!deviceId) {
throw new Error('Device ID could not be determined. Please provide a valid IDs array.');
}
let state = deviceStateMap.get(deviceId);
if (!state) {
state = {
lastInvocationTime: 0,
lastCallTime: 0,
lastArgs: null,
skipIntervalTimerId: null,
skipIntervalEndTime: 0,
inactivityTimerId: null,
executionScheduled: false,
isBurst: false,
};
deviceStateMap.set(deviceId, state);
}
const now = Date.now();
// Update last call time and args
state.lastCallTime = now;
state.lastArgs = args;
// If we're not in a burst, apply skipInterval
if (!state.isBurst) {
state.isBurst = true;
if (skipInterval > 0) {
// Start skipInterval timer without executing immediately
state.skipIntervalEndTime = now + skipInterval;
state.skipIntervalTimerId = setTimeout(() => {
state.skipIntervalTimerId = null;
scheduleExecution();
}, skipInterval);
}
else {
// skipInterval is zero
scheduleExecution();
}
}
else {
// We're in a burst, schedule execution as per timeWindow and inactivityThreshold
scheduleExecution();
}
// Reset the inactivity timer
if (state.inactivityTimerId) {
clearTimeout(state.inactivityTimerId);
}
state.inactivityTimerId = setTimeout(() => {
// Inactivity threshold passed, burst has ended
state.isBurst = false;
// Schedule the last execution if needed
scheduleExecution();
}, inactivityThreshold);
function scheduleExecution() {
const now = Date.now();
// Calculate time since last invocation for timeWindow
const timeSinceLastInvocation = now - state.lastInvocationTime;
const timeWindowRemaining = timeWindow - timeSinceLastInvocation;
const timeWindowDelay = timeWindowRemaining > 0 ? timeWindowRemaining : 0;
let totalDelay = timeWindowDelay;
// If skipInterval is active, adjust totalDelay to ensure skipInterval is respected
if (state.skipIntervalTimerId) {
const skipIntervalRemaining = state.skipIntervalEndTime - now;
totalDelay = Math.max(totalDelay, skipIntervalRemaining);
}
if (state.executionScheduled) {
// Execution already scheduled, no need to schedule again
return;
}
state.executionScheduled = true;
setTimeout(() => {
executeMethod();
}, totalDelay);
}
function executeMethod() {
state.lastInvocationTime = Date.now();
state.executionScheduled = false;
originalMethod.apply(device, state.lastArgs);
// Clear timers
if (state.inactivityTimerId) {
clearTimeout(state.inactivityTimerId);
state.inactivityTimerId = null;
}
}
};
};
}
class SetFunctions {
constructor(platform) {
this.platform = platform;
this.setFunctionsMapping = new Map();
const { TargetHeatingCoolingState } = this.platform.Characteristic;
this.modeMap = {
[TargetHeatingCoolingState.OFF]: 'Off',
[TargetHeatingCoolingState.HEAT]: 'Heat',
[TargetHeatingCoolingState.COOL]: 'Cool',
[TargetHeatingCoolingState.AUTO]: 'Auto',
};
this.initializeFunctionsMapping();
}
initializeFunctionsMapping() {
const prototype = Object.getPrototypeOf(this);
const characteristicMethods = Reflect.getMetadata('characteristicMethods', prototype) || [];
for (const [methodName, characteristics] of characteristicMethods) {
for (const characteristic of characteristics) {
this.setFunctionsMapping.set(this.platform.Characteristic[characteristic], this[methodName].bind(this));
}
}
}
async setOn(value, context, characteristic, service, IDs) {
const setValue = (delay = 100) => {
setTimeout(() => {
characteristic.setValue(0, undefined, 'fromSetValue');
}, delay);
};
if (service.isVirtual && !service.isGlobalVariableSwitch && !service.isGlobalVariableDimmer) {
// It's a virtual device so the command is pressButton and not turnOn or Off
await this.command('pressButton', [IDs[1]], service, IDs);
// In order to behave like a push button reset the status to off
setValue();
}
else if (service.isScene) {
// It's a scene so the command is execute scene
await this.scene(IDs[0]);
// In order to behave like a push button reset the status to off
setValue();
}
else if (service.isGlobalVariableSwitch) {
await this.setGlobalVariable(IDs[1], value ? 'true' : 'false');
}
else if (service.isGlobalVariableDimmer) {
const currentDimmerValue = (await this.getGlobalVariable(IDs[1])).body.value;
if (currentDimmerValue !== '0' && value) {
return;
}
await this.setGlobalVariable(IDs[1], value ? '100' : '0');
}
else {
await this.command(value ? 'turnOn' : 'turnOff', null, service, IDs);
}
}
async setBrightness(value, context, characteristic, service, IDs) {
// Handle global variable dimmer separately
if (service.isGlobalVariableDimmer) {
await this.setGlobalVariable(IDs[1], value.toString());
return;
}
await this.command('setValue', [value], service, IDs);
}
async setTargetPosition(value, context, characteristic, service, IDs) {
if (service.isOpenCloseOnly) {
// For open/close only devices, use specific commands based on the target position
const action = value === 0 ? 'close' : value >= 99 ? 'open' : null;
if (action) {
await this.command(action, [0], service, IDs);
}
return;
}
const result = await this.command('setValue', [value], service, IDs);
if (result && result.body && !result.body.result) {
// The command was discarded because the device is not ready to accept commands
// set the characteristic value to the current value of the device
characteristic.updateValue(service.getCharacteristic(this.platform.Characteristic.CurrentPosition).value, undefined, 'fromSetValue');
this.platform.log.warn('setValue discarded for device', IDs[0]);
}
}
async setHoldPosition(value, context, characteristic, service, IDs) {
if (value) {
await this.command('stop', [0], service, IDs);
}
}
async setTargetTiltAngle(angle, context, characteristic, service, IDs) {
const value2 = utils_1.Utils.scale(angle, characteristic.props.minValue, characteristic.props.maxValue, 0, 100);
await this.command('setValue2', [value2], service, IDs);
}
async setLockTargetState(value, context, characteristic, service, IDs) {
const Characteristic = this.platform.Characteristic;
const isUnsecured = value === Characteristic.LockTargetState.UNSECURED;
if (service.isLockSwitch) {
// Handle lock switch
const action = isUnsecured ? 'turnOn' : 'turnOff';
await this.command(action, null, service, IDs);
service.getCharacteristic(Characteristic.LockCurrentState).updateValue(value, undefined, 'fromSetValue');
return;
}
// Handle regular lock
const action = isUnsecured ? 'unsecure' : 'secure';
await this.command(action, [0], service, IDs);
// Update lock current state after a delay
setTimeout(() => {
service.getCharacteristic(Characteristic.LockCurrentState).updateValue(value, undefined, 'fromSetValue');
}, 1000);
// Check if the action is correctly executed after a specified timeout
if (this.platform.config.doorlocktimeout !== '0') {
const timeout = parseInt(this.platform.config.doorlocktimeout) * 1000;
setTimeout(() => this.checkLockCurrentState(IDs, value), timeout);
}
}
async setTargetDoorState(value, context, characteristic, service, IDs) {
const action = value === this.platform.Characteristic.TargetDoorState.CLOSED ? 'close' : 'open';
await this.command(action, [0], service, IDs);
setTimeout(() => {
characteristic.setValue(value, undefined, 'fromSetValue');
service.getCharacteristic(this.platform.Characteristic.CurrentDoorState)
.setValue(value, undefined, 'fromSetValue');
}, 100);
}
async setTargetHeatingCoolingState(value, context, characteristic, service, IDs) {
switch (true) {
case service.isRadiatorThermostaticValve:
case service.isHeatingZone:
return; // Early return as they don't support mode changes
case service.isClimateZone: {
const mode = this.modeMap[value];
if (!mode) {
return;
}
const currentTemperature = service.getCharacteristic(this.platform.Characteristic.CurrentTemperature).value;
const timestamp = Math.trunc(Date.now() / 1000) + parseInt(this.platform.config.thermostattimeout);
await this.platform.fibaroClient.setClimateZoneHandTemperature(IDs[0], mode, currentTemperature, timestamp);
break;
}
default:
break;
}
}
async setTargetTemperature(value, context, characteristic, service, IDs) {
const timestamp = Math.trunc(Date.now() / 1000) + parseInt(this.platform.config.thermostattimeout);
switch (true) {
case service.isClimateZone: {
const currentState = service.getCharacteristic(this.platform.Characteristic.CurrentHeatingCoolingState).value;
const mode = this.modeMap[currentState];
if (mode) {
await this.platform.fibaroClient.setClimateZoneHandTemperature(IDs[0], mode, value, timestamp);
}
break;
}
case service.isHeatingZone:
await this.platform.fibaroClient.setHeatingZoneHandTemperature(IDs[0], value, timestamp);
break;
case service.isRadiatorThermostaticValve:
await this.command('setHeatingThermostatSetpoint', [value], service, IDs);
break;
default:
break;
}
}
async setHue(value, context, characteristic, service, IDs) {
this.updateHomeCenterColorFromHomeKit(value, null, service, IDs);
}
async setSaturation(value, context, characteristic, service, IDs) {
this.updateHomeCenterColorFromHomeKit(null, value, service, IDs);
}
async setSecuritySystemTargetState(value) {
const { Characteristic } = this.platform;
const sceneMap = new Map([
[Characteristic.SecuritySystemTargetState.AWAY_ARM, this.platform.scenes.SetAwayArmed],
[Characteristic.SecuritySystemTargetState.DISARM, this.platform.scenes.SetDisarmed],
[Characteristic.SecuritySystemTargetState.NIGHT_ARM, this.platform.scenes.SetNightArmed],
[Characteristic.SecuritySystemTargetState.STAY_ARM, this.platform.scenes.SetStayArmed],
]);
const sceneID = sceneMap.get(value);
if (sceneID === undefined) {
return;
}
if (value === Characteristic.SecuritySystemTargetState.DISARM) {
value = Characteristic.SecuritySystemCurrentState.DISARMED;
}
await this.scene(sceneID);
}
async setActive(value, context, characteristic, service, IDs) {
const action = (value === this.platform.Characteristic.Active.ACTIVE) ? 'turnOn' : 'turnOff';
await this.command(action, null, service, IDs);
}
async updateHomeCenterColorFromHomeKit(h, s, service, IDs) {
if (h !== null) {
service.h = h;
}
if (s !== null) {
service.s = s;
}
if (service.h !== undefined && service.s !== undefined) {
const v = service.characteristics[2].value;
const rgbw = utils_1.Utils.HSVtoRGB(service.h, service.s, v);
await this.command('setColor', [rgbw.r, rgbw.g, rgbw.b, rgbw.w], service, IDs);
await this.command('setValue', [v], service, IDs);
delete service.h;
delete service.s;
}
}
async command(c, value, service, IDs) {
const result = await this.platform.fibaroClient.executeDeviceAction(IDs[0], c, value);
if (this.platform.config.logsLevel >= 1) {
const nc = c.replace(/turnOn|turnOff|setValue|open|close|setColor|setHeatingThermostatSetpoint/g, match => {
const replacements = {
turnOn: 'On',
turnOff: 'Off',
setValue: '',
open: 'Open',
close: 'Close',
setColor: 'Color',
setHeatingThermostatSetpoint: 'Setpoint',
};
return replacements[match] || match;
});
const logMessage = `${service.displayName} [${IDs[0]}]: set ${nc}${value !== null && nc !== 'Open' && nc !== 'Close' && nc !== 'Color' && nc !== 'Setpoint' ? ` ${value}%` :
(value !== null && (nc === 'Color' || nc === 'Setpoint') ? ` ${value}` : '')}`;
this.platform.log(logMessage);
}
return result;
}
async scene(sceneID) {
await this.platform.fibaroClient.executeScene(sceneID, this.platform.isOldApi());
}
async setGlobalVariable(variableID, value) {
await this.platform.fibaroClient.setGlobalVariable(variableID, value);
}
async getGlobalVariable(variableID) {
const value = await this.platform.fibaroClient.getGlobalVariable(variableID);
return value;
}
async checkLockCurrentState(IDs, value) {
const properties = (await this.platform.fibaroClient.getDeviceProperties(IDs[0])).body.properties;
const currentValue = (properties.value === true) ?
this.platform.Characteristic.LockCurrentState.SECURED : this.platform.Characteristic.LockCurrentState.UNSECURED;
if (currentValue !== value) {
this.platform.log.error('There was a problem setting value to Lock: ', `${IDs[0]}`);
}
}
}
exports.SetFunctions = SetFunctions;
__decorate([
characteristicSetter(constants_1.Characteristics.On),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setOn", null);
__decorate([
characteristicSetter(constants_1.Characteristics.Brightness),
throttle(100, 100, 500),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setBrightness", null);
__decorate([
characteristicSetter(constants_1.Characteristics.TargetPosition),
throttle(500, 200, 1400),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setTargetPosition", null);
__decorate([
characteristicSetter(constants_1.Characteristics.HoldPosition),
throttle(1000, 200, 1200),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setHoldPosition", null);
__decorate([
characteristicSetter(constants_1.Characteristics.TargetHorizontalTiltAngle),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setTargetTiltAngle", null);
__decorate([
characteristicSetter(constants_1.Characteristics.LockTargetState),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setLockTargetState", null);
__decorate([
characteristicSetter(constants_1.Characteristics.TargetDoorState),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setTargetDoorState", null);
__decorate([
characteristicSetter(constants_1.Characteristics.TargetHeatingCoolingState),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setTargetHeatingCoolingState", null);
__decorate([
characteristicSetter(constants_1.Characteristics.TargetTemperature),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setTargetTemperature", null);
__decorate([
characteristicSetter(constants_1.Characteristics.Hue),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setHue", null);
__decorate([
characteristicSetter(constants_1.Characteristics.Saturation),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setSaturation", null);
__decorate([
characteristicSetter(constants_1.Characteristics.SecuritySystemTargetState),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setSecuritySystemTargetState", null);
__decorate([
characteristicSetter(constants_1.Characteristics.Active),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object, Object, Object]),
__metadata("design:returntype", Promise)
], SetFunctions.prototype, "setActive", null);
//# sourceMappingURL=setFunctions.js.map