UNPKG

homebridge-z2m

Version:

Expose your Zigbee devices to HomeKit with ease, by integrating Zigbee2MQTT with Homebridge.

284 lines 13.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ElectricalSensorCreator = exports.ProducedEnergySensorHandler = exports.ElectricalSensorHandler = void 0; const hap_1 = require("../hap"); const helpers_1 = require("../helpers"); const z2mModels_1 = require("../z2mModels"); const monitor_1 = require("./monitor"); // Custom Service UUID (from homebridge-3em-energy-meter, proven in Eve app) const ELECTRICAL_SERVICE_UUID = '00000001-0000-1777-8000-775D67EC4377'; // Eve Characteristic UUIDs const CHARACTERISTIC_WATT_UUID = 'E863F10D-079E-48FF-8F27-9C2605A29F52'; const CHARACTERISTIC_VOLT_UUID = 'E863F10A-079E-48FF-8F27-9C2605A29F52'; const CHARACTERISTIC_AMPERE_UUID = 'E863F126-079E-48FF-8F27-9C2605A29F52'; const CHARACTERISTIC_KWH_UUID = 'E863F10C-079E-48FF-8F27-9C2605A29F52'; // Characteristic Names const CHARACTERISTIC_WATT_NAME = 'Consumption'; const CHARACTERISTIC_VOLT_NAME = 'Voltage'; const CHARACTERISTIC_AMPERE_NAME = 'Current'; const CHARACTERISTIC_KWH_NAME = 'Total Consumption'; const CHARACTERISTIC_PRODUCED_KWH_NAME = 'Total Production'; // Property names with fallbacks (first match wins) const POWER_NAMES = ['power', 'active_power', 'load']; const VOLTAGE_NAMES = ['voltage', 'mains_voltage', 'rms_voltage']; const CURRENT_NAMES = ['current']; const ENERGY_CONSUMED_NAMES = ['energy', 'consumed_energy', 'energy_consumed']; const ENERGY_PRODUCED_NAMES = ['produced_energy', 'energy_produced']; // All electrical property names (for filtering) const ALL_ELECTRICAL_NAMES = new Set([ ...POWER_NAMES, ...VOLTAGE_NAMES, ...CURRENT_NAMES, ...ENERGY_CONSUMED_NAMES, ...ENERGY_PRODUCED_NAMES, ]); function findExpose(exposes, names) { for (const name of names) { const found = exposes.find((e) => e.name === name && e.type === z2mModels_1.ExposesKnownTypes.NUMERIC); if (found) { return found; } } return undefined; } // Helper functions to create characteristics (following air_pressure.ts pattern) function createWattCharacteristic() { const characteristic = new hap_1.hap.Characteristic(CHARACTERISTIC_WATT_NAME, CHARACTERISTIC_WATT_UUID, { format: "float" /* hap.Formats.FLOAT */, perms: ["pr" /* hap.Perms.PAIRED_READ */, "ev" /* hap.Perms.NOTIFY */], minValue: 0, maxValue: 65535, minStep: 0.1, }); characteristic.value = 0; return characteristic; } function createVoltCharacteristic() { const characteristic = new hap_1.hap.Characteristic(CHARACTERISTIC_VOLT_NAME, CHARACTERISTIC_VOLT_UUID, { format: "float" /* hap.Formats.FLOAT */, perms: ["pr" /* hap.Perms.PAIRED_READ */, "ev" /* hap.Perms.NOTIFY */], minValue: 0, maxValue: 500, minStep: 0.1, }); characteristic.value = 0; return characteristic; } function createAmpereCharacteristic() { const characteristic = new hap_1.hap.Characteristic(CHARACTERISTIC_AMPERE_NAME, CHARACTERISTIC_AMPERE_UUID, { format: "float" /* hap.Formats.FLOAT */, perms: ["pr" /* hap.Perms.PAIRED_READ */, "ev" /* hap.Perms.NOTIFY */], minValue: 0, maxValue: 100, minStep: 0.001, }); characteristic.value = 0; return characteristic; } function createKilowattHourCharacteristic() { const characteristic = new hap_1.hap.Characteristic(CHARACTERISTIC_KWH_NAME, CHARACTERISTIC_KWH_UUID, { format: "float" /* hap.Formats.FLOAT */, perms: ["pr" /* hap.Perms.PAIRED_READ */, "ev" /* hap.Perms.NOTIFY */], minValue: 0, maxValue: 4294967295, minStep: 0.001, }); characteristic.value = 0; return characteristic; } function createProducedKilowattHourCharacteristic() { const characteristic = new hap_1.hap.Characteristic(CHARACTERISTIC_PRODUCED_KWH_NAME, CHARACTERISTIC_KWH_UUID, { format: "float" /* hap.Formats.FLOAT */, perms: ["pr" /* hap.Perms.PAIRED_READ */, "ev" /* hap.Perms.NOTIFY */], minValue: 0, maxValue: 4294967295, minStep: 0.001, }); characteristic.value = 0; return characteristic; } function createElectricalSensorService(displayName, subtype) { return new hap_1.hap.Service(displayName, ELECTRICAL_SERVICE_UUID, subtype); } // Helper to get or add a custom characteristic (for characteristics not in HAP spec) function getOrAddCustomCharacteristic(service, name, factory) { const existing = service.getCharacteristic(name); if (existing) { return existing; } return service.addCharacteristic(factory()); } class ElectricalSensorHandler { log; monitors = []; service; serviceName; identifier = ''; powerExpose; voltageExpose; currentExpose; energyExpose; constructor(accessory, electricalExposes, endpoint) { this.log = accessory.log; this.serviceName = accessory.getDefaultServiceDisplayName(endpoint); this.identifier = ElectricalSensorHandler.generateIdentifier(endpoint); this.service = accessory.getOrAddService(createElectricalSensorService(this.serviceName, endpoint)); accessory.log.debug(`Configuring ElectricalSensor for ${this.serviceName}`); // Add characteristics based on available exposes (using getOrAdd to handle cached services) if (electricalExposes.power) { this.powerExpose = electricalExposes.power; getOrAddCustomCharacteristic(this.service, CHARACTERISTIC_WATT_NAME, createWattCharacteristic); this.monitors.push(new monitor_1.PassthroughCharacteristicMonitor(electricalExposes.power.property, this.service, CHARACTERISTIC_WATT_NAME)); } if (electricalExposes.voltage) { this.voltageExpose = electricalExposes.voltage; getOrAddCustomCharacteristic(this.service, CHARACTERISTIC_VOLT_NAME, createVoltCharacteristic); this.monitors.push(new monitor_1.PassthroughCharacteristicMonitor(electricalExposes.voltage.property, this.service, CHARACTERISTIC_VOLT_NAME)); } if (electricalExposes.current) { this.currentExpose = electricalExposes.current; getOrAddCustomCharacteristic(this.service, CHARACTERISTIC_AMPERE_NAME, createAmpereCharacteristic); this.monitors.push(new monitor_1.PassthroughCharacteristicMonitor(electricalExposes.current.property, this.service, CHARACTERISTIC_AMPERE_NAME)); } if (electricalExposes.energy) { this.energyExpose = electricalExposes.energy; getOrAddCustomCharacteristic(this.service, CHARACTERISTIC_KWH_NAME, createKilowattHourCharacteristic); this.monitors.push(new monitor_1.PassthroughCharacteristicMonitor(electricalExposes.energy.property, this.service, CHARACTERISTIC_KWH_NAME)); } } get mainCharacteristics() { const characteristics = []; if (this.powerExpose) { characteristics.push(this.service.getCharacteristic(CHARACTERISTIC_WATT_NAME)); } if (this.voltageExpose) { characteristics.push(this.service.getCharacteristic(CHARACTERISTIC_VOLT_NAME)); } if (this.currentExpose) { characteristics.push(this.service.getCharacteristic(CHARACTERISTIC_AMPERE_NAME)); } if (this.energyExpose) { characteristics.push(this.service.getCharacteristic(CHARACTERISTIC_KWH_NAME)); } return characteristics; } get getableKeys() { const keys = []; if (this.powerExpose) { keys.push(this.powerExpose.property); } if (this.voltageExpose) { keys.push(this.voltageExpose.property); } if (this.currentExpose) { keys.push(this.currentExpose.property); } if (this.energyExpose) { keys.push(this.energyExpose.property); } return keys; } updateState(state) { this.monitors.forEach((m) => m.callback(state, this.log)); } static generateIdentifier(endpoint) { let identifier = ELECTRICAL_SERVICE_UUID; if (endpoint !== undefined) { identifier += '_' + endpoint.trim(); } return identifier; } } exports.ElectricalSensorHandler = ElectricalSensorHandler; class ProducedEnergySensorHandler { log; monitors = []; service; serviceName; identifier = ''; static SUBTYPE = 'produced'; producedEnergyExpose; constructor(accessory, producedEnergyExpose, endpoint) { this.log = accessory.log; this.producedEnergyExpose = producedEnergyExpose; const subType = endpoint === undefined ? ProducedEnergySensorHandler.SUBTYPE : `${endpoint} ${ProducedEnergySensorHandler.SUBTYPE}`; this.serviceName = accessory.getDefaultServiceDisplayName(subType); this.identifier = ProducedEnergySensorHandler.generateIdentifier(endpoint); this.service = accessory.getOrAddService(createElectricalSensorService(this.serviceName, subType)); accessory.log.debug(`Configuring ProducedEnergySensor for ${this.serviceName}`); // Add kWh characteristic for produced energy (using getOrAdd to handle cached services) getOrAddCustomCharacteristic(this.service, CHARACTERISTIC_PRODUCED_KWH_NAME, createProducedKilowattHourCharacteristic); this.monitors.push(new monitor_1.PassthroughCharacteristicMonitor(producedEnergyExpose.property, this.service, CHARACTERISTIC_PRODUCED_KWH_NAME)); } get mainCharacteristics() { return [this.service.getCharacteristic(CHARACTERISTIC_PRODUCED_KWH_NAME)]; } get getableKeys() { return [this.producedEnergyExpose.property]; } updateState(state) { this.monitors.forEach((m) => m.callback(state, this.log)); } static generateIdentifier(endpoint) { let identifier = `${ProducedEnergySensorHandler.SUBTYPE}_${ELECTRICAL_SERVICE_UUID}`; if (endpoint !== undefined) { identifier += '_' + endpoint.trim(); } return identifier; } } exports.ProducedEnergySensorHandler = ProducedEnergySensorHandler; class ElectricalSensorCreator { createServicesFromExposes(accessory, exposes) { // Filter for numeric electrical exposes const electricalExposes = exposes .filter((e) => (0, z2mModels_1.exposesHasProperty)(e) && (0, z2mModels_1.exposesIsPublished)(e) && e.type === z2mModels_1.ExposesKnownTypes.NUMERIC && ALL_ELECTRICAL_NAMES.has(e.name)) .map((e) => e); if (electricalExposes.length === 0) { return; } // Group by endpoint const endpointMap = (0, helpers_1.groupByEndpoint)(electricalExposes); endpointMap.forEach((endpointExposes, endpoint) => { // Find exposes for consumed electrical properties (power, voltage, current, energy) const electricalData = { power: findExpose(endpointExposes, POWER_NAMES), voltage: findExpose(endpointExposes, VOLTAGE_NAMES), current: findExpose(endpointExposes, CURRENT_NAMES), energy: findExpose(endpointExposes, ENERGY_CONSUMED_NAMES), }; // Only create electrical sensor if we have power, current, or energy // (voltage alone is typically just battery voltage, not useful for power monitoring) const hasPowerCurrentOrEnergy = electricalData.power !== undefined || electricalData.current !== undefined || electricalData.energy !== undefined; if (hasPowerCurrentOrEnergy && !accessory.isServiceHandlerIdKnown(ElectricalSensorHandler.generateIdentifier(endpoint))) { this.createElectricalSensorHandler(accessory, electricalData, endpoint); } // Find and create produced energy sensor separately const producedEnergyExpose = findExpose(endpointExposes, ENERGY_PRODUCED_NAMES); if (producedEnergyExpose !== undefined && !accessory.isServiceHandlerIdKnown(ProducedEnergySensorHandler.generateIdentifier(endpoint))) { this.createProducedEnergySensorHandler(accessory, producedEnergyExpose, endpoint); } }); } createElectricalSensorHandler(accessory, electricalExposes, endpoint) { try { const handler = new ElectricalSensorHandler(accessory, electricalExposes, endpoint); accessory.registerServiceHandler(handler); } catch (error) { accessory.log.warn(`Failed to setup electrical sensor for accessory ${accessory.displayName} on endpoint "${endpoint}": ${error}`); } } createProducedEnergySensorHandler(accessory, producedEnergyExpose, endpoint) { try { const handler = new ProducedEnergySensorHandler(accessory, producedEnergyExpose, endpoint); accessory.registerServiceHandler(handler); } catch (error) { accessory.log.warn(`Failed to setup produced energy sensor for accessory ${accessory.displayName} on endpoint "${endpoint}": ${error}`); } } } exports.ElectricalSensorCreator = ElectricalSensorCreator; //# sourceMappingURL=electrical.js.map