homebridge-linky-enedis-meter
Version:
Homebridge plugin pour affichage consommation Linky par Enedis (EVE app seulement).
545 lines (395 loc) • 14.5 kB
JavaScript
var inherits = require('util').inherits;
var Service, Characteristic, pHomebridge;
var request = require('request');
var fs = require('fs');
var path = require('path');
var FakeGatoHistoryService = require('fakegato-history');
const { fail } = require('assert');
const version = require('./package.json').version;
//const { Session } = require('linky');
const fakegatoStorage = require('fakegato-history/fakegato-storage');
const consoleerror = require('console');
module.exports = function (homebridge) {
Service = homebridge.hap.Service;
Characteristic = homebridge.hap.Characteristic;
Accessory = homebridge.platformAccessory;
UUIDGen = homebridge.hap.uuid;
pHomebridge = homebridge;
hap = homebridge.hap;
FakeGatoHistoryService = require('fakegato-history')(homebridge);
homebridge.registerAccessory("homebridge-linky-enedis-meter", "EnergyMeter", EnergyMeter);
}
function EnergyMeter(log, config) {
this.session = null;
this.EntryAdded = false;
this.log = log;
this.usagePointId = config["usagePointId"] || "";
this.accessToken = config["accessToken"] || "";
this.name = config["name"];
this.displayName = config["name"];
try {
this.configFirstDate = new Date(config["firstDateRecord"]);
if (!this.isValidDate(this.configFirstDate)) {
this.log('Error Reading Config Date');
throw "Error";
}
this.configFirstDate.setHours(0, 0, 0, 0);
} catch (error) {
this.log('Error Reading Config Date Keep DateNow');
this.configFirstDate = this.getdatenow();
this.configFirstDate.setHours(0, 0, 0, 0);
}
this.update_interval = config["update_interval"] || 60000;
this.serial = this.usagePointId;
// internal variables
this.waiting_response = false;
this.ResetCall = false;
var EvePowerConsumption = function () {
Characteristic.call(this, 'Consumption', 'E863F10D-079E-48FF-8F27-9C2605A29F52');
this.setProps({
format: Characteristic.Formats.UINT16,
unit: "Watts",
maxValue: 100000,
minValue: 0,
minStep: 1,
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY]
});
this.value = this.getDefaultValue();
};
EvePowerConsumption.UUID = 'E863F10D-079E-48FF-8F27-9C2605A29F52';
inherits(EvePowerConsumption, Characteristic);
var EveTotalConsumption = function () {
Characteristic.call(this, 'Energy', 'E863F10C-079E-48FF-8F27-9C2605A29F52');
this.setProps({
format: Characteristic.Formats.FLOAT,
unit: 'kWh',
maxValue: 1000000000,
minValue: 0,
minStep: 0.001,
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY]
});
this.value = this.getDefaultValue();
};
EveTotalConsumption.UUID = 'E863F10C-079E-48FF-8F27-9C2605A29F52';
inherits(EveTotalConsumption, Characteristic);
var EveVoltage1 = function () {
Characteristic.call(this, 'Volt', 'E863F10A-079E-48FF-8F27-9C2605A29F52');
this.setProps({
format: Characteristic.Formats.FLOAT,
unit: 'Volt',
maxValue: 1000000000,
minValue: 0,
minStep: 0.001,
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY]
});
this.value = this.getDefaultValue();
};
EveVoltage1.UUID = 'E863F10A-079E-48FF-8F27-9C2605A29F52';
inherits(EveVoltage1, Characteristic);
var EveAmpere1 = function () {
Characteristic.call(this, 'Ampere', 'E863F126-079E-48FF-8F27-9C2605A29F52');
this.setProps({
format: Characteristic.Formats.FLOAT,
unit: 'Ampere',
maxValue: 1000000000,
minValue: 0,
minStep: 0.001,
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY]
});
this.value = this.getDefaultValue();
};
EveAmpere1.UUID = 'E863F126-079E-48FF-8F27-9C2605A29F52';
inherits(EveAmpere1, Characteristic);
var EveResetHistory = function () {
Characteristic.call(this, 'Time from totalizer reset', 'E863F112-079E-48FF-8F27-9C2605A29F52');
this.setProps({
format: Characteristic.Formats.UINT32,
unit: 'Time from totalizer reset',
perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY, Characteristic.Perms.WRITE]
});
this.value = this.getDefaultValue();
};
EveResetHistory.UUID = 'E863F112-079E-48FF-8F27-9C2605A29F52';
inherits(EveResetHistory, Characteristic);
var PowerMeterService = function (displayName, subtype) {
Service.call(this, displayName, '00000001-0000-1777-8000-775D67EC4377', subtype);
this.addOptionalCharacteristic(EveTotalConsumption);
this.addOptionalCharacteristic(EveResetHistory);
};
PowerMeterService.UUID = '00000001-0000-1777-8000-775D67EC4377';
inherits(PowerMeterService, Service);
// local vars
this._EveResetHistory = EveResetHistory;
this._EvePowerConsumption = EvePowerConsumption;
this._EveTotalConsumption = EveTotalConsumption;
this._EveVoltage1 = EveVoltage1;
this._EveAmpere1 = EveAmpere1;
// info
this.informationService = new Service.AccessoryInformation();
this.informationService
.setCharacteristic(Characteristic.Manufacturer, "Linky - Enedis")
.setCharacteristic(Characteristic.Model, "Linky")
.setCharacteristic(Characteristic.FirmwareRevision, version)
.setCharacteristic(Characteristic.SerialNumber, this.serial);
// construct service
this.service = new PowerMeterService(this.name);
this.service.addCharacteristic(this._EveTotalConsumption).on('get', this.getTotalConsumption.bind(this));
this.service.addCharacteristic(this._EveResetHistory).on('set', this.setResetEvent.bind(this));;
this.powerConsumption = 0;
this.totalPowerConsumption = 0;
this.voltage1 = 220;
this.ampere1 = 0;
this.historyService = new FakeGatoHistoryService("energy", this, { filename: this.serial + ".json", disableRepeatLastData: true, disableTimer: true, storage: 'fs' });
this.storagePath = path.join(
this.historyService.path,
`${config.accessory}.${PowerMeterService.UUID}.json`
);
this.loadState();
if (this.fstoken != this.accessToken) {
this.fstoken = this.accessToken;
this.saveState();
}
import("linky")
.then(ns => {
this.session = new ns.Session(this.fstoken, this.usagePointId);
})
.catch(error => {
this.log.error(error);
});
};
EnergyMeter.prototype.isValidDate = function (d) {
return d instanceof Date && !isNaN(d);
}
EnergyMeter.prototype.loadState = function () {
try {
let rawFile = '{}';
if (fs.existsSync(this.storagePath)) {
rawFile = fs.readFileSync(this.storagePath, 'utf8');
}
const stored = JSON.parse(rawFile);
this.fstoken = stored.fstoken || "";
this.historyService.currentEntry = stored.uploadEntry || 0;
this.totalPowerConsumption = stored.total || 0;
try {
if (stored.configdate == undefined) {
this.configdate = this.configFirstDate;
}
if (new Date(stored.configdate).valueOf() != this.configFirstDate.valueOf()) {
stored.firstdate = null;
}
} catch (error) {
}
try {
if (stored.firstdate == undefined) {
this.firstdate = new Date(this.configFirstDate);
} else {
this.firstdate = new Date(stored.firstdate);
this.firstdate.setHours(0, -this.firstdate.getTimezoneOffset(), 0, 0);
}
} catch (error) {
this.log('Incorect date');
this.firstdate = this.getdatenow();
this.firstdate.setHours(0, 0, 0, 0);
}
} catch (error) {
fs.writeFileSync(
this.storagePath,
JSON.stringify({
fstoken: this.fstoken,
firstdate: this.firstdate,
uploadEntry: this.historyService.currentEntry,
total: this.totalPowerConsumption,
configdate: this.configFirstDate
})
);
}
};
EnergyMeter.prototype.saveState = function () {
if (this.firstdate != undefined) {
this.firstdate.setHours(0, 0, 0, 0);
}
fs.writeFileSync(
this.storagePath,
JSON.stringify({
fstoken: this.fstoken,
firstdate: this.firstdate,
uploadEntry: this.historyService.currentEntry,
total: this.totalPowerConsumption,
configdate: this.configFirstDate
})
);
}
Date.prototype.addDays = function (days) {
var date = new Date(this.valueOf());
date.setDate(date.getDate() + days);
return date;
};
function toDateTime(secs) {
var t = new Date(1970, 0, 1); // Epoch
t.setSeconds(secs);
return t;
}
function compareNumbers(a, b) {
return a.time - b.time;
}
EnergyMeter.prototype.updateState = function () {
if (this.ResetCall) {
return;
}
//Check if hours is under 05:00 for prevent 404 No Data Error
//or Error: Invalid request: The end date parameter must be earlier than the current date.
if (new Date().getHours() <= 5) {
return;
}
if (this.waiting_response) {
this.log('Please select a higher update_interval value. Querry command may not finish!');
return;
}
this.waiting_response = true;
this.last_value = new Promise((resolve, reject) => {
var datenow = this.getdatenow();
if (this.EntryAdded && this.historyService.history.length == 1) {
this.setResetEvent();
throw new Error("Error writing history must reset");
}
this.service.getCharacteristic(this._EveTotalConsumption).setValue(this.totalPowerConsumption, undefined, undefined);
if (this.firstdate.getDate() == datenow.getDate() && this.firstdate.getMonth() == datenow.getMonth() && this.firstdate.getFullYear() == datenow.getFullYear()) {
this.waiting_response = false;
resolve();
return true;
}
var date = new Date(this.firstdate.valueOf());
date.setDate(date.getDate() + 7);
var dateseek = date;
if (dateseek.getTime() > datenow.getTime()) {
var TotalDays = Math.ceil((datenow - this.firstdate) / (1000 * 3600 * 24));
date = new Date(this.firstdate.valueOf());
date.setDate(date.getDate() + TotalDays);
dateseek = date;
}
if (dateseek.getTime() > datenow.getTime()) {
dateseek = this.getdatenow();
dateseek.setHours(0, 0, 0, 0);
}
if (dateseek.getTime() === this.firstdate.getTime()) {
this.saveState();
this.waiting_response = false;
resolve();
return true;
}
if (this.firstdate.getMonth() < dateseek.getMonth()) {
this.totalPowerConsumption = 0;
}
this.firstdate.setHours(0, -this.firstdate.getTimezoneOffset(), 0, 0);
dateseek.setHours(0, -dateseek.getTimezoneOffset(), 0, 0);
this.log('Query start = ' + this.firstdate.toISOString().split("T")[0] + ' End = ' + dateseek.toISOString().split("T")[0]);
var error;
this.PowerComsuption = 0;
this.session.getLoadCurve(new Date(this.firstdate).toISOString().split("T")[0], new Date(dateseek).toISOString().split("T")[0])
.catch((errorparsed) => {
this.log.error(errorparsed);
error = errorparsed;
})
.then(async (json) => {
if (!error) {
var mindate = Date.parse(json.interval_reading[0].date);
var maxdate = Date.parse(json.interval_reading[json.interval_reading.length - 1].date);
var preval = undefined;
while (mindate <= maxdate) {
var val = json.interval_reading.find(felement => Date.parse(felement.date) == mindate)
if (val != undefined) { preval = val; }
if (preval != undefined) {
var findvalue = this.historyService.history.find(
function (felement) {
if (felement != undefined && felement != null) {
if (felement.time != undefined && felement.time != null) {
return felement.time == Math.round(mindate / 1000);
}
}
return false;
})
if (findvalue == undefined) {
if (val != undefined) {
if (new Date(val.date).getMonth() == datenow.getMonth() && new Date(val.date).getFullYear() == datenow.getFullYear()) {
var conso = parseInt(val.value);
if (Number.isInteger(conso)) {
this.totalPowerConsumption = this.totalPowerConsumption + ((conso / 1000) / 2);
}
}
}
this.historyService.addEntry({ time: Math.round(mindate / 1000), power: (preval.value) });
this.EntryAdded = true;
}
}
mindate = mindate + 600000;
}
this.log('Query ' + this.firstdate.toISOString().split("T")[0] + '/' + dateseek.toISOString().split("T")[0] + ' Finish');
this.firstdate = dateseek;
this.saveState();
resolve();
} else {
reject(error);
}
json = undefined;
return true;
});
}).catch(
(error) => {
this.log.error(error);
}
).then(() => {
this.service.getCharacteristic(this._EveTotalConsumption).setValue(this.totalPowerConsumption, undefined, undefined);
this.waiting_response = false;
return true;
}).finally(() => {
this.last_value = undefined;
})
};
async function sleep(msec) {
return new Promise(resolve => setTimeout(resolve, msec));
}
function getHoursDiff(startDate, endDate) {
const msInHour = 1000 * 60 * 60;
return Math.round(Math.abs(endDate - startDate) / msInHour);
}
EnergyMeter.prototype.setResetEvent = function (callback) {
this.firstdate = this.configFirstDate;
this.EntryAdded = false;
this.historyService.currentEntry = 0;
this.totalPowerConsumption = 0;
this.saveState();
this.log("Reset Detected From EVE App must Restart HomeBridge");
if (this.historyService != null) {
this.historyService.cleanPersist();
fs.unlinkSync(this.storagePath);
}
this.ResetCall = true;
};
EnergyMeter.prototype.getdatenow = function () {
var date = new Date();
date.getHours();
date.setHours(12, 0, 0, 0);
return date;
};
EnergyMeter.prototype.getResetEvent = function (callback) {
callback(null, this.resetvalue);
};
EnergyMeter.prototype.getPowerConsumption = function (callback) {
callback(null, this.powerConsumption);
};
EnergyMeter.prototype.getTotalConsumption = function (callback) {
callback(null, this.totalPowerConsumption);
};
EnergyMeter.prototype.getVoltage1 = function (callback) {
callback(null, this.voltage1);
};
EnergyMeter.prototype.getAmpere1 = function (callback) {
callback(null, this.ampere1);
};
EnergyMeter.prototype.getServices = function () {
this.log("getServices: " + this.name);
if (this.update_interval > 0) {
this.timer = setInterval(this.updateState.bind(this), this.update_interval);
}
return [this.informationService, this.service, this.historyService];
};