homebridge-z2m
Version:
Expose your Zigbee devices to HomeKit with ease, by integrating Zigbee2MQTT with Homebridge.
284 lines • 13.2 kB
JavaScript
"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