homebridge-sense-energy-monitor
Version:
Enhanced Homebridge plugin for Sense Home Energy Monitor with comprehensive API integration and real-time monitoring
102 lines • 5 kB
JavaScript
import { getEveCharacteristics } from './eveCharacteristics.js';
import { PLUGIN_VERSION } from './settings.js';
const PERSIST_INTERVAL_MS = 5 * 60 * 1000;
const MAX_ENERGY_GAP_HOURS = 1;
const DEFAULT_VOLTAGE = 120;
export class EnergyMonitorAccessory {
platform;
accessory;
outletService;
eve;
historyService = null;
lastEnergyAt = 0;
lastPersistAt = Date.now();
constructor(platform, accessory) {
this.platform = platform;
this.accessory = accessory;
const { Service: HapService, Characteristic } = platform.api.hap;
this.eve = getEveCharacteristics(platform.api);
const information = accessory.getService(HapService.AccessoryInformation)
?? accessory.addService(HapService.AccessoryInformation);
information
.setCharacteristic(Characteristic.Manufacturer, 'Sense Labs')
.setCharacteristic(Characteristic.Model, 'Energy Monitor')
.setCharacteristic(Characteristic.SerialNumber, String(platform.senseApi?.monitorId ?? 'Unknown'))
.setCharacteristic(Characteristic.FirmwareRevision, PLUGIN_VERSION);
// Reuse the cached Outlet service — removing and re-adding it every
// boot is what used to break HomeKit automations.
this.outletService = accessory.getService(HapService.Outlet)
?? accessory.addService(HapService.Outlet, accessory.displayName);
this.outletService.getCharacteristic(Characteristic.On)
.onGet(() => this.isOn())
.onSet(() => {
// The outlet is a read-only power indicator; snap back to reality.
setTimeout(() => {
this.outletService.updateCharacteristic(Characteristic.On, this.isOn());
}, 100);
});
this.outletService.getCharacteristic(Characteristic.OutletInUse)
.onGet(() => this.isOn());
for (const characteristic of [this.eve.Consumption, this.eve.Voltage, this.eve.ElectricCurrent, this.eve.TotalConsumption]) {
if (!this.outletService.testCharacteristic(characteristic)) {
this.outletService.addCharacteristic(new characteristic());
}
}
const context = this.context();
context.type = 'main';
context.totalConsumptionKwh ??= 0;
this.attachHistory();
}
context() {
return this.accessory.context;
}
isOn() {
const power = this.platform.senseApi?.realtime.power ?? 0;
return power > (this.platform.pluginConfig?.devicePowerThreshold ?? 10);
}
attachHistory() {
const FakeGato = this.platform.fakeGatoService;
if (!this.platform.pluginConfig?.enableHistory || !FakeGato) {
return;
}
try {
this.historyService = new FakeGato('energy', this.accessory, {
storage: 'fs',
path: this.platform.api.user.storagePath(),
});
}
catch (error) {
this.platform.log.warn(`Failed to create Eve history service: ${error.message}`);
}
}
update(data) {
const { Characteristic } = this.platform.api.hap;
const isOn = data.power > (this.platform.pluginConfig?.devicePowerThreshold ?? 10);
const voltage = data.voltage.length > 0
? data.voltage.reduce((sum, v) => sum + v, 0) / data.voltage.length
: DEFAULT_VOLTAGE;
const current = voltage > 0 ? data.power / voltage : 0;
// Integrate power into the lifetime kWh accumulator. Eve expects a
// monotonic total, so this lives in accessory.context and survives
// restarts via the accessory cache.
const now = Date.now();
const context = this.context();
if (this.lastEnergyAt > 0 && now > this.lastEnergyAt) {
const hours = Math.min((now - this.lastEnergyAt) / 3_600_000, MAX_ENERGY_GAP_HOURS);
context.totalConsumptionKwh = (context.totalConsumptionKwh ?? 0) + (data.power * hours) / 1000;
}
this.lastEnergyAt = now;
this.outletService.updateCharacteristic(Characteristic.On, isOn);
this.outletService.updateCharacteristic(Characteristic.OutletInUse, isOn);
this.outletService.updateCharacteristic(this.eve.Consumption, data.power);
this.outletService.updateCharacteristic(this.eve.Voltage, Math.round(voltage * 10) / 10);
this.outletService.updateCharacteristic(this.eve.ElectricCurrent, Math.round(current * 100) / 100);
this.outletService.updateCharacteristic(this.eve.TotalConsumption, Math.round((context.totalConsumptionKwh ?? 0) * 1000) / 1000);
this.historyService?.addEntry({ time: Math.round(now / 1000), power: data.power });
if (now - this.lastPersistAt >= PERSIST_INTERVAL_MS) {
this.lastPersistAt = now;
this.platform.api.updatePlatformAccessories([this.accessory]);
}
}
}
//# sourceMappingURL=energyMonitorAccessory.js.map