homebridge-fibaro-home-center
Version:
Homebridge plugin for Fibaro Home Center (2, 2 Lite, 3, 3 Lite, Yubii Home).
455 lines • 23.6 kB
JavaScript
"use strict";
// fibaroAccessory.ts
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.FibaroAccessory = void 0;
const constants = __importStar(require("./constants"));
const deviceManual_1 = require("./deviceManual");
const deviceAuto_1 = require("./deviceAuto");
class FibaroAccessory {
constructor(platform, accessory, device) {
var _a;
this.platform = platform;
this.accessory = accessory;
this.device = device;
this.isValid = true;
// set accessory information
const properties = this.device.properties || {};
const manufacturer = (properties.zwaveCompany || 'IlCato').replace('Fibargroup', 'Fibar Group');
const roomName = this.platform.getRoomNameById(this.device.roomID);
// Set accessory information
const serialNumber = properties.serialNumber
? properties.serialNumber.startsWith('h\'')
? properties.serialNumber.slice(2).replace(/^0+/, '')
: properties.serialNumber.replace(/^0+/, '')
: undefined;
const accessoryInfo = this.accessory.getService(this.platform.Service.AccessoryInformation);
accessoryInfo
.setCharacteristic(this.platform.Characteristic.Manufacturer, manufacturer)
.setCharacteristic(this.platform.Characteristic.Model, `${this.device.type.length > 1 ?
this.device.type :
'HomeCenter Bridged Accessory'}`);
const serialNumberValue = this.constructSerialNumber(this.device.id, roomName, serialNumber);
accessoryInfo.setCharacteristic(this.platform.Characteristic.SerialNumber, serialNumberValue);
// Check for device-specific configuration
const devConfig = (_a = this.platform.config.devices) === null || _a === void 0 ? void 0 : _a.find(item => item.id === this.device.id);
// Configure accessory based on config or device type
let serviceAndCharacteristics;
if (devConfig) {
serviceAndCharacteristics = this.configureAccessoryFromConfig(devConfig);
}
else {
serviceAndCharacteristics = this.configureAccessoryFromType();
}
if (!serviceAndCharacteristics || !(serviceAndCharacteristics instanceof Array) || serviceAndCharacteristics.length === 0) {
this.isValid = false;
return;
}
// Add battery service if the device supports it
if (this.device.interfaces && this.device.interfaces.includes('battery')) {
serviceAndCharacteristics.push({
service: this.platform.Service.Battery,
characteristics: [this.platform.Characteristic.BatteryLevel,
this.platform.Characteristic.ChargingState,
this.platform.Characteristic.StatusLowBattery],
subtype: this.device.id + '----',
});
}
// loop through all services, create it if necessary and bind characteristics
serviceAndCharacteristics.forEach(({ service, characteristics, subtype }) => {
let s = this.accessory.getServiceById(service, subtype);
if (!s) {
const serviceName = this.buildServiceName(service, subtype);
s = this.accessory.addService(service, serviceName, subtype);
}
this.bindCharacterstics(s, characteristics);
});
// Remove no more existing services
for (let t = 0; t < this.accessory.services.length; t++) {
const s = this.accessory.services[t];
if (s.constructor === this.platform.Service.AccessoryInformation) {
continue;
}
if (!serviceAndCharacteristics.some(sc => sc.service === s.constructor)) {
this.accessory.removeService(s);
}
}
}
buildServiceName(service, subtype) {
if (service === this.platform.Service.Battery) {
return this.device.name + ' Battery';
}
else if (service === this.platform.Service.StatelessProgrammableSwitch) {
return this.device.name + ' Button ' + subtype.split('-')[4];
}
else {
return this.device.name;
}
}
bindCharacterstics(service, characteristics) {
if (!characteristics) {
return;
}
const IDs = service.subtype.split('-');
this.setServiceProperties(service, IDs);
for (let i = 0; i < characteristics.length; i++) {
const characteristic = service.getCharacteristic(characteristics[i]);
// Set the range for light level measurements
if (characteristic.constructor === this.platform.Characteristic.CurrentAmbientLightLevel) {
characteristic.props.maxValue = 100000;
characteristic.props.minStep = 1;
characteristic.props.minValue = 0;
}
// Set the minimum temperature that can be reported
if (characteristic.constructor === this.platform.Characteristic.CurrentTemperature) {
characteristic.props.minValue = -50;
}
// Set the maximum target temperature based on configuration
if (characteristic.constructor === this.platform.Characteristic.TargetTemperature) {
characteristic.props.maxValue = this.platform.config.thermostatmaxtemperature;
}
// Set the default valve type to generic
if (characteristic.constructor === this.platform.Characteristic.ValveType) {
characteristic.value = this.platform.Characteristic.ValveType.GENERIC_VALVE;
}
// Set the initial air quality status to unknown
if (characteristic.constructor === this.platform.Characteristic.AirQuality) {
characteristic.value = this.platform.Characteristic.AirQuality.UNKNOWN;
}
// Set the remote controller scene activationbutton options
if (service.isRemoteControllerSceneActivation &&
characteristic.constructor === this.platform.Characteristic.ProgrammableSwitchEvent) {
characteristic.props.validValues = [this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS,
this.platform.Characteristic.ProgrammableSwitchEvent.LONG_PRESS];
}
// Set the remote controller central scene button options
if (service.isRemoteControllerCentralScene &&
characteristic.constructor === this.platform.Characteristic.ProgrammableSwitchEvent) {
characteristic.props.validValues = [this.platform.Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS,
this.platform.Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS,
this.platform.Characteristic.ProgrammableSwitchEvent.LONG_PRESS];
}
// Set the thermosta radiator options
if (service.isRadiatorThermostaticValve &&
(characteristic.constructor === this.platform.Characteristic.CurrentHeatingCoolingState ||
characteristic.constructor === this.platform.Characteristic.TargetHeatingCoolingState)) {
characteristic.props.validValues = [
this.platform.Characteristic.CurrentHeatingCoolingState.OFF,
this.platform.Characteristic.CurrentHeatingCoolingState.HEAT,
];
}
// Bind the characteristic to the service
this.bindCharacteristic(characteristic, service, IDs);
}
}
bindCharacteristic(characteristic, service, IDs) {
if (!characteristic || !service || !service.subtype) {
return;
}
if (this.shouldSubscribeToUpdates(service, characteristic)) {
this.subscribeUpdate(service, characteristic, this.getPropertyToSubscribe(service, characteristic));
}
this.bindSetEvent(characteristic, service, IDs);
this.bindGetEvent(characteristic, service, IDs);
}
setServiceProperties(service, IDs) {
// IDs[0] is always device ID, "0" for security system and "G" for global variables switches
// IDs[1] is reserved for the button ID for virtual devices, or the global variable name for global variable devices, otherwise is ""
// IDs[2] is a subdevice type:
// "LOCK" for locks,
// "SC" for Scenes,
// "CZ" for Climate zones,
// "HZ" for heating zones,
// "G" for global variables,
// "D" for dimmer global variables,
// "PM2.5" for PM2.5 sensor
// IDs[3] is for remote controllers and remote scene controllers and represent the remote controller type:
// "CS" for remote controller central scene,
// "SA" for remote controller scene activation
// IDs[4] is for remote controllers and remote scene controllers and represent the remote controller button number
service.isVirtual = IDs[1] !== '';
service.isSecuritySystem = IDs[0] === '0';
service.isGlobalVariableSwitch = IDs[0] === 'G';
service.isGlobalVariableDimmer = IDs[0] === 'D';
service.isLockSwitch = IDs.length >= 3 && IDs[2] === constants.SUBTYPE_LOCK;
service.isScene = IDs.length >= 3 && IDs[2] === constants.SUBTYPE_SCENE;
service.isClimateZone = IDs.length >= 3 && IDs[2] === constants.SUBTYPE_CLIMATE_ZONE;
service.isHeatingZone = IDs.length >= 3 && IDs[2] === constants.SUBTYPE_HEATING_ZONE;
service.isRadiatorThermostaticValve = IDs.length >= 3 && IDs[2] === constants.SUBTYPE_RADIATOR_THERMOSTATIC_VALVE;
service.isOpenCloseOnly = IDs.length >= 3 && IDs[2] === constants.SUBTYPE_OPEN_CLOSE_ONLY;
service.isPM2_5Sensor = IDs.length >= 3 && IDs[2] === constants.SUBTYPE_PM2_5;
service.isRemoteControllerCentralScene = IDs.length >= 5 && IDs[3] === constants.SUBTYPE_REMOTE_CONTROLLER_CENTRAL_SCENE;
service.isRemoteControllerSceneActivation = IDs.length >= 5 && IDs[3] === constants.SUBTYPE_REMOTE_CONTROLLER_SCENE_ACTIVATION;
service.remoteButtonNumber = IDs.length >= 5 ? parseInt(IDs[4]) : -1;
}
shouldSubscribeToUpdates(service, characteristic) {
return !service.isVirtual && !service.isScene
&& characteristic.constructor !== this.platform.Characteristic.ValveType;
}
getPropertyToSubscribe(service, characteristic) {
if (characteristic.constructor === this.platform.Characteristic.Hue
|| characteristic.constructor === this.platform.Characteristic.Saturation) {
return 'color';
}
if (characteristic.constructor === this.platform.Characteristic.CurrentHeatingCoolingState ||
characteristic.constructor === this.platform.Characteristic.TargetHeatingCoolingState) {
return 'mode';
}
if (characteristic.constructor === this.platform.Characteristic.TargetTemperature) {
return 'targettemperature';
}
if (service.UUID === this.platform.Service.WindowCovering.UUID
&& (characteristic.constructor === this.platform.Characteristic.CurrentHorizontalTiltAngle
|| characteristic.constructor === this.platform.Characteristic.TargetHorizontalTiltAngle)) {
return 'value2';
}
return 'value';
}
bindSetEvent(characteristic, service, IDs) {
characteristic.on("set" /* CharacteristicEventTypes.SET */, async (value, callback, context) => {
this.setCharacteristicValue(value, context, characteristic, service, IDs);
callback();
});
}
bindGetEvent(characteristic, service, IDs) {
characteristic.on("get" /* CharacteristicEventTypes.GET */, async (callback) => {
if (this.isStaticCharacteristic(characteristic)) {
callback(undefined, characteristic.value);
}
else if (this.isVirtualButtonOrScene(service)) {
callback(undefined, false);
}
else {
this.getCharacteristicValue(callback, characteristic, service, this.accessory, IDs);
}
});
}
isStaticCharacteristic(characteristic) {
return characteristic.constructor === this.platform.Characteristic.Name
|| characteristic.constructor === this.platform.Characteristic.ValveType;
}
isVirtualButtonOrScene(service) {
return (service.isVirtual && !service.isGlobalVariableSwitch && !service.isGlobalVariableDimmer) || service.isScene;
}
async setCharacteristicValue(value, context, characteristic, service, IDs) {
if (context !== 'fromFibaro' && context !== 'fromSetValue') {
if (this.platform.setFunctions) {
const setFunction = this.platform.setFunctions.setFunctionsMapping.get(characteristic.constructor);
const poller = this.platform.poller;
if (poller) {
poller.cancelPoll();
}
if (setFunction) {
setFunction.call(this.platform.setFunctions, value, context, characteristic, service, IDs);
}
if (poller) {
poller.restartPoll(constants.POLLER_RESTART_AFTER_SET_VALUE);
}
}
}
}
async getCharacteristicValue(callback, characteristic, service, accessory, IDs) {
if (this.platform.config.logsLevel === constants.CONFIG_LOGS_LEVEL_VERBOSE) {
this.platform.log.info(`${this.device.name} [${IDs[0]}]:`, 'getting', `${characteristic.displayName}`);
}
if (!this.platform.fibaroClient) {
this.platform.log.error('No Fibaro client available.');
callback(-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
return;
}
else {
// communicate to HomeKit the current value of the characteristic.
// the logic that follows may update the value or return error in case it is not able to get the updated value
callback(undefined, characteristic.value);
}
try {
if (service.isSecuritySystem) {
await this.handleSecuritySystem(characteristic, service, IDs);
}
else if (service.isGlobalVariableSwitch || service.isGlobalVariableDimmer) {
await this.handleGlobalVariable(characteristic, service, IDs);
}
else {
await this.handleDefaultCase(characteristic, service, IDs);
}
}
catch (e) {
characteristic.updateValue(new Error((-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */).toString()));
this.platform.log.error('G1 - There was a problem getting value from: ', `${IDs[0]} - Err: ${e}`);
}
}
async handleSecuritySystem(characteristic, service, IDs) {
const securitySystemStatus = (await this.platform.fibaroClient.getGlobalVariable(constants.SECURITY_SYSTEM_GLOBAL_VARIABLE)).body;
this.platform.getFunctions.getSecuritySystemState(characteristic, service, IDs, securitySystemStatus);
}
async handleGlobalVariable(characteristic, service, IDs) {
const value = (await this.platform.fibaroClient.getGlobalVariable(IDs[1])).body;
const getFunction = this.platform.getFunctions.getFunctionsMapping.get(characteristic.constructor);
if (getFunction) {
getFunction.call(this.platform.getFunctions, characteristic, service, IDs, value);
}
}
async handleDefaultCase(characteristic, service, IDs) {
const getFunction = this.platform.getFunctions.getFunctionsMapping.get(characteristic.constructor);
if (getFunction) {
const properties = await this.getDeviceProperties(service, IDs[0]);
if (this.shouldConvertTemperature(characteristic)) {
properties.value = this.convertFahrenheitToCelsius(properties.value);
}
this.handleDeadDeviceStatus(service, properties, IDs[0]);
this.callGetFunctionAndHandleResult(getFunction, characteristic, service, IDs, properties);
}
else {
characteristic.updateValue(new Error((-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */).toString()));
this.platform.log.error('No get function defined for: ', `${characteristic.displayName}`);
}
}
async getDeviceProperties(service, deviceId) {
if (!service.isClimateZone && !service.isHeatingZone && this.platform.fibaroClient) {
return (await this.platform.fibaroClient.getDeviceProperties(deviceId)).body.properties;
}
return {};
}
shouldConvertTemperature(characteristic) {
return this.platform.config.FibaroTemperatureUnit === constants.CONFIG_FIBARO_TEMPERATURE_UNIT_FAHRENHEIT &&
characteristic.displayName === 'Current Temperature';
}
convertFahrenheitToCelsius(value) {
return (value - 32) * 5 / 9;
}
handleDeadDeviceStatus(service, properties, deviceId) {
// Reset deadLogged flag if this is a different service
if (this.lastServiceChecked !== service) {
this.lastServiceChecked = service;
service.deadLogged = false;
}
// Log dead status once per service
if ((properties.dead === true || properties.dead === 'true') && !service.deadLogged) {
this.platform.log.warn('Device dead: ', `${deviceId} service: ${service.displayName}, reason: ${properties.deadReason}`);
service.deadLogged = true;
}
}
callGetFunctionAndHandleResult(getFunction, characteristic, service, IDs, properties) {
if (properties.dead === true || properties.dead === 'true') {
if (this.platform.config.markDeadDevices) {
// Report dead status to HomeKit
characteristic.updateValue(new Error((-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */).toString()));
}
else {
// Do nothing, we already set the last known value before
return;
}
}
else {
getFunction.call(this.platform.getFunctions, characteristic, service, IDs, properties);
}
}
subscribeUpdate(service, characteristic, propertyChanged) {
const IDs = service.subtype.split('-');
this.platform.updateSubscriptions.push({ 'id': IDs[0], 'service': service, 'characteristic': characteristic, 'property': propertyChanged });
}
configureAccessoryFromConfig(devConfig) {
if (!devConfig) {
return;
}
const Service = this.platform.Service;
const Characteristic = this.platform.Characteristic;
if (this.platform.config.logsLevel === constants.CONFIG_LOGS_LEVEL_VERBOSE) {
this.platform.log.info(`${this.device.name} [id: ${this.device.id}, type: ${this.device.type}]: device found in config`);
}
// If the device is excluded in config
if (devConfig.displayAs === 'exclude') {
if (this.platform.config.logsLevel > 0) {
this.platform.log.info(`${this.device.name} [id: ${this.device.id}, type: ${this.device.type}]: device excluded in config`);
}
return;
}
// Find a matching function based on the name
const manualConfigFunc = deviceManual_1.manualDeviceConfigs.get(devConfig.displayAs);
// If a matching manualConfigFunc was found
if (manualConfigFunc) {
// Set the configuration for this device by calling manualConfigFunc
return manualConfigFunc.call(null, Service, Characteristic, this.device);
}
else {
// Default to switch if no matching configuration is found
return [{
service: Service.Switch,
characteristics: [Characteristic.On],
subtype: this.device.id + '----',
}];
}
}
configureAccessoryFromType() {
var _a;
const Service = this.platform.Service;
const Characteristic = this.platform.Characteristic;
const type = this.device.type;
// Find the matching device configuration function
const deviceConfigFunction = (_a = [...deviceAuto_1.autoDeviceConfigs.entries()].find(([key]) => (key instanceof RegExp && key.test(type)) || key === type)) === null || _a === void 0 ? void 0 : _a[1];
// If a matching deviceConfigFunction was found
if (deviceConfigFunction) {
// Set the configuration for this device by calling deviceConfigFunction
return deviceConfigFunction.call(null, Service, Characteristic, this.device, this.platform.config, this.platform.log);
}
else {
// If no matching DeviceClass was found, log that the device is not supported
if (this.platform.config.logsLevel > 0) {
this.platform.log.debug(`${this.device.name} [id: ${this.device.id}, type: ${type}]: device not supported`);
}
return;
}
}
constructSerialNumber(deviceId, roomName, serialNumber) {
let serialNumberValue = `ID:${deviceId}`;
let remainingLength = 64 - serialNumberValue.length;
if (roomName && remainingLength > 0) {
const roomString = `,Room:${roomName}`;
if (roomString.length <= remainingLength) {
serialNumberValue += roomString;
remainingLength -= roomString.length;
}
}
if (serialNumber && remainingLength > 0) {
const serialString = `,${serialNumber}`;
if (serialString.length <= remainingLength) {
serialNumberValue += serialString;
}
}
return serialNumberValue;
}
}
exports.FibaroAccessory = FibaroAccessory;
//# sourceMappingURL=fibaroAccessory.js.map