UNPKG

homebridge-gira-client

Version:

Homebridge Plugin für Gira Homeserver 4 mit automatischer Geräteerkennung über IoT REST API

344 lines 15.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DeviceManager = void 0; const types_1 = require("./types"); const gira_accessory_1 = require("./accessories/gira-accessory"); const settings_1 = require("./settings"); class DeviceManager { constructor(context, giraClient) { this.context = context; this.giraClient = giraClient; this.deviceMappings = new Map(); this.initializeDeviceMappings(); } initializeDeviceMappings() { this.deviceMappings.set(types_1.QuoadFunctionType.SWITCHING, { homebridgeService: settings_1.SUPPORTED_SERVICES.SWITCH, characteristics: [ { homebridgeCharacteristic: 'On', quoadFunction: 'switching', converter: (value) => Boolean(value), reverseConverter: (value) => value ? 1 : 0, }, ], }); this.deviceMappings.set(types_1.QuoadFunctionType.DIMMING, { homebridgeService: settings_1.SUPPORTED_SERVICES.LIGHTBULB, characteristics: [ { homebridgeCharacteristic: 'On', quoadFunction: 'switching', converter: (value) => Boolean(value), reverseConverter: (value) => value ? 1 : 0, }, { homebridgeCharacteristic: 'Brightness', quoadFunction: 'dimming', converter: (value) => Math.round((value / 255) * 100), reverseConverter: (value) => Math.round((value / 100) * 255), }, ], }); this.deviceMappings.set(types_1.QuoadFunctionType.BLINDS, { homebridgeService: settings_1.SUPPORTED_SERVICES.WINDOW_COVERING, characteristics: [ { homebridgeCharacteristic: 'CurrentPosition', quoadFunction: 'position', converter: (value) => Math.round(100 - ((value / 255) * 100)), }, { homebridgeCharacteristic: 'TargetPosition', quoadFunction: 'position', converter: (value) => Math.round(100 - ((value / 255) * 100)), reverseConverter: (value) => Math.round(((100 - value) / 100) * 255), }, { homebridgeCharacteristic: 'PositionState', quoadFunction: 'moving', converter: (value) => { if (value === 0) return 2; return value > 0 ? 1 : 0; }, }, ], }); this.deviceMappings.set(types_1.QuoadFunctionType.TEMPERATURE, { homebridgeService: settings_1.SUPPORTED_SERVICES.TEMPERATURE_SENSOR, characteristics: [ { homebridgeCharacteristic: 'CurrentTemperature', quoadFunction: 'temperature', converter: (value) => parseFloat(value) || 0, }, ], }); this.deviceMappings.set(types_1.QuoadFunctionType.HUMIDITY, { homebridgeService: settings_1.SUPPORTED_SERVICES.HUMIDITY_SENSOR, characteristics: [ { homebridgeCharacteristic: 'CurrentRelativeHumidity', quoadFunction: 'humidity', converter: (value) => parseFloat(value) || 0, }, ], }); this.deviceMappings.set(types_1.QuoadFunctionType.HEATING, { homebridgeService: settings_1.SUPPORTED_SERVICES.THERMOSTAT, characteristics: [ { homebridgeCharacteristic: 'CurrentTemperature', quoadFunction: 'current_temperature', converter: (value) => parseFloat(value) || 0, }, { homebridgeCharacteristic: 'TargetTemperature', quoadFunction: 'target_temperature', converter: (value) => parseFloat(value) || 20, reverseConverter: (value) => value, }, { homebridgeCharacteristic: 'CurrentHeatingCoolingState', quoadFunction: 'heating_state', converter: (value) => { if (value === 'heating') return 1; if (value === 'cooling') return 2; return 0; }, }, { homebridgeCharacteristic: 'TargetHeatingCoolingState', quoadFunction: 'mode', converter: (value) => { if (value === 'heat') return 1; if (value === 'cool') return 2; if (value === 'auto') return 3; return 0; }, reverseConverter: (value) => { switch (value) { case 1: return 'heat'; case 2: return 'cool'; case 3: return 'auto'; default: return 'off'; } }, }, ], }); } async createOrUpdateAccessory(device) { const primaryFunction = this.getPrimaryFunction(device); if (!primaryFunction) { this.context.log.debug(`Device ${device.name} has no functions`); return null; } let mapping = this.deviceMappings.get(primaryFunction.type); // If no mapping found, try to create a generic mapping if (!mapping) { this.context.log.debug(`No mapping found for device type: ${primaryFunction.type}, creating generic mapping`); mapping = this.createGenericMapping(device, primaryFunction); } if (!mapping) { this.context.log.debug(`Could not create mapping for device: ${device.name}`); return null; } const uuid = this.context.api.hap.uuid.generate(`${device.id}-${device.name}`); let accessory = this.context.accessories.get(uuid); if (!accessory) { this.context.log.info(`Creating new accessory: ${device.name}`); accessory = new this.context.api.platformAccessory(device.name, uuid); accessory.context.device = device; accessory.context.primaryFunction = primaryFunction; accessory.context.mapping = mapping; this.setAccessoryCategory(accessory, primaryFunction.type); this.setAccessoryInformation(accessory, device); this.context.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]); this.context.accessories.set(uuid, accessory); } else { this.context.log.debug(`Updating existing accessory: ${device.name}`); accessory.context.device = device; accessory.context.primaryFunction = primaryFunction; accessory.context.mapping = mapping; } new gira_accessory_1.GiraAccessory(this.context, accessory, this.giraClient); return accessory; } getPrimaryFunction(device) { const priorityOrder = [ types_1.QuoadFunctionType.DIMMING, types_1.QuoadFunctionType.SWITCHING, types_1.QuoadFunctionType.BLINDS, types_1.QuoadFunctionType.HEATING, types_1.QuoadFunctionType.TEMPERATURE, types_1.QuoadFunctionType.HUMIDITY, types_1.QuoadFunctionType.SCENE, types_1.QuoadFunctionType.SENSOR, ]; for (const priority of priorityOrder) { const func = device.functions.find(f => f.type === priority); if (func) { return func; } } // If no specific type found, return the first function (for unknown types) return device.functions[0] || null; } createGenericMapping(device, primaryFunction) { // Try to determine the best service type based on function characteristics let serviceType = settings_1.SUPPORTED_SERVICES.SWITCH; // Default fallback const characteristics = []; // Check if device has writable boolean-like functions const hasWritableBinary = device.functions.some(f => f.writable && (f.dataType === 'boolean' || f.name.toLowerCase().includes('onoff'))); // Check if device has percentage/dimming functions const hasPercentage = device.functions.some(f => f.dataType === 'percentage' || f.name.toLowerCase().includes('brightness')); // Check if device has temperature functions const hasTemperature = device.functions.some(f => f.name.toLowerCase().includes('temperature')); // Check if device has position functions (blinds) const hasPosition = device.functions.some(f => f.name.toLowerCase().includes('position')); if (hasTemperature) { serviceType = settings_1.SUPPORTED_SERVICES.TEMPERATURE_SENSOR; const tempFunc = device.functions.find(f => f.name.toLowerCase().includes('temperature')); if (tempFunc) { characteristics.push({ homebridgeCharacteristic: 'CurrentTemperature', quoadFunction: tempFunc.id, converter: (value) => parseFloat(value) || 0, }); } } else if (hasPosition) { serviceType = settings_1.SUPPORTED_SERVICES.WINDOW_COVERING; const posFunc = device.functions.find(f => f.name.toLowerCase().includes('position')); if (posFunc) { characteristics.push({ homebridgeCharacteristic: 'CurrentPosition', quoadFunction: posFunc.id, converter: (value) => Math.round(parseFloat(value) || 0), }); characteristics.push({ homebridgeCharacteristic: 'TargetPosition', quoadFunction: posFunc.id, converter: (value) => Math.round(parseFloat(value) || 0), reverseConverter: (value) => value, }); } } else if (hasPercentage && hasWritableBinary) { serviceType = settings_1.SUPPORTED_SERVICES.LIGHTBULB; // Add On/Off characteristic const onOffFunc = device.functions.find(f => f.writable && (f.dataType === 'boolean' || f.name.toLowerCase().includes('onoff'))); if (onOffFunc) { characteristics.push({ homebridgeCharacteristic: 'On', quoadFunction: onOffFunc.id, converter: (value) => Boolean(value), reverseConverter: (value) => value ? 1 : 0, }); } // Add Brightness characteristic const brightnessFunc = device.functions.find(f => f.dataType === 'percentage' || f.name.toLowerCase().includes('brightness')); if (brightnessFunc) { characteristics.push({ homebridgeCharacteristic: 'Brightness', quoadFunction: brightnessFunc.id, converter: (value) => Math.round(parseFloat(value) || 0), reverseConverter: (value) => value, }); } } else if (hasWritableBinary) { serviceType = settings_1.SUPPORTED_SERVICES.SWITCH; const onOffFunc = device.functions.find(f => f.writable && (f.dataType === 'boolean' || f.name.toLowerCase().includes('onoff'))); if (onOffFunc) { characteristics.push({ homebridgeCharacteristic: 'On', quoadFunction: onOffFunc.id, converter: (value) => Boolean(value), reverseConverter: (value) => value ? 1 : 0, }); } } else { // Read-only sensor serviceType = settings_1.SUPPORTED_SERVICES.CONTACT_SENSOR; const sensorFunc = device.functions[0]; if (sensorFunc) { characteristics.push({ homebridgeCharacteristic: 'ContactSensorState', quoadFunction: sensorFunc.id, converter: (value) => Boolean(value) ? 1 : 0, }); } } if (characteristics.length === 0) { this.context.log.debug(`No characteristics could be mapped for device: ${device.name}`); return undefined; } this.context.log.info(`Created generic mapping for ${device.name}: ${serviceType} with ${characteristics.length} characteristics`); return { homebridgeService: serviceType, characteristics, }; } setAccessoryCategory(accessory, functionType) { switch (functionType) { case types_1.QuoadFunctionType.SWITCHING: accessory.category = 8 /* Categories.SWITCH */; break; case types_1.QuoadFunctionType.DIMMING: accessory.category = 5 /* Categories.LIGHTBULB */; break; case types_1.QuoadFunctionType.BLINDS: accessory.category = 14 /* Categories.WINDOW_COVERING */; break; case types_1.QuoadFunctionType.TEMPERATURE: case types_1.QuoadFunctionType.HUMIDITY: accessory.category = 10 /* Categories.SENSOR */; break; case types_1.QuoadFunctionType.HEATING: accessory.category = 9 /* Categories.THERMOSTAT */; break; default: accessory.category = 1 /* Categories.OTHER */; break; } } setAccessoryInformation(accessory, device) { const service = accessory.getService(this.context.api.hap.Service.AccessoryInformation) || accessory.addService(this.context.api.hap.Service.AccessoryInformation); service .setCharacteristic(this.context.api.hap.Characteristic.Manufacturer, 'Gira') .setCharacteristic(this.context.api.hap.Characteristic.Model, device.type || 'Unknown') .setCharacteristic(this.context.api.hap.Characteristic.SerialNumber, device.id) .setCharacteristic(this.context.api.hap.Characteristic.FirmwareRevision, '1.0.0'); if (device.room) { service.setCharacteristic(this.context.api.hap.Characteristic.Name, `${device.name} (${device.room})`); } } async removeDevice(deviceId) { const uuid = this.context.api.hap.uuid.generate(deviceId); const accessory = this.context.accessories.get(uuid); if (accessory) { this.context.log.info(`Removing device: ${accessory.displayName}`); this.context.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]); this.context.accessories.delete(uuid); } } getDeviceMapping(functionType) { return this.deviceMappings.get(functionType); } getSupportedDeviceTypes() { return Array.from(this.deviceMappings.keys()); } } exports.DeviceManager = DeviceManager; //# sourceMappingURL=device-manager.js.map