fakegato-history
Advanced tools
Comparing version 0.3.8 to 0.4.0
@@ -6,2 +6,3 @@ /*jshint esversion: 6,node: true,-W041: false */ | ||
const FakeGatoTimer = require('./fakegato-timer').FakeGatoTimer; | ||
const FakeGatoStorage = require('./fakegato-storage').FakeGatoStorage; | ||
const moment = require('moment'); | ||
@@ -65,2 +66,6 @@ | ||
return val.charAt(0).toUpperCase() + val.substr(1); | ||
}, | ||
precisionRound = function (number, precision) { | ||
var factor = Math.pow(10, precision); | ||
return Math.round(number * factor) / factor; | ||
}; | ||
@@ -136,9 +141,9 @@ | ||
FakeGatoHistoryService.UUID = 'E863F007-079E-48FF-8F27-9C2605A29F52'; | ||
var thisAccessory={}; | ||
class FakeGatoHistory extends Service { | ||
constructor(accessoryType, accessory, size, minutes) { | ||
constructor(accessoryType, accessory, optionalParams) { | ||
super(accessory.displayName + " History", FakeGatoHistoryService.UUID); | ||
var entry2address = function (val) { | ||
var entry2address = function (val) { // not used ? | ||
var temp = val % this.memorySize; | ||
@@ -148,13 +153,57 @@ return temp; | ||
this.size = size || 4032 ; | ||
this.minutes = minutes || 10; // Optional timer length | ||
this.accessoryName = accessory.displayName; | ||
this.log = accessory.log; | ||
if (homebridge.globalFakeGatoTimer === undefined) | ||
homebridge.globalFakeGatoTimer = new FakeGatoTimer({ | ||
minutes: this.minutes, | ||
log: this.log | ||
if(typeof(optionalParams) === 'object') { | ||
this.size = optionalParams.size || 4032; | ||
this.minutes = optionalParams.minutes || 10; // Optional timer length | ||
this.storage = optionalParams.storage; // 'fs' or 'googleDrive' | ||
this.path = optionalParams.path || optionalParams.folder; | ||
this.disableTimer = optionalParams.disableTimer || false; | ||
} else { | ||
this.size = optionalParams || 4032; | ||
this.minutes = 10; | ||
this.disableTimer = false; | ||
} | ||
thisAccessory = accessory; | ||
this.accessoryName = thisAccessory.displayName; | ||
this.log = thisAccessory.log || {}; | ||
if (!this.log.debug) { | ||
this.log.debug = function() {}; | ||
} | ||
if(!this.disableTimer) { | ||
if (homebridge.globalFakeGatoTimer === undefined) | ||
homebridge.globalFakeGatoTimer = new FakeGatoTimer({ | ||
minutes: this.minutes, | ||
log: this.log | ||
}); | ||
} | ||
if(this.storage !== undefined) { | ||
this.loaded=false; | ||
if (homebridge.globalFakeGatoStorage === undefined) { | ||
homebridge.globalFakeGatoStorage = new FakeGatoStorage({ | ||
log: this.log | ||
}); | ||
} | ||
homebridge.globalFakeGatoStorage.addWriter(this,{ | ||
storage: this.storage, | ||
path: this.path, | ||
keyPath: optionalParams.keyPath || homebridge.user.storagePath() || undefined, | ||
onReady:function(){ | ||
this.load(function(err,loaded){ | ||
//this.log.debug("Loaded",loaded); | ||
//this.registerEvents(); | ||
if(err) this.log.debug('Load error :',err); | ||
else { | ||
if(loaded) this.log.debug('History Loaded from Persistant Storage'); | ||
this.loaded=true; | ||
} | ||
}.bind(this)); | ||
}.bind(this) | ||
}); | ||
} | ||
switch (accessoryType) { | ||
@@ -164,30 +213,49 @@ case TYPE_WEATHER: | ||
this.accessoryType117 = "07"; | ||
if(!this.disableTimer) { | ||
homebridge.globalFakeGatoTimer.subscribe(this, function (params) { // callback | ||
var backLog = params.backLog || []; | ||
var previousAvrg = params.previousAvrg || {}; | ||
var timer = params.timer; | ||
var fakegato = this.service; | ||
var calc = { | ||
sum: {}, | ||
num: {}, | ||
avrg: {} | ||
}; | ||
homebridge.globalFakeGatoTimer.subscribe(this, function (backLog, timer, immediate) { // callback | ||
var fakegato = this.service; | ||
var calc = { | ||
sum: {}, | ||
num: {}, | ||
avrg: {} | ||
}; | ||
for (var h in backLog) { | ||
if (backLog.hasOwnProperty(h)) { // only valid keys | ||
for (var key in backLog[h]) { // each record | ||
if (backLog[h].hasOwnProperty(key) && key != 'time') { // except time | ||
if (!calc.sum[key]) | ||
calc.sum[key] = 0; | ||
if (!calc.num[key]) | ||
calc.num[key] = 0; | ||
calc.sum[key] += backLog[h][key]; | ||
calc.num[key]++; | ||
calc.avrg[key] = calc.sum[key] / calc.num[key]; | ||
for (var h in backLog) { | ||
if (backLog.hasOwnProperty(h)) { // only valid keys | ||
for (let key in backLog[h]) { // each record | ||
if (backLog[h].hasOwnProperty(key) && key != 'time') { // except time | ||
if (!calc.sum[key]) | ||
calc.sum[key] = 0; | ||
if (!calc.num[key]) | ||
calc.num[key] = 0; | ||
calc.sum[key] += backLog[h][key]; | ||
calc.num[key]++; | ||
calc.avrg[key] = precisionRound(calc.sum[key] / calc.num[key],2); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
calc.avrg.time = moment().unix(); // set the time of the avrg | ||
fakegato._addEntry(calc.avrg); | ||
timer.emptyData(fakegato);// should i ? or repeat the last datas ? | ||
}); | ||
calc.avrg.time = moment().unix(); // set the time of the avrg | ||
for (let key in previousAvrg) { // each record of previous average | ||
if (previousAvrg.hasOwnProperty(key) && key != 'time') { // except time | ||
if( !backLog.length ||//calc.avrg[key] == 0 || // zero value | ||
calc.avrg[key] === undefined) // no key (meaning no value received for this key yet) | ||
{ | ||
calc.avrg[key]=previousAvrg[key]; | ||
} | ||
} | ||
} | ||
if(Object.keys(calc.avrg).length > 1) { | ||
fakegato._addEntry(calc.avrg); | ||
timer.emptyData(fakegato); | ||
} | ||
return calc.avrg; | ||
}); | ||
} | ||
break; | ||
@@ -197,2 +265,47 @@ case TYPE_ENERGY: | ||
this.accessoryType117 = "1f"; | ||
if(!this.disableTimer) { | ||
homebridge.globalFakeGatoTimer.subscribe(this, function (params) { // callback | ||
var backLog = params.backLog || []; | ||
var previousAvrg = params.previousAvrg || {}; | ||
var timer = params.timer; | ||
var fakegato = this.service; | ||
var calc = { | ||
sum: {}, | ||
num: {}, | ||
avrg: {} | ||
}; | ||
for (var h in backLog) { | ||
if (backLog.hasOwnProperty(h)) { // only valid keys | ||
for (let key in backLog[h]) { // each record | ||
if (backLog[h].hasOwnProperty(key) && key != 'time') { // except time | ||
if (!calc.sum[key]) | ||
calc.sum[key] = 0; | ||
if (!calc.num[key]) | ||
calc.num[key] = 0; | ||
calc.sum[key] += backLog[h][key]; | ||
calc.num[key]++; | ||
calc.avrg[key] = precisionRound(calc.sum[key] / calc.num[key],2); | ||
} | ||
} | ||
} | ||
} | ||
calc.avrg.time = moment().unix(); // set the time of the avrg | ||
for (let key in previousAvrg) { // each record of previous average | ||
if (previousAvrg.hasOwnProperty(key) && key != 'time') { // except time | ||
if( !backLog.length ||//calc.avrg[key] == 0 || // zero value | ||
calc.avrg[key] === undefined) // no key (meaning no value received for this key yet) | ||
{ | ||
calc.avrg[key]=previousAvrg[key]; | ||
} | ||
} | ||
} | ||
fakegato._addEntry(calc.avrg); | ||
timer.emptyData(fakegato); | ||
return calc.avrg; | ||
}); | ||
} | ||
break; | ||
@@ -202,30 +315,49 @@ case TYPE_ROOM: | ||
this.accessoryType117 = "0f"; | ||
if(!this.disableTimer) { | ||
homebridge.globalFakeGatoTimer.subscribe(this, function (params) { // callback | ||
var backLog = params.backLog || []; | ||
var previousAvrg = params.previousAvrg || {}; | ||
var timer = params.timer; | ||
var fakegato = this.service; | ||
var calc = { | ||
sum: {}, | ||
num: {}, | ||
avrg: {} | ||
}; | ||
homebridge.globalFakeGatoTimer.subscribe(this, function (backLog, timer, immediate) { // callback | ||
var fakegato = this.service; | ||
var calc = { | ||
sum: {}, | ||
num: {}, | ||
avrg: {} | ||
}; | ||
for (var h in backLog) { | ||
if (backLog.hasOwnProperty(h)) { // only valid keys | ||
for (var key in backLog[h]) { // each record | ||
if (backLog[h].hasOwnProperty(key) && key != 'time') { // except time | ||
if (!calc.sum[key]) | ||
calc.sum[key] = 0; | ||
if (!calc.num[key]) | ||
calc.num[key] = 0; | ||
calc.sum[key] += backLog[h][key]; | ||
calc.num[key]++; | ||
calc.avrg[key] = calc.sum[key] / calc.num[key]; | ||
for (var h in backLog) { | ||
if (backLog.hasOwnProperty(h)) { // only valid keys | ||
for (let key in backLog[h]) { // each record | ||
if (backLog[h].hasOwnProperty(key) && key != 'time') { // except time | ||
if (!calc.sum[key]) | ||
calc.sum[key] = 0; | ||
if (!calc.num[key]) | ||
calc.num[key] = 0; | ||
calc.sum[key] += backLog[h][key]; | ||
calc.num[key]++; | ||
calc.avrg[key] = precisionRound(calc.sum[key] / calc.num[key],2); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
calc.avrg.time = moment().unix(); // set the time of the avrg | ||
fakegato._addEntry(calc.avrg); | ||
timer.emptyData(fakegato); // should i ? or repeat the last datas ? | ||
}); | ||
calc.avrg.time = moment().unix(); // set the time of the avrg | ||
for (let key in previousAvrg) { // each record of previous average | ||
if (previousAvrg.hasOwnProperty(key) && key != 'time') { // except time | ||
if( !backLog.length ||//calc.avrg[key] == 0 || // zero value | ||
calc.avrg[key] === undefined) // no key (meaning no value received for this key yet) | ||
{ | ||
calc.avrg[key]=previousAvrg[key]; | ||
} | ||
} | ||
} | ||
if(Object.keys(calc.avrg).length > 1) { | ||
fakegato._addEntry(calc.avrg); | ||
timer.emptyData(fakegato); | ||
} | ||
return calc.avrg; | ||
}); | ||
} | ||
break; | ||
@@ -235,19 +367,25 @@ case TYPE_DOOR: | ||
this.accessoryType117 = "01"; | ||
if(!this.disableTimer) { | ||
homebridge.globalFakeGatoTimer.subscribe(this, function (params) { // callback | ||
var backLog = params.backLog || []; | ||
var immediate = params.immediate; | ||
var fakegato = this.service; | ||
var actualEntry={}; | ||
homebridge.globalFakeGatoTimer.subscribe(this, function (backLog, timer, immediate) { // callback | ||
var fakegato = this.service; | ||
var actualEntry={}; | ||
if(backLog.length) { | ||
if(!immediate) { | ||
actualEntry.time = moment().unix(); | ||
actualEntry.status = backLog[0].status; | ||
} | ||
else { | ||
actualEntry.time = backLog[0].time; | ||
actualEntry.status = backLog[0].status; | ||
} | ||
fakegato.log.debug('**Fakegato-timer callbackDoor: ', fakegato.accessoryName, ', immediate: ',immediate,', entry: ',actualEntry); | ||
if(!immediate) { | ||
actualEntry.time = moment().unix(); | ||
actualEntry.status = backLog[0].status; | ||
} | ||
else { | ||
actualEntry.time = backLog[0].time; | ||
actualEntry.status = backLog[0].status; | ||
} | ||
fakegato.log.debug('**Fakegato-timer callbackDoor: ', fakegato.accessoryName, ', immediate: ',immediate,', entry: ',actualEntry); | ||
fakegato._addEntry(actualEntry); | ||
}); | ||
fakegato._addEntry(actualEntry); | ||
} | ||
}); | ||
} | ||
break; | ||
@@ -257,19 +395,25 @@ case TYPE_MOTION: | ||
this.accessoryType117 = "02"; | ||
if(!this.disableTimer) { | ||
homebridge.globalFakeGatoTimer.subscribe(this, function (params) { // callback | ||
var backLog = params.backLog || []; | ||
var immediate = params.immediate; | ||
var fakegato = this.service; | ||
var actualEntry={}; | ||
homebridge.globalFakeGatoTimer.subscribe(this, function (backLog, timer, immediate) { // callback | ||
var fakegato = this.service; | ||
var actualEntry={}; | ||
if(backLog.length) { | ||
if(!immediate) { | ||
actualEntry.time = moment().unix(); | ||
actualEntry.status = backLog[0].status; | ||
} | ||
else { | ||
actualEntry.time = backLog[0].time; | ||
actualEntry.status = backLog[0].status; | ||
} | ||
fakegato.log.debug('**Fakegato-timer callbackMotion: ', fakegato.accessoryName, ', immediate: ',immediate,', entry: ',actualEntry); | ||
if(!immediate) { | ||
actualEntry.time = moment().unix(); | ||
actualEntry.status = backLog[0].status; | ||
} | ||
else { | ||
actualEntry.time = backLog[0].time; | ||
actualEntry.status = backLog[0].status; | ||
} | ||
fakegato.log.debug('**Fakegato-timer callbackMotion: ', fakegato.accessoryName, ', immediate: ',immediate,', entry: ',actualEntry); | ||
fakegato._addEntry(actualEntry); | ||
}); | ||
fakegato._addEntry(actualEntry); | ||
} | ||
}); | ||
} | ||
break; | ||
@@ -285,3 +429,3 @@ case TYPE_THERMO: | ||
this.lastEntry = 0; | ||
this.history = []; | ||
this.history = ["noValue"]; | ||
this.memorySize = this.size; | ||
@@ -295,10 +439,20 @@ this.usedMemory = 0; | ||
this.dataStream = ''; | ||
this.IntervalID = null; | ||
if ( typeof accessory.getService === "function" ) { | ||
this.saving=false; | ||
this.registerEvents(); | ||
if(this.storage === undefined) { | ||
this.loaded=true; | ||
} | ||
} | ||
registerEvents() { | ||
this.log.debug('Registring Events',thisAccessory.displayName); | ||
if ( typeof thisAccessory.getService === "function" ) { | ||
// Platform API | ||
this.service = accessory.getService(FakeGatoHistoryService); | ||
this.log.debug('Platform',thisAccessory.displayName); | ||
this.service = thisAccessory.getService(FakeGatoHistoryService); | ||
if (this.service === undefined) { | ||
this.service = accessory.addService(FakeGatoHistoryService, ucfirst(accessoryType) + ' History', accessoryType); | ||
this.service = thisAccessory.addService(FakeGatoHistoryService, ucfirst(thisAccessory.displayName) + ' History', this.accessoryType); | ||
} | ||
@@ -318,2 +472,3 @@ | ||
// Accessory API | ||
this.log.debug('Accessory',thisAccessory.displayName); | ||
@@ -343,11 +498,17 @@ this.addCharacteristic(S2R1Characteristic); | ||
addEntry(entry) { | ||
var selfService = this; | ||
switch (this.accessoryType) { | ||
case TYPE_DOOR: | ||
case TYPE_MOTION: | ||
homebridge.globalFakeGatoTimer.addData({entry: entry, service: this, immediateCallback: true}); | ||
if(!this.disableTimer) | ||
homebridge.globalFakeGatoTimer.addData({entry: entry, service: this, immediateCallback: true}); | ||
else | ||
this._addEntry(entry); | ||
break; | ||
case TYPE_WEATHER: | ||
case TYPE_ROOM: | ||
homebridge.globalFakeGatoTimer.addData({entry: entry, service: this}); | ||
case TYPE_ENERGY: | ||
if(!this.disableTimer) | ||
homebridge.globalFakeGatoTimer.addData({entry: entry, service: this}); | ||
else | ||
this._addEntry(entry); | ||
break; | ||
@@ -362,64 +523,135 @@ default: | ||
_addEntry(entry) { | ||
if(this.loaded) { | ||
var entry2address = function (val) { | ||
return val % this.memorySize; | ||
} | ||
.bind(this); | ||
var val; | ||
var entry2address = function (val) { | ||
return val % this.memorySize; | ||
} | ||
.bind(this); | ||
if (this.usedMemory < this.memorySize) { | ||
this.usedMemory++; | ||
this.firstEntry = 0; | ||
this.lastEntry = this.usedMemory; | ||
} else { | ||
this.firstEntry++; | ||
this.lastEntry = this.firstEntry + this.usedMemory; | ||
} | ||
var val; | ||
if (this.refTime == 0) { | ||
this.refTime = entry.time - EPOCH_OFFSET; | ||
this.history[this.lastEntry] = { | ||
time: entry.time, | ||
setRefTime: 1 | ||
}; | ||
this.initialTime=entry.time; | ||
this.lastEntry++; | ||
this.usedMemory++; | ||
} | ||
if (this.usedMemory < this.memorySize) { | ||
this.usedMemory++; | ||
this.firstEntry = 0; | ||
this.lastEntry = this.usedMemory; | ||
this.history[entry2address(this.lastEntry)] = (entry); | ||
if (this.usedMemory < this.memorySize) { | ||
val = Format( | ||
'%s00000000%s%s%s%s%s000000000101', | ||
numToHex(swap32(entry.time - this.refTime - EPOCH_OFFSET), 8), | ||
numToHex(swap32(this.refTime), 8), | ||
this.accessoryType116, | ||
numToHex(swap16(this.usedMemory+1), 4), | ||
numToHex(swap16(this.memorySize), 4), | ||
numToHex(swap32(this.firstEntry), 8)); | ||
} else { | ||
val = Format( | ||
'%s00000000%s%s%s%s%s000000000101', | ||
numToHex(swap32(entry.time - this.refTime - EPOCH_OFFSET), 8), | ||
numToHex(swap32(this.refTime), 8), | ||
this.accessoryType116, | ||
numToHex(swap16(this.usedMemory), 4), | ||
numToHex(swap16(this.memorySize), 4), | ||
numToHex(swap32(this.firstEntry+1), 8)); | ||
} | ||
if (this.service === undefined) { // Accessory API | ||
this.getCharacteristic(S2R1Characteristic).setValue(hexToBase64(val)); | ||
} | ||
else { // Platform API | ||
this.service.getCharacteristic(S2R1Characteristic).setValue(hexToBase64(val)); | ||
} | ||
this.log.debug("First entry %s: %s", this.accessoryName, this.firstEntry.toString(16)); | ||
this.log.debug("Last entry %s: %s", this.accessoryName, this.lastEntry.toString(16)); | ||
this.log.debug("Used memory %s: %s", this.accessoryName, this.usedMemory.toString(16)); | ||
this.log.debug("116 %s: %s", this.accessoryName, val); | ||
if(this.storage !== undefined) this.save(); | ||
} else { | ||
this.firstEntry++; | ||
this.lastEntry = this.firstEntry + this.usedMemory; | ||
setTimeout(function(){ // retry in 100ms | ||
this._addEntry(entry); | ||
}.bind(this),100); | ||
} | ||
} | ||
getInitialTime() { | ||
return this.initialTime; | ||
} | ||
save() { | ||
if(this.loaded) { | ||
if (this.refTime == 0) { | ||
this.refTime = entry.time - EPOCH_OFFSET; | ||
this.history[this.lastEntry] = { | ||
time: entry.time, | ||
setRefTime: 1 | ||
}; | ||
this.lastEntry++; | ||
this.usedMemory++; | ||
} | ||
let data = { | ||
firstEntry:this.firstEntry, | ||
lastEntry :this.lastEntry, | ||
usedMemory:this.usedMemory, | ||
refTime :this.refTime, | ||
initialTime:this.initialTime, | ||
history :this.history | ||
}; | ||
homebridge.globalFakeGatoStorage.write({ | ||
service: this, | ||
data:typeof(data) === "object" ? JSON.stringify(data) : data | ||
}); | ||
this.history[entry2address(this.lastEntry)] = (entry); | ||
if (this.usedMemory < this.memorySize) { | ||
val = Format( | ||
'%s00000000%s%s%s%s%s000000000101', | ||
numToHex(swap32(entry.time - this.refTime - EPOCH_OFFSET), 8), | ||
numToHex(swap32(this.refTime), 8), | ||
this.accessoryType116, | ||
numToHex(swap16(this.usedMemory+1), 4), | ||
numToHex(swap16(this.memorySize), 4), | ||
numToHex(swap32(this.firstEntry), 8)); | ||
} else { | ||
val = Format( | ||
'%s00000000%s%s%s%s%s000000000101', | ||
numToHex(swap32(entry.time - this.refTime - EPOCH_OFFSET), 8), | ||
numToHex(swap32(this.refTime), 8), | ||
this.accessoryType116, | ||
numToHex(swap16(this.usedMemory), 4), | ||
numToHex(swap16(this.memorySize), 4), | ||
numToHex(swap32(this.firstEntry+1), 8)); | ||
} | ||
if (this.service === undefined) { | ||
this.getCharacteristic(S2R1Characteristic).setValue(hexToBase64(val)); | ||
} | ||
else { | ||
this.service.getCharacteristic(S2R1Characteristic).setValue(hexToBase64(val)); | ||
setTimeout(function(){ // retry in 100ms | ||
this.save(); | ||
}.bind(this),100); | ||
} | ||
this.log.debug("First entry %s: %s", this.accessoryName, this.firstEntry.toString(16)); | ||
this.log.debug("Last entry %s: %s", this.accessoryName, this.lastEntry.toString(16)); | ||
this.log.debug("Used memory %s: %s", this.accessoryName, this.usedMemory.toString(16)); | ||
this.log.debug("116 %s: %s", this.accessoryName, val); | ||
} | ||
load(cb) { | ||
this.log.debug("Loading..."); | ||
homebridge.globalFakeGatoStorage.read({ | ||
service: this, | ||
callback: function(err,data){ | ||
if(!err) { | ||
if(data) { | ||
try { | ||
this.log.debug("read data from",this.accessoryName,":",data); | ||
let jsonFile = typeof(data) === "object" ? data : JSON.parse(data); | ||
this.firstEntry = jsonFile.firstEntry; | ||
this.lastEntry = jsonFile.lastEntry; | ||
this.usedMemory = jsonFile.usedMemory; | ||
this.refTime = jsonFile.refTime; | ||
this.initialTime= jsonFile.initialTime; | ||
this.history = jsonFile.history; | ||
} catch (e) { | ||
this.log.debug("**ERROR fetching persisting data restart from zero - invalid JSON**",e); | ||
cb(e,false); | ||
} | ||
cb(null,true); | ||
} | ||
} else { | ||
// file don't exists | ||
cb(null,false); | ||
} | ||
}.bind(this) | ||
}); | ||
} | ||
cleanPersist() { | ||
this.log.debug("Cleaning..."); | ||
homebridge.globalFakeGatoStorage.remove({ | ||
service: this | ||
}); | ||
} | ||
getCurrentS2R2(callback) { | ||
@@ -433,3 +665,3 @@ var entry2address = function(val) { | ||
if ((this.history[this.memoryAddress].setRefTime == 1) || (this.setTime == true)) { | ||
var val = Format( | ||
@@ -444,3 +676,2 @@ '15%s 0100 0000 81%s0000 0000 00 0000', | ||
this.currentEntry = this.currentEntry + 1; | ||
} | ||
@@ -514,3 +745,3 @@ else { | ||
} | ||
}; | ||
} | ||
@@ -517,0 +748,0 @@ |
@@ -5,2 +5,3 @@ /*jshint esversion: 6,node: true,-W041: false */ | ||
const DEBUG = true; | ||
var debug = require('debug')('FakeGatoTimer'); | ||
@@ -17,5 +18,4 @@ class FakeGatoTimer { | ||
this.log = params.log || {}; | ||
if (!params.log || !params.log.debug) { | ||
if(DEBUG) this.log.debug = console.log; | ||
else this.log.debug = function(){}; | ||
if (!this.log.debug) { | ||
this.log.debug = DEBUG ? console.log : function() {}; | ||
} | ||
@@ -31,3 +31,4 @@ } | ||
'backLog': [], | ||
'previousBackLog': [] | ||
'previousBackLog': [], | ||
'previousAvrg': {} | ||
}; | ||
@@ -61,3 +62,3 @@ | ||
start() { | ||
this.log.debug("**Start Global Fakegato-Timer - ",this.minutes,"min**"); | ||
this.log.debug("**Start Global Fakegato-Timer - "+this.minutes+"min**"); | ||
if (this.running) | ||
@@ -83,4 +84,10 @@ this.stop(); | ||
let service = this.subscribedServices[s]; | ||
if (typeof(service.callback) == 'function' && service.backLog.length) | ||
service.callback(service.backLog, this, false); | ||
if (typeof(service.callback) == 'function') { | ||
service.previousAvrg=service.callback({ | ||
'backLog':service.backLog, | ||
'previousAvrg':service.previousAvrg, | ||
'timer':this, | ||
'immediate':false | ||
}); | ||
} | ||
} | ||
@@ -94,3 +101,7 @@ } | ||
if (typeof(service.callback) == 'function' && service.backLog.length) | ||
service.callback(service.backLog, this, true); | ||
service.callback({ | ||
'backLog':service.backLog, | ||
'timer':this, | ||
'immediate':true | ||
}); | ||
} | ||
@@ -121,3 +132,3 @@ addData(params) { | ||
source.previousBackLog = source.backLog; | ||
if(source.backLog.length) source.previousBackLog = source.backLog; | ||
source.backLog = []; | ||
@@ -124,0 +135,0 @@ } |
{ | ||
"name": "fakegato-history", | ||
"version": "0.3.8", | ||
"version": "0.4.0", | ||
"description": "Module emulating Elgato Eve history for homebridge plugins", | ||
@@ -15,3 +15,6 @@ "main": "fakegato-history.js", | ||
"dependencies": { | ||
"moment": "*" | ||
"moment": "*", | ||
"google-auth-library": "^0.10.0", | ||
"googleapis": "^18.0.0", | ||
"debug": "^2.2.0" | ||
}, | ||
@@ -18,0 +21,0 @@ "author": "simont77", |
# fakegato-history | ||
Module to emulate Elgato Eve history service in Homebridge accessories, so that it will show in Eve.app (Home.app does not support it). Still work in progress. Use at your own risk, no guarantee is provided. | ||
More details on communication protocol and custom Characteristics here: https://gist.github.com/simont77/3f4d4330fa55b83f8ca96388d9004e7d | ||
More details on communication protocol and custom Characteristics in the Wiki. | ||
Your plugin should expose the corresponding custom Elgato services and characteristics in order for the history to be seen in Eve.app. For a weather example see https://github.com/simont77/homebridge-weather-station-extended, for an energy example see https://github.com/simont77/homebridge-myhome/blob/master/index.js (MHPowerMeter class). For other types see the gist above. | ||
Note that if your Eve.app is controlling more than one accessory for each type, the serial number should be unique, otherwise Eve.app will merge the histories. Including hostname is recommended as well, for running multiple copies of the same plugin on different machines (i.e. production and development), i.e.: | ||
Your plugin should expose the corresponding custom Elgato services and characteristics in order for the history to be seen in Eve.app. For a weather example see https://github.com/simont77/homebridge-weather-station-extended, for an energy example see https://github.com/simont77/homebridge-myhome/blob/master/index.js (MHPowerMeter class). For other types see the Wiki. | ||
Avoid the use of "/" in characteristics of the Information Service (e.g. serial number, manifacturer, etc.), since this may cause data to not appear in the history. Note that if your Eve.app is controlling more than one accessory for each type, the serial number should be unique, otherwise Eve.app will merge the histories. Adding hostname is recommended as well, for running multiple copies of the same plugin on different machines (i.e. production and development), i.e.: | ||
@@ -30,3 +30,7 @@ .setCharacteristic(Characteristic.SerialNumber, hostname + "-" + this.deviceID) | ||
Eve.app requires at least an entry every 10 minutes to avoid holes in the history. Depending on the accessory type, fakegato-history may add extra entries every 10 minutes or may average the entries from the plugin and send data every 10 minutes. This is done using a single global timer shared among all accessories using fakegato. | ||
Eve.app requires at least an entry every 10 minutes to avoid holes in the history. Depending on the accessory type, fakegato-history may add extra entries every 10 minutes or may average the entries from the plugin and send data every 10 minutes. This is done using a single global timer shared among all accessories using fakegato. You may opt for managing yourself the Timer and disabling the embedded one by using that constructor: | ||
``` | ||
this.loggingService = new FakeGatoHistoryService(accessoryType, Accessory, {size:length,disableTimer:true}); | ||
``` | ||
then you'll have to addEntry yourself data every 10min. | ||
@@ -41,7 +45,7 @@ Depending on your accessory type: | ||
* Add entries to history of accessory emulating **Eve Energy** (Outlet service) using something like this every 10 minutes: | ||
* Add entries to history of accessory emulating **Eve Energy** (Outlet service) using something like this: | ||
this.loggingService.addEntry({time: moment().unix(), power: this.power}); | ||
Power should be the average power in W over 10 minutes period. To have good accuracy, it is strongly advised not to use a single instantaneous measurement, but to average many few seconds measurements over 10 minutes. Fakegato does not use the internal timer for Energy, entries are added to the history as received from the plugin (this is done because the plugin may already have its own average for TotalComsumption calculation) | ||
Power is in Watt. Entries are internally averaged and sent every 10 minutes using the global fakegato timer. To have good accuracy, your entries should be in any case periodic, in order to avoid error with the average. | ||
@@ -72,6 +76,41 @@ * Add entries to history of accessory emulating **Eve Room** (TempSensor, HumiditySensor and AirQuality Services) using something like this: | ||
For Energy and Door accessories it is also worth to add the custom characteristic E863F112 for resetting, respectively, the Total Consumption accumulated value or the Aperture Counter (not the history). See the gist above. The value of this characteristic is changed whenever the reset button is tapped on Eve, so it can be used to reset the locally stored value. The value seems to be the number of seconds from 1.1.2001. I left this characteristics out of fakegato-history because it is not part of the common history service. | ||
For Energy and Door accessories it is also worth to add the custom characteristic E863F112 for resetting, respectively, the Total Consumption accumulated value or the Aperture Counter (not the history). See Wiki. The value of this characteristic is changed whenever the reset button is tapped on Eve, so it can be used to reset the locally stored value. The value seems to be the number of seconds from 1.1.2001. I left this characteristics out of fakegato-history because it is not part of the common history service. | ||
For Door and Motion you may want to add characteristic E863F11A for setting the time of last activation. Value is the number of second from reset of fakegato-history. You can get this time using the function getInitialTime() | ||
If your "weather" or "room" plugin don't send addEntry for a short time (supposedly less than 1h - need feedback), the graph will draw a straight line from the last data received to the new data received. Instead, if your plugin don't send addEntry for "weather" and "room" for a long time (supposedly more than few hours - need feedback), the graph will show "no data for the period". Take this in consideration if your sensor does not send entries if the difference from the previuos one is small, you will end up with holes in the history. This is not currently addresses by fakegato, you should add extra entries if needed. Note that if you do not send a new entry at least every 10 minutes, the average will be 0, and you will a zero entry. This will be fixed soon. | ||
### History Persistance | ||
It is possible to persist data to disk or to Google Drive to avoid loosing part of the history not yet downloaded by Eve on restart or system crash. Data is saved every 10min for "weather" and "room", on every event and every 10 minutes for "door" and "motion", on every event for other types. | ||
#### File System | ||
In order to enable persistance on local disk, when instantiating the FakeGatoHistoryService, the third argument become an object with these attributes: | ||
``` | ||
this.loggingService = new FakeGatoHistoryService(accessoryType, Accessory, { | ||
size:length, // optional - if you still need to specify the length | ||
storage:'fs', | ||
path:'/place/to/store/my/persistence/' // if empty it will be used the -U homebridge option if present or .homebridge | ||
}); | ||
``` | ||
Data will be saved in json files, one for each persisted accessory, with filename in the form *hostname_accessoryDisplayName_persist.json*. In order to reset the persisted data, simply delete these files. | ||
#### Google Drive | ||
In order to enable persistance on Google Drive, when instantiating the FakeGatoHistoryService, the third argument become an object with these attributes : | ||
``` | ||
this.loggingService = new FakeGatoHistoryService(accessoryType, Accessory, { | ||
size:length, // optional - if you still need to specify the length | ||
storage:'googleDrive', | ||
folder:'fakegatoFolder', // folder on Google drive to persist data, 'fakegato' if empty | ||
keyPath:'/place/to/store/my/keys/' // where to find client_secret.json, if empty it will be used the -U homebridge option if present or .homebridge | ||
}); | ||
``` | ||
For the setup of Google Drive, please follow the Google Drive Quickstart for Node.js instructions from https://developers.google.com/drive/v3/web/quickstart/nodejs, except for these changes: | ||
* In Step 1-h the working directory should be the .homebridge directory | ||
* Skip Step 2 and 3 | ||
* In step 4, use the quickstartGoogleDrive.js included with this module. You need to run the command from fakegato-hisory directory. Then just follow steps a to c. | ||
##### Additional notes for Google Drive | ||
* Pay attention so that your plugin does not issue multiple addEntry calls for the same accessory at the same time (this may results in unproper behaeviour of Google Drive to the its asynchronous nature) | ||
### TODO | ||
@@ -82,10 +121,10 @@ | ||
- [x] Add other accessory types. Help from people with access to real Eve accessory is needed. Dump of custom Characteristics during data transfer is required. | ||
- [ ] Make history persistent | ||
- [x] Make history persistent | ||
- [x] Adjustable history length | ||
- [ ] Addition and management of other history related characteristics | ||
- [ ] Periodic sending of reference time stamp (seems not really needed if the time of your homebridge machine is correct) | ||
### Known bugs | ||
- ~~Currenly not fully compatible with dynamic Platforms using Homebridge API v2 format.~~ | ||
- Currently valve position history in thermo is not working | ||
- In "weather" and "room" if you do not send at least an entry every 10 minutes you will get zeros in the history. | ||
@@ -92,0 +131,0 @@ ### How to contribute |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
59745
9
1361
137
4
5
+ Addeddebug@^2.2.0
+ Addedgoogle-auth-library@^0.10.0
+ Addedgoogleapis@^18.0.0
+ Addedajv@6.12.6(transitive)
+ Addedasn1@0.2.6(transitive)
+ Addedassert-plus@1.0.0(transitive)
+ Addedasync@2.1.5(transitive)
+ Addedasynckit@0.4.0(transitive)
+ Addedaws-sign2@0.7.0(transitive)
+ Addedaws4@1.13.2(transitive)
+ Addedbcrypt-pbkdf@1.0.2(transitive)
+ Addedbuffer-equal-constant-time@1.0.1(transitive)
+ Addedcaseless@0.12.0(transitive)
+ Addedcombined-stream@1.0.8(transitive)
+ Addedcore-util-is@1.0.2(transitive)
+ Addeddashdash@1.14.1(transitive)
+ Addeddebug@2.6.9(transitive)
+ Addeddelayed-stream@1.0.0(transitive)
+ Addedecc-jsbn@0.1.2(transitive)
+ Addedecdsa-sig-formatter@1.0.11(transitive)
+ Addedextend@3.0.2(transitive)
+ Addedextsprintf@1.3.0(transitive)
+ Addedfast-deep-equal@3.1.3(transitive)
+ Addedfast-json-stable-stringify@2.1.0(transitive)
+ Addedforever-agent@0.6.1(transitive)
+ Addedform-data@2.3.3(transitive)
+ Addedgetpass@0.1.7(transitive)
+ Addedgoogle-auth-library@0.10.0(transitive)
+ Addedgoogle-p12-pem@0.1.2(transitive)
+ Addedgoogleapis@18.0.0(transitive)
+ Addedgtoken@1.2.3(transitive)
+ Addedhar-schema@2.0.0(transitive)
+ Addedhar-validator@5.1.5(transitive)
+ Addedhttp-signature@1.2.0(transitive)
+ Addedis-typedarray@1.0.0(transitive)
+ Addedisstream@0.1.2(transitive)
+ Addedjsbn@0.1.1(transitive)
+ Addedjson-schema@0.4.0(transitive)
+ Addedjson-schema-traverse@0.4.1(transitive)
+ Addedjson-stringify-safe@5.0.1(transitive)
+ Addedjsprim@1.4.2(transitive)
+ Addedjwa@1.4.1(transitive)
+ Addedjws@3.2.2(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedlodash.noop@3.0.1(transitive)
+ Addedmime@1.6.0(transitive)
+ Addedmime-db@1.52.0(transitive)
+ Addedmime-types@2.1.35(transitive)
+ Addedms@2.0.0(transitive)
+ Addednode-forge@0.7.6(transitive)
+ Addedoauth-sign@0.9.0(transitive)
+ Addedperformance-now@2.1.0(transitive)
+ Addedpsl@1.15.0(transitive)
+ Addedpunycode@2.3.1(transitive)
+ Addedqs@6.5.3(transitive)
+ Addedrequest@2.88.2(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
+ Addedsshpk@1.18.0(transitive)
+ Addedstring-template@1.0.0(transitive)
+ Addedtough-cookie@2.5.0(transitive)
+ Addedtunnel-agent@0.6.0(transitive)
+ Addedtweetnacl@0.14.5(transitive)
+ Addeduri-js@4.4.1(transitive)
+ Addeduuid@3.4.0(transitive)
+ Addedverror@1.10.0(transitive)