UNPKG

homebridge-s0meter

Version:

S0 power meter accessory homebridge: https://github.com/ma-ku/homebridge-s0meter

300 lines (213 loc) 9.63 kB
'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]; } };