homebridge-gira-client
Version:
Homebridge Plugin für Gira Homeserver 4 mit automatischer Geräteerkennung über IoT REST API
344 lines • 15.9 kB
JavaScript
;
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