UNPKG

homebridge-fibaro-home-center

Version:

Homebridge plugin for Fibaro Home Center (2, 2 Lite, 3, 3 Lite, Yubii Home).

455 lines 23.6 kB
"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