homebridge-s0meter
Version:
S0 power meter accessory homebridge: https://github.com/ma-ku/homebridge-s0meter
300 lines (213 loc) • 9.63 kB
JavaScript
'use strict';
const PluginName = "homebridge-s0meter";
const AccessoryName = "S0Meter";
const SerialPort = require('serialport')
const Readline = require('@serialport/parser-readline')
const FS = require('fs');
const shell = require('shelljs');
const Devices = require('./devices.json');
const { PerformanceObserver, performance } = require('perf_hooks');
const EventEmitter = require('events').EventEmitter;
let Accessory, Homebridge, Service, Characteristic, UUIDGen, FakeGatoHistoryService, EnergyCharacteristics;
// This pattern will be used to extract the power consumption
const patPowerConsumption = /1\.8\.1\((\d+\.\d+)\*(.*)\)/g;
const patDeviceIdentifier = /0\.0\((.+)\)/g;
/**
* This is the initializer for the javascript module
*/
module.exports = function(homebridge) {
Homebridge = homebridge;
Accessory = homebridge.platformAccessory;
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
UUIDGen = homebridge.hap.uuid;
EnergyCharacteristics = require('./energyCharacteristics').EnergyCharacteristics(Characteristic);
FakeGatoHistoryService = require('fakegato-history')(homebridge);
homebridge.registerAccessory('homebridge-s0meter', 'S0Meter', S0Meter);
};
class S0Meter {
constructor(log, config) {
// global vars
this.log = log;
this.config = config;
this.log.info("S0Meter accessory init");
this.validateConfig(config);
this.name = config.name;
this.portConfig = config.connection;
this.deviceInfo = Devices[config.type];
this.deviceInfo.identifier = config.identifier;
// We will request an update from the stick every 5 seconds
this.defaultUpdateInterval = 5000;
this.updateInterval = this.defaultUpdateInterval;
if (config.updateInterval) {
this.updateInterval = config.updateInterval;
}
this.log(config);
this.log.info("Update inverval is %f seconds", this.updateInterval / 1000.0);
// Create a serial port connection
this.port = new SerialPort(this.portConfig.port, this.portConfig);
this.totalConsumption = 0;
this.currentConsumption = 1;
// we will pass responses through this parser to read line by line
const parser = new Readline({
delimiter: '\r\n'
});
this.port.pipe(parser)
parser.on('data', line => { this.processResponse(line); });
this.initServices();
this.sendInit();
}
sendInit() {
this.log.info("Starting request from port");
this.port.write(this.deviceInfo.initRequest);
}
requestInfo() {
this.log.info("Requesting data from port");
this.port.write(this.deviceInfo.infoRequest);
}
processResponse(line) {
// this.log.info(line);
if (line.match(/!/g)) {
var accessory = this;
setTimeout(function() { accessory.sendInit(); }, this.updateInterval);
}
if (line.match(/\//gm)) {
this.requestInfo();
}
var match = patPowerConsumption.exec(line);
if (match != null) {
this.log.info("Found Power Consumption");
var kWh = parseFloat(match[1]);
if (this.totalConsumption != 0) {
var delta = kWh - this.totalConsumption;
var elapsed = this.lastMeasurement;
this.log.info("Messung : " + this.lastMeasurement);
this.lastMeasurement = Date.now();
elapsed = this.lastMeasurement - elapsed;
this.log.info(delta + " - " + elapsed)
if ((elapsed > 0) && (delta > 0)) {
delta *= 1000; // get Wh
elapsed *= 1000; // get Seconds
this.currentConsumption = delta;
this.log.info("Current consumption: %f", this.currentConsumption);
}
}
else {
this.lastMeasurement = Date.now();
this.currentConsumption = 0.0;
}
this.totalConsumption = kWh;
this.energyService.getCharacteristic(EnergyCharacteristics.TotalConsumption)
.updateValue(this.totalConsumption);
this.loggingService.addEntry({
time: Date.now(),
power: this.totalConsumption
});
}
match = patDeviceIdentifier.exec(line);
if (match != null) {
var serial = match[1];
serial = serial.trim();
this.deviceInfo.identifier = serial;
this.log.info("Device serial: %s", serial);
// this.infoService.getCharacteristic(Characteristic.SerialNumber)
// .updateValue(this.deviceInfo.identifier);
}
}
validateConfig(config) {
if (!config.name) {
throw new Error("Missing mandatory config 'name'");
}
if (!config.type) {
throw new Error("Missing mandatory config 'type'");
}
if (!Devices[config.type]) {
throw new Error("Unsupported meter type ${config.type}");
}
if (!config.identifier) {
throw new Error("Missing mandatory config 'identifier'");
}
}
onAfterFakeGatoHistoryLoaded() { }
fakeGatoHistoryLoaded() {
if (this.loggingService.isHistoryLoaded() == false) {
this.log.debug("%s (%s / %s) > Wait of FakeGato history loaded.", (this.constructor).name, this.dDevice.id, this.dDevice.name);
setTimeout(this.fakeGatoHistoryLoaded.bind(this), 100);
}
else {
this.log.debug("%s (%s / %s) > FakeGato history loaded.", (this.constructor).name, this.dDevice.id, this.dDevice.name);
this.onAfterFakeGatoHistoryLoaded();
}
}
addFakeGatoHistory(type, disTimer) {
var folder = this.Homebridge.user.storagePath() + '/.homebridge-devolo/fakegato-history';
shell.mkdir('-p', folder);
this.log.info("%s (%s / %s) > FakeGato initialized (%s).", (this.constructor).name, this.dDevice.id, this.dDevice.name, folder);
this.loggingService = new FakeGatoHistoryService(type, this, { storage: 'fs', path: folder, disableTimer: disTimer });
this.fakeGatoHistoryLoaded();
}
addFakeGatoEntry(data) {
if ((this.loggingService != undefined) && (data != undefined)) {
data.time = moment().unix();
this.loggingService.addEntry(data);
this.log.debug("%s (%s / %s) > FakeGato > New entry saved: %s.", (this.constructor).name, this.dDevice.id, this.dDevice.name, JSON.stringify(data));
}
}
initServices() {
var info = new Service.AccessoryInformation();
info.getCharacteristic(Characteristic.Manufacturer)
.setValue(this.deviceInfo.manufacturer.toString());
info.getCharacteristic(Characteristic.Model)
.setValue(this.deviceInfo.model.toString());
info.getCharacteristic(Characteristic.SerialNumber)
.setValue(this.deviceInfo.identifier);
// info.getCharacteristic(Characteristic.SerialNumber)
// .on('get', this.getDeviceSerial.bind(this));
this.infoService = info;
// Energy-Meter Characteristics
var energy = new Service.Outlet(this.name);
energy.addCharacteristic(EnergyCharacteristics.TotalConsumption);
energy.addCharacteristic(EnergyCharacteristics.CurrentConsumption);
this.energyService = energy;
energy.getCharacteristic(EnergyCharacteristics.TotalConsumption)
.setValue(0);
energy.getCharacteristic(EnergyCharacteristics.CurrentConsumption)
.setValue(0);
energy.getCharacteristic(EnergyCharacteristics.TotalConsumption)
.on('get', this.getTotalConsumption.bind(this));
energy.getCharacteristic(EnergyCharacteristics.CurrentConsumption)
.on('get', this.getCurrentConsumption.bind(this));
this.energyService = energy;
var folder = Homebridge.user.storagePath() + '/fakegato/';
shell.mkdir('-p', folder);
this.loggingService = new FakeGatoHistoryService("energy", this, {
disableTimer: false,
disableRepeatLastData: true,
storage: 'fs',
length : Math.pow(2, 14),
path: folder,
filename: this.name.replace(" ", "_")
});
this.log.info("Fakegato storage is " + folder + this.name.replace(" ", "_"));
}
getDeviceSerial(callback) {
this.log.info("Requested Serial: %s", this.deviceInfo.identifier);
callback(null, this.deviceInfo.identifier);
}
getTotalConsumption(callback) {
this.log("Requested Current Energy: %s", this.totalConsumption);
var totalPower = this.totalConsumption;
this.ExtraPersistedData = this.loggingService.getExtraPersistedData();
if (this.ExtraPersistedData != undefined) {
totalPower = this.ExtraPersistedData.totalPower;
}
callback(null, totalPower);
}
getCurrentConsumption(callback) {
this.log("Requested Current Power: %s", this.currentConsumption);
callback(null, this.currentConsumption);
}
getServices() {
return [this.infoService, this.energyService, this.loggingService];
}
};