Add logging support

This commit is contained in:
Michael Zanetti 2021-11-15 01:20:17 +01:00
parent 885fe9be3a
commit 59d4e1d50d
11 changed files with 1081 additions and 13 deletions

View File

@ -0,0 +1,108 @@
#include "energylogs.h"
#include <QVariant>
EnergyLogs::EnergyLogs(QObject *parent): QObject(parent)
{
}
PowerBalanceLogEntry::PowerBalanceLogEntry()
{
}
PowerBalanceLogEntry::PowerBalanceLogEntry(const QDateTime &timestamp, double consumption, double production, double acquisition, double storage):
m_timestamp(timestamp),
m_consumption(consumption),
m_production(production),
m_acquisition(acquisition),
m_storage(storage)
{
}
QDateTime PowerBalanceLogEntry::timestamp() const
{
return m_timestamp;
}
double PowerBalanceLogEntry::consumption() const
{
return m_consumption;
}
double PowerBalanceLogEntry::production() const
{
return m_production;
}
double PowerBalanceLogEntry::acquisition() const
{
return m_acquisition;
}
double PowerBalanceLogEntry::storage() const
{
return m_storage;
}
QVariant PowerBalanceLogEntries::get(int index) const
{
return QVariant::fromValue(at(index));
}
void PowerBalanceLogEntries::put(const QVariant &variant)
{
append(variant.value<PowerBalanceLogEntry>());
}
ThingPowerLogEntry::ThingPowerLogEntry()
{
}
ThingPowerLogEntry::ThingPowerLogEntry(const QDateTime &timestamp, const ThingId &thingId, double currentPower, double totalConsumption, double totalProduction):
m_timestamp(timestamp),
m_thingId(thingId),
m_currentPower(currentPower),
m_totalConsumption(totalConsumption),
m_totalProduction(totalProduction)
{
}
QDateTime ThingPowerLogEntry::timestamp() const
{
return m_timestamp;
}
ThingId ThingPowerLogEntry::thingId() const
{
return m_thingId;
}
double ThingPowerLogEntry::currentPower() const
{
return m_currentPower;
}
double ThingPowerLogEntry::totalConsumption() const
{
return m_totalConsumption;
}
double ThingPowerLogEntry::totalProduction() const
{
return m_totalProduction;
}
QVariant ThingPowerLogEntries::get(int index) const
{
return QVariant::fromValue(at(index));
}
void ThingPowerLogEntries::put(const QVariant &variant)
{
append(variant.value<ThingPowerLogEntry>());
}

View File

@ -0,0 +1,125 @@
#ifndef ENERGYLOGS_H
#define ENERGYLOGS_H
#include <QObject>
#include <QDateTime>
#include <QVariant>
#include <typeutils.h>
class PowerBalanceLogEntry;
class PowerBalanceLogEntries;
class ThingPowerLogEntry;
class ThingPowerLogEntries;
class EnergyLogs: public QObject
{
Q_OBJECT
public:
EnergyLogs(QObject *parent = nullptr);
virtual ~EnergyLogs() = default;
enum SampleRate {
SampleRate1Min = 1,
SampleRate15Mins = 15,
SampleRate1Hour = 60,
SampleRate3Hours = 180,
SampleRate1Day = 1440,
SampleRate1Week = 10080,
SampleRate1Month = 43200,
SampleRate1Year = 525600
};
Q_ENUM(SampleRate)
/*! Returns logs for the given sample rate for total household consumption, production, acquisition and storage balance.
* From and to may be given to limit results to a time span.
*/
virtual PowerBalanceLogEntries powerBalanceLogs(SampleRate sampleRate, const QDateTime &from = QDateTime(), const QDateTime &to = QDateTime()) const = 0;
/*! Returns logs for the given sample rate for currentPower, totalEnergyConsumed and totalEnergyProduced for the given things.
* From and to may be given to limie results to a time span.
* If thingIds is empty, all things will be returned.
*/
virtual ThingPowerLogEntries thingPowerLogs(SampleRate sampleRate, const QList<ThingId> &thingIds, const QDateTime &from = QDateTime(), const QDateTime &to = QDateTime()) const = 0;
signals:
void powerBalanceEntryAdded(SampleRate sampleRate, const PowerBalanceLogEntry &entry);
void thingPowerEntryAdded(SampleRate sampleRate, const ThingPowerLogEntry &entry);
};
class PowerBalanceLogEntry
{
Q_GADGET
Q_PROPERTY(QDateTime timestamp READ timestamp)
Q_PROPERTY(double consumption READ consumption)
Q_PROPERTY(double production READ production)
Q_PROPERTY(double acquisition READ acquisition)
Q_PROPERTY(double storage READ storage)
public:
PowerBalanceLogEntry();
PowerBalanceLogEntry(const QDateTime &timestamp, double consumption, double production, double acquisition, double storage);
QDateTime timestamp() const;
double consumption() const;
double production() const;
double acquisition() const;
double storage() const;
private:
QDateTime m_timestamp;
double m_consumption = 0;
double m_production = 0;
double m_acquisition = 0;
double m_storage = 0;
};
Q_DECLARE_METATYPE(PowerBalanceLogEntry)
class PowerBalanceLogEntries: public QList<PowerBalanceLogEntry>
{
Q_GADGET
Q_PROPERTY(int count READ count)
public:
PowerBalanceLogEntries() = default;
PowerBalanceLogEntries(const QList<PowerBalanceLogEntry> &other);
Q_INVOKABLE QVariant get(int index) const;
Q_INVOKABLE void put(const QVariant &variant);
};
Q_DECLARE_METATYPE(PowerBalanceLogEntries)
class ThingPowerLogEntry {
Q_GADGET
Q_PROPERTY(QDateTime timstamp READ timestamp)
Q_PROPERTY(QUuid thingId READ thingId)
Q_PROPERTY(double currentPower READ currentPower)
Q_PROPERTY(double totalConsumption READ totalConsumption)
Q_PROPERTY(double totalProduction READ totalProduction)
public:
ThingPowerLogEntry();
ThingPowerLogEntry(const QDateTime &timestamp, const ThingId &thingId, double currentPower, double totalConsumption, double totalProuction);
QDateTime timestamp() const;
ThingId thingId() const;
double currentPower() const;
double totalConsumption() const;
double totalProduction() const;
private:
QDateTime m_timestamp;
ThingId m_thingId;
double m_currentPower = 0;
double m_totalConsumption = 0;
double m_totalProduction = 0;
};
Q_DECLARE_METATYPE(ThingPowerLogEntry)
class ThingPowerLogEntries: public QList<ThingPowerLogEntry>
{
Q_GADGET
Q_PROPERTY(int count READ count)
public:
ThingPowerLogEntries() = default;
ThingPowerLogEntries(const QList<ThingPowerLogEntry> &other): QList<ThingPowerLogEntry>(other) {}
Q_INVOKABLE QVariant get(int index) const;
Q_INVOKABLE void put(const QVariant &variant);
};
Q_DECLARE_METATYPE(ThingPowerLogEntries)
#endif // ENERGYLOGS_H

View File

@ -32,10 +32,13 @@
#ifndef ENERGYMANAGER_H
#define ENERGYMANAGER_H
#include "energylogs.h"
#include <QObject>
#include <integrations/thing.h>
class EnergyManager : public QObject
{
Q_OBJECT
@ -47,6 +50,7 @@ public:
};
Q_ENUM(EnergyError)
explicit EnergyManager(QObject *parent = nullptr);
virtual ~EnergyManager() = default;
@ -56,6 +60,9 @@ public:
virtual double currentPowerConsumption() const = 0;
virtual double currentPowerProduction() const = 0;
virtual double currentPowerAcquisition() const = 0;
virtual double currentPowerStorage() const = 0;
virtual EnergyLogs* logs() const = 0;
signals:
void rootMeterChanged();

View File

@ -9,10 +9,12 @@ PKGCONFIG += nymea
HEADERS += \
energylogs.h \
energymanager.h \
energyplugin.h
SOURCES += \
energylogs.cpp \
energymanager.cpp \
energyplugin.cpp

View File

@ -8,6 +8,10 @@ EnergyJsonHandler::EnergyJsonHandler(EnergyManager *energyManager, QObject *pare
m_energyManager(energyManager)
{
registerEnum<EnergyManager::EnergyError>();
registerEnum<EnergyLogs::SampleRate>();
registerObject<PowerBalanceLogEntry, PowerBalanceLogEntries>();
registerObject<ThingPowerLogEntry, ThingPowerLogEntries>();
QVariantMap params, returns;
QString description;
@ -30,18 +34,51 @@ EnergyJsonHandler::EnergyJsonHandler(EnergyManager *energyManager, QObject *pare
returns.insert("currentPowerAcquisition", enumValueName(Double));
registerMethod("GetPowerBalance", description, params, returns);
params.clear(); returns.clear();
description = "Get logs for the power balance. If from is not give, the log will start at the beginning of "
"recording. If to is not given, the logs will and at the last sample for this sample rate before now.";
params.insert("sampleRate", enumRef<EnergyLogs::SampleRate>());
params.insert("o:from", enumValueName(Uint));
params.insert("o:to", enumValueName(Uint));
returns.insert("powerBalanceLogEntries", objectRef<PowerBalanceLogEntries>());
registerMethod("GetPowerBalanceLogs", description, params, returns);
params.clear(); returns.clear();
description = "Get logs for one or more things power values. If thingIds is not given, logs for all energy related "
"things will be returned. If from is not given, the log will start at the beginning of recording. If "
"to is not given, the logs will and at the last sample for this sample rate before now.";
params.insert("sampleRate", enumRef<EnergyLogs::SampleRate>());
params.insert("o:thingIds", QVariantList() << enumValueName(Uuid));
params.insert("o:from", enumValueName(Uint));
params.insert("o:to", enumValueName(Uint));
returns.insert("thingPowerLogEntries", objectRef<ThingPowerLogEntries>());
registerMethod("GetThingPowerLogs", description, params, returns);
params.clear();
description = "Emitted whenever the root meter id changes. If the root meter has been unset, the params will be empty.";
params.insert("o:rootMeterThingId", enumValueName(Uuid));
registerNotification("RootMeterChanged", description, params);
params.clear();
description = "Emitted whenever the energy balance changes. That is, when the current consumption, production or acquisition changes. Typically they will all change at the same time.";
description = "Emitted whenever the energy balance changes. That is, when the current consumption, production or "
"acquisition changes. Typically they will all change at the same time.";
params.insert("currentPowerConsumption", enumValueName(Double));
params.insert("currentPowerProduction", enumValueName(Double));
params.insert("currentPowerAcquisition", enumValueName(Double));
registerNotification("PowerBalanceChanged", description, params);
params.clear();
description = "Emitted whenever a entry is added to the power balance log.";
params.insert("sampleRate", enumRef<EnergyLogs::SampleRate>());
params.insert("powerBalanceLogEntry", objectRef<PowerBalanceLogEntry>());
registerNotification("PowerBalanceLogEntryAdded", description, params);
params.clear();
description = "Emitted whenever a entry is added to the thing power log.";
params.insert("sampleRate", enumRef<EnergyLogs::SampleRate>());
params.insert("thingPowerLogEntry", objectRef<ThingPowerLogEntry>());
registerNotification("ThingPowerLogEntryAdded", description, params);
connect(m_energyManager, &EnergyManager::rootMeterChanged, this, [=](){
QVariantMap params;
if (m_energyManager->rootMeter()) {
@ -57,6 +94,20 @@ EnergyJsonHandler::EnergyJsonHandler(EnergyManager *energyManager, QObject *pare
params.insert("currentPowerAcquisition", m_energyManager->currentPowerAcquisition());
emit PowerBalanceChanged(params);
});
connect(m_energyManager->logs(), &EnergyLogs::powerBalanceEntryAdded, this, [=](EnergyLogs::SampleRate sampleRate, const PowerBalanceLogEntry &entry){
QVariantMap params;
params.insert("sampleRate", enumValueName(sampleRate));
params.insert("powerBalanceLogEntry", pack(entry));
emit PowerBalanceLogEntryAdded(params);
});
connect(m_energyManager->logs(), &EnergyLogs::thingPowerEntryAdded, this, [=](EnergyLogs::SampleRate sampleRate, const ThingPowerLogEntry &entry){
QVariantMap params;
params.insert("sampleRate", enumValueName(sampleRate));
params.insert("thingPowerLogEntry", pack(entry));
emit ThingPowerLogEntryAdded(params);
});
}
QString EnergyJsonHandler::name() const
@ -96,3 +147,30 @@ JsonReply *EnergyJsonHandler::GetPowerBalance(const QVariantMap &params)
ret.insert("currentPowerAcquisition", m_energyManager->currentPowerAcquisition());
return createReply(ret);
}
JsonReply *EnergyJsonHandler::GetPowerBalanceLogs(const QVariantMap &params)
{
qCDebug(dcEnergyExperience()) << "params" << params;
qCDebug(dcEnergyExperience()) << "from" << params.value("from");
EnergyLogs::SampleRate sampleRate = enumNameToValue<EnergyLogs::SampleRate>(params.value("sampleRate").toString());
QDateTime from = params.contains("from") ? QDateTime::fromMSecsSinceEpoch(params.value("from").toLongLong() * 1000) : QDateTime();
qCDebug(dcEnergyExperience()) << "from2" << from;
QDateTime to = params.contains("to") ? QDateTime::fromMSecsSinceEpoch(params.value("to").toLongLong() * 1000) : QDateTime();
QVariantMap returns;
returns.insert("powerBalanceLogEntries", pack(m_energyManager->logs()->powerBalanceLogs(sampleRate, from, to)));
return createReply(returns);
}
JsonReply *EnergyJsonHandler::GetThingPowerLogs(const QVariantMap &params)
{
EnergyLogs::SampleRate sampleRate = enumNameToValue<EnergyLogs::SampleRate>(params.value("sampleRate").toString());
QList<ThingId> thingIds;
foreach (const QVariant &thingId, params.value("thingIds").toList()) {
thingIds.append(thingId.toUuid());
}
QDateTime from = params.contains("from") ? QDateTime::fromMSecsSinceEpoch(params.value("from").toLongLong() * 1000) : QDateTime();
QDateTime to = params.contains("to") ? QDateTime::fromMSecsSinceEpoch(params.value("to").toLongLong() * 1000) : QDateTime();
QVariantMap returns;
returns.insert("thingPowerLogEntries",pack(m_energyManager->logs()->thingPowerLogs(sampleRate, thingIds, from, to)));
return createReply(returns);
}

View File

@ -17,15 +17,17 @@ public:
Q_INVOKABLE JsonReply* GetRootMeter(const QVariantMap &params);
Q_INVOKABLE JsonReply* SetRootMeter(const QVariantMap &params);
Q_INVOKABLE JsonReply* GetPowerBalance(const QVariantMap &params);
Q_INVOKABLE JsonReply* GetPowerBalanceLogs(const QVariantMap &params);
Q_INVOKABLE JsonReply* GetThingPowerLogs(const QVariantMap &params);
signals:
void RootMeterChanged(const QVariantMap &params);
void PowerBalanceChanged(const QVariantMap &params);
void PowerBalanceLogEntryAdded(const QVariantMap &params);
void ThingPowerLogEntryAdded(const QVariantMap &params);
private:
EnergyManager *m_energyManager = nullptr;
};
#endif // ENERGYJSONHANDLER_H

637
plugin/energylogger.cpp Normal file
View File

@ -0,0 +1,637 @@
#include "energylogger.h"
#include <nymeasettings.h>
#include <QStandardPaths>
#include <QDir>
#include <QSqlQuery>
#include <QSqlError>
#include <QSettings>
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(dcEnergyExperience)
EnergyLogger::EnergyLogger(QObject *parent) : EnergyLogs(parent)
{
if (!initDB()) {
qCCritical(dcEnergyExperience()) << "Unable to open energy log. Energy logs will not be available.";
return;
}
// Logging configuration
// Note: SampleRate1Min is always sampled as it is the base series for others
// Make sure your base series always has enough samples to build a full sample
// of all series building on it.
// Disk space considerations;
// Each entry takes approx 30 bytes for powerBalance + 50 bytes for thingCurrentPower per thing of disk space
// SQLite adds metadata and overhead of about 5%
// The resulting database size can be estimated with (count being the sum of all numbers below):
// (count * 30 bytes) + (count * things * 50 bytes) + 5%
// 10000 entries, with 5 energy things => ~3MB
m_maxMinuteSamples = 15;
addConfig(SampleRate15Mins, SampleRate1Min, 6720); // 10 weeks
addConfig(SampleRate1Hour, SampleRate15Mins, 1680); // 10 weeks
addConfig(SampleRate3Hours, SampleRate15Mins, 560); // 10 weeks
addConfig(SampleRate1Day, SampleRate1Hour, 1095); // 3 years
addConfig(SampleRate1Week, SampleRate1Day, 168); // 3 years
addConfig(SampleRate1Month, SampleRate1Day, 240); // 20 years
addConfig(SampleRate1Year, SampleRate1Month, 20); // 20 years
// Load thingIds from logs so we have the complete list available for sampling, even if a thing might not produce any logs for a while.
QSqlQuery query(m_db);
query.prepare("SELECT DISTINCT thingId FROM thingPower;");
query.exec();
if (query.lastError().isValid()) {
qCWarning(dcEnergyExperience()) << "Failed to load existing things from logs:" << query.lastError();
} else {
while (query.next()) {
m_thingsPowerLiveLogs[query.value("thingId").toUuid()] = ThingPowerLogEntries();
}
}
// Start the scheduling
scheduleNextSample(SampleRate1Min);
foreach (SampleRate sampleRate, m_configs.keys()) {
scheduleNextSample(sampleRate);
}
// Now all the data is initialized. We can start with sampling.
// First check if we missed any samplings (e.g. because the system was offline at the time when it should have created a sample)
foreach(SampleRate sampleRate, m_configs.keys()) {
rectifySamples(sampleRate, m_configs.value(sampleRate).baseSampleRate);
}
// And start the sampler timer
connect(&m_sampleTimer, &QTimer::timeout, this, &EnergyLogger::sample);
m_sampleTimer.start(1000);
}
void EnergyLogger::logPowerBalance(double consumption, double production, double acquisition, double storage)
{
PowerBalanceLogEntry entry(QDateTime::currentDateTime(), consumption, production, acquisition, storage);
// Add everything to livelog, keep that for one day, in memory only
m_balanceLiveLog.prepend(entry);
while (m_balanceLiveLog.count() > 1 && m_balanceLiveLog.last().timestamp().addDays(1) < QDateTime::currentDateTime()) {
qCDebug(dcEnergyExperience) << "Discarding livelog entry from" << m_balanceLiveLog.last().timestamp().toString();
m_balanceLiveLog.removeLast();
}
}
void EnergyLogger::logThingPower(const ThingId &thingId, double currentPower, double totalConsumption, double totalProduction)
{
qCDebug(dcEnergyExperience()) << "Logging thing power:" << currentPower << totalConsumption << totalProduction;
ThingPowerLogEntry entry(QDateTime::currentDateTime(), thingId, currentPower, totalConsumption, totalProduction);
m_thingsPowerLiveLogs[thingId].prepend(entry);
while (m_thingsPowerLiveLogs[thingId].count() > 1 && m_thingsPowerLiveLogs[thingId].last().timestamp().addDays(1) < QDateTime::currentDateTime()) {
qCDebug(dcEnergyExperience()) << "Discarding thing power livelog entry for thing" << thingId << "from" << m_thingsPowerLiveLogs[thingId].last().timestamp().toString();
m_thingsPowerLiveLogs[thingId].removeLast();
}
}
PowerBalanceLogEntries EnergyLogger::powerBalanceLogs(SampleRate sampleRate, const QDateTime &from, const QDateTime &to) const
{
PowerBalanceLogEntries result;
QSqlQuery query(m_db);
QString queryString = "SELECT * FROM powerBalance WHERE sampleRate = ?";
QVariantList bindValues;
bindValues << sampleRate;
qCDebug(dcEnergyExperience()) << "Fetching logs. Timestamp:" << from << from.isNull();
if (!from.isNull()) {
queryString += " AND timestamp >= ?";
bindValues << from.toMSecsSinceEpoch();
}
if (!to.isNull()) {
queryString += " AND timestamp <= ?";
bindValues << to.toMSecsSinceEpoch();
}
query.prepare(queryString);
foreach (const QVariant &bindValue, bindValues) {
query.addBindValue(bindValue);
}
qCDebug(dcEnergyExperience()) << "Executing" << queryString << query.executedQuery() << bindValues;
query.exec();
if (query.lastError().isValid()) {
qCWarning(dcEnergyExperience()) << "Error fetching power balance logs:" << query.lastError() << query.executedQuery();
return result;
}
while (query.next()) {
qCDebug(dcEnergyExperience()) << "Adding result";
result.append(PowerBalanceLogEntry(QDateTime::fromMSecsSinceEpoch(query.value("timestamp").toLongLong()), query.value("consumption").toDouble(), query.value("production").toDouble(), query.value("acquisition").toDouble(), query.value("storage").toDouble()));
}
return result;
}
ThingPowerLogEntries EnergyLogger::thingPowerLogs(SampleRate sampleRate, const QList<ThingId> &thingIds, const QDateTime &from, const QDateTime &to) const
{
ThingPowerLogEntries result;
QSqlQuery query(m_db);
QString queryString = "SELECT * FROM thingPower WHERE sampleRate = ?";
QVariantList bindValues;
bindValues << sampleRate;
QStringList thingsQuery;
foreach (const ThingId &thingId, thingIds) {
thingsQuery.append("thingId = ?");
bindValues << thingId;
}
if (!thingsQuery.isEmpty()) {
queryString += "AND (" + thingsQuery.join(" OR ") + " )";
}
if (!from.isNull()) {
queryString += " AND timestamp >= ?";
bindValues << from.toMSecsSinceEpoch();
}
if (!to.isNull()) {
queryString += " AND timestamp <= ?";
bindValues << to.toMSecsSinceEpoch();
}
query.prepare(queryString);
foreach (const QVariant &bindValue, bindValues) {
query.addBindValue(bindValue);
}
query.exec();
if (query.lastError().isValid()) {
qCWarning(dcEnergyExperience()) << "Error fetching power balance logs:" << query.lastError() << query.executedQuery();
return result;
}
while (query.next()) {
result.append(ThingPowerLogEntry(
QDateTime::fromMSecsSinceEpoch(query.value("timestamp").toLongLong()),
query.value("thingId").toUuid(),
query.value("currentPower").toDouble(),
query.value("totalConsumption").toDouble(),
query.value("totalProduction").toDouble()));
}
return result;
}
void EnergyLogger::sample()
{
QDateTime now = QDateTime::currentDateTime();
if (now >= m_nextSamples.value(SampleRate1Min)) {
QDateTime sampleEnd = m_nextSamples.value(SampleRate1Min);
QDateTime sampleStart = sampleEnd.addMSecs(-60 * 1000);
qCDebug(dcEnergyExperience()) << "Sampling 1 min" << sampleEnd.toString();
double medianConsumption = 0;
double medianProduction = 0;
double medianAcquisition = 0;
double medianStorage = 0;
for (int i = 0; i < m_balanceLiveLog.count(); i++) {
const PowerBalanceLogEntry &entry = m_balanceLiveLog.at(i);
QDateTime frameStart = (entry.timestamp() < sampleStart) ? sampleStart : entry.timestamp();
QDateTime frameEnd = i == 0 ? sampleEnd : m_balanceLiveLog.at(i-1).timestamp();
int frameDuration = frameStart.msecsTo(frameEnd);
medianConsumption += entry.consumption() * frameDuration;
medianProduction += entry.production() * frameDuration;
medianAcquisition += entry.acquisition() * frameDuration;
medianStorage += entry.storage() * frameDuration;
// qCDebug(dcEnergyExperience()) << "Frame" << i << "duration:" << frameDuration << "value:" << entry.consumption << "start" << frameStart.toString() << "end" << frameEnd.toString();
if (entry.timestamp() < sampleStart) {
break;
}
}
medianConsumption /= sampleStart.msecsTo(sampleEnd);
medianProduction /= sampleStart.msecsTo(sampleEnd);
medianAcquisition /= sampleStart.msecsTo(sampleEnd);
medianStorage /= sampleStart.msecsTo(sampleEnd);
qCDebug(dcEnergyExperience()) << "Power balance for sample:" << medianConsumption << medianProduction << medianAcquisition << medianStorage << "duration:" << sampleStart.msecsTo(sampleEnd);
insertPowerBalance(sampleEnd, SampleRate1Min, medianConsumption, medianProduction, medianAcquisition, medianStorage);
foreach (const ThingId &thingId, m_thingsPowerLiveLogs.keys()) {
medianConsumption = 0;
ThingPowerLogEntries entries = m_thingsPowerLiveLogs.value(thingId);
for (int i = 0; i < entries.count(); i++) {
const ThingPowerLogEntry &entry = entries.at(i);
QDateTime frameStart = (entry.timestamp() < sampleStart) ? sampleStart : entry.timestamp();
QDateTime frameEnd = i == 0 ? sampleEnd : entries.at(i-1).timestamp();
int frameDuration = frameStart.msecsTo(frameEnd);
medianConsumption += entry.currentPower() * frameDuration;
// qCDebug(dcEnergyExperience()) << "Frame" << i << "duration:" << frameDuration << "value:" << entry.value;
if (entry.timestamp() < sampleStart) {
break;
}
}
medianConsumption /= sampleStart.msecsTo(sampleEnd);
double totalConsumption = 0;
double totalProduction = 0;
if (entries.count() > 0) {
totalConsumption = entries.last().totalConsumption();
totalProduction = entries.last().totalProduction();
}
qCDebug(dcEnergyExperience()) << "Thing power of sample:" << medianConsumption << totalConsumption << totalProduction << "total duration:" << sampleStart.msecsTo(sampleEnd);
insertThingPower(sampleEnd, SampleRate1Min, thingId, medianConsumption, totalConsumption, totalProduction);
}
}
// First sample all the configs.
foreach (SampleRate sampleRate, m_configs.keys()) {
if (now >= m_nextSamples.value(sampleRate)) {
QDateTime sampleTime = m_nextSamples.value(sampleRate);
SampleRate baseSampleRate = m_configs.value(sampleRate).baseSampleRate;
samplePowerBalance(sampleRate, baseSampleRate, sampleTime);
sampleThingsPower(sampleRate, baseSampleRate, sampleTime);
}
}
// and then trim them
if (now > m_nextSamples.value(SampleRate1Min)) {
QDateTime sampleTime = m_nextSamples.value(SampleRate1Min);
QDateTime oldestTimestamp = sampleTime.addMSecs(-m_maxMinuteSamples * 60 * 1000);
trimPowerBalance(SampleRate1Min, oldestTimestamp);
foreach (const ThingId &thingId, m_thingsPowerLiveLogs.keys()) {
trimThingPower(thingId, SampleRate1Min, oldestTimestamp);
}
}
foreach (SampleRate sampleRate, m_configs.keys()) {
if (now >= m_nextSamples.value(sampleRate)) {
QDateTime sampleTime = m_nextSamples.value(sampleRate);
QDateTime oldestTimestamp = sampleTime.addMSecs(-m_configs.value(sampleRate).maxSamples * sampleRate * 60 * 1000);
trimPowerBalance(sampleRate, oldestTimestamp);
foreach (const ThingId &thingId, m_thingsPowerLiveLogs.keys()) {
trimThingPower(thingId, sampleRate, oldestTimestamp);
}
}
}
// Lastly we reschedule the next sample for each config
// Note: keep this at the end as the previous stuff uses the schedule to work
if (now > m_nextSamples.value(SampleRate1Min)) {
scheduleNextSample(SampleRate1Min);
}
foreach (SampleRate sampleRate, m_configs.keys()) {
if (now >= m_nextSamples.value(sampleRate)) {
scheduleNextSample(sampleRate);
}
}
}
bool EnergyLogger::initDB()
{
m_db.close();
m_db = QSqlDatabase::addDatabase("QSQLITE", "energylogs");
QDir path = QDir(NymeaSettings::storagePath());
if (!path.exists()) {
path.mkpath(path.path());
}
m_db.setDatabaseName(path.filePath("energylogs.sqlite"));
bool opened = m_db.open();
if (!opened) {
qCWarning(dcEnergyExperience()) << "Cannot open energy log DB at" << m_db.databaseName() << m_db.lastError();
return false;
}
if (!m_db.tables().contains("powerBalance")) {
qCDebug(dcEnergyExperience()) << "No \"powerBalance\" table in database. Creating it.";
m_db.exec("CREATE TABLE powerBalance "
"("
"timestamp BIGINT,"
"sampleRate INT,"
"consumption FLOAT,"
"production FLOAT,"
"acquisition FLOAT,"
"storage FLOAT"
");");
if (m_db.lastError().isValid()) {
qCWarning(dcEnergyExperience()) << "Error creating powerBalance table in energy log database. Driver error:" << m_db.lastError().driverText() << "Database error:" << m_db.lastError().databaseText();
return false;
}
}
if (!m_db.tables().contains("thingPower")) {
qCDebug(dcEnergyExperience()) << "No \"thingPower\" table in database. Creating it.";
m_db.exec("CREATE TABLE thingPower "
"("
"timestamp BIGINT,"
"sampleRate INT,"
"thingId VARCHAR(38),"
"currentPower FLOAT,"
"totalConsumption FLOAT,"
"totalProduction FLOAT"
");");
if (m_db.lastError().isValid()) {
qCWarning(dcEnergyExperience()) << "Error creating thingPower table in energy log database. Driver error:" << m_db.lastError().driverText() << "Database error:" << m_db.lastError().databaseText();
return false;
}
}
qCDebug(dcEnergyExperience()) << "Initialized logging DB successfully.";
return true;
}
void EnergyLogger::addConfig(SampleRate sampleRate, SampleRate baseSampleRate, int maxSamples)
{
SampleConfig config;
config.baseSampleRate = baseSampleRate;
config.maxSamples = maxSamples;
m_configs.insert(sampleRate, config);
}
QDateTime EnergyLogger::getOldestPowerBalanceSampleTimestamp(SampleRate sampleRate)
{
QSqlQuery query(m_db);
query.prepare("SELECT MIN(timestamp) AS oldestTimestamp FROM powerBalance WHERE sampleRate = ?;");
query.addBindValue(sampleRate);
query.exec();
if (query.next() && !query.value("oldestTimestamp").isNull()) {
return QDateTime::fromMSecsSinceEpoch(query.value("oldestTimestamp").toLongLong());
}
return QDateTime();
}
QDateTime EnergyLogger::getNewestPowerBalanceSampleTimestamp(SampleRate sampleRate)
{
QSqlQuery query(m_db);
query.prepare("SELECT MAX(timestamp) AS latestTimestamp FROM powerBalance WHERE sampleRate = ?;");
query.addBindValue(sampleRate);
query.exec();
if (query.next() && !query.value("latestTimestamp").isNull()) {
return QDateTime::fromMSecsSinceEpoch(query.value("latestTimestamp").toLongLong());
}
return QDateTime();
}
QDateTime EnergyLogger::getOldestThingPowerSampleTimestamp(const ThingId &thingId, SampleRate sampleRate)
{
QSqlQuery query(m_db);
query.prepare("SELECT MIN(timestamp) AS oldestTimestamp FROM thingPower WHERE thingId = ? AND sampleRate = ?;");
query.addBindValue(thingId);
query.addBindValue(sampleRate);
query.exec();
if (query.next() && !query.value("oldestTimestamp").isNull()) {
return QDateTime::fromMSecsSinceEpoch(query.value("oldestTimestamp").toLongLong());
}
return QDateTime();
}
QDateTime EnergyLogger::getNewestThingPowerSampleTimestamp(const ThingId &thingId, SampleRate sampleRate)
{
QSqlQuery query(m_db);
query.prepare("SELECT MAX(timestamp) AS newestTimestamp FROM thingPower WHERE thingId = ? AND sampleRate = ?;");
query.addBindValue(thingId);
query.addBindValue(sampleRate);
query.exec();
if (query.next() && !query.value("newestTimestamp").isNull()) {
return QDateTime::fromMSecsSinceEpoch(query.value("newestTimestamp").toLongLong());
}
return QDateTime();
}
void EnergyLogger::scheduleNextSample(SampleRate sampleRate)
{
QDateTime next = nextSampleTimestamp(sampleRate, QDateTime::currentDateTime());
m_nextSamples.insert(sampleRate, next);
qCDebug(dcEnergyExperience()) << "Next sample for" << sampleRate << "scheduled at" << next.toString();
}
void EnergyLogger::rectifySamples(SampleRate sampleRate, SampleRate baseSampleRate)
{
// Normally we'd need to find the newest available sample of a serien and catch up from there.
// However, it could happen a series does not have any samples at all yet. For example if we're logging since january,
// and at new years the system was off, we missed the new years yearly sample and don't have any earlier. For those cases
// we need to start resampling from the oldest timestamp we find in the DB at all (regardless of the sampleRate)
QDateTime oldestBaseSample = getOldestPowerBalanceSampleTimestamp(baseSampleRate);
QDateTime newestSample = getNewestPowerBalanceSampleTimestamp(sampleRate);
qCDebug(dcEnergyExperience()) << "Checking for missing samples for" << sampleRate;
qCDebug(dcEnergyExperience()) << "Newest sample:" << newestSample.toString() << "Oldest base sample:" << oldestBaseSample.toString();
if (newestSample.isNull()) {
qCDebug(dcEnergyExperience()) << "No sample at all so far. Using base as starting point.";
newestSample = oldestBaseSample;
}
qCDebug(dcEnergyExperience()) << "next sample after last in series:" << nextSampleTimestamp(sampleRate, newestSample).toString();
qCDebug(dcEnergyExperience()) << "next scheduled sample:" << m_nextSamples.value(sampleRate).toString();
while (!newestSample.isNull() && nextSampleTimestamp(sampleRate, newestSample) < m_nextSamples[sampleRate]) {
QDateTime nextSample = nextSampleTimestamp(sampleRate, newestSample.addMSecs(1000));
qCDebug(dcEnergyExperience()) << "Rectifying missed sample for" << sampleRate << "from" << nextSample.toString();
samplePowerBalance(sampleRate, baseSampleRate, nextSample);
newestSample = nextSample;
}
foreach (const ThingId &thingId, m_thingsPowerLiveLogs.keys()) {
QDateTime oldestBaseSample = getOldestThingPowerSampleTimestamp(thingId, baseSampleRate);
QDateTime newestSample = getNewestThingPowerSampleTimestamp(thingId, sampleRate);
qCDebug(dcEnergyExperience()) << "T Checking for missing samples for" << sampleRate;
qCDebug(dcEnergyExperience()) << "T Newest sample:" << newestSample.toString() << "Oldest base sample:" << oldestBaseSample.toString();
if (newestSample.isNull()) {
qCDebug(dcEnergyExperience()) << "T No sample at all so far. Using base as starting point.";
newestSample = oldestBaseSample;
}
qCDebug(dcEnergyExperience()) << "T next sample after last in series:" << nextSampleTimestamp(sampleRate, newestSample).toString();
qCDebug(dcEnergyExperience()) << "T next scheduled sample:" << m_nextSamples.value(sampleRate).toString();
while (!newestSample.isNull() && nextSampleTimestamp(sampleRate, newestSample) < m_nextSamples[sampleRate]) {
QDateTime nextSample = nextSampleTimestamp(sampleRate, newestSample.addMSecs(1000));
qCDebug(dcEnergyExperience()) << "T Rectifying missed sample for" << sampleRate << "from" << nextSample.toString();
sampleThingPower(thingId, sampleRate, baseSampleRate, nextSample);
newestSample = nextSample;
}
}
}
QDateTime EnergyLogger::nextSampleTimestamp(SampleRate sampleRate, const QDateTime &dateTime)
{
QTime time = dateTime.time();
QDate date = dateTime.date();
QDateTime next;
switch (sampleRate) {
case SampleRate1Min:
time.setHMS(time.hour(), time.minute(), 0);
next = QDateTime(date, time).addMSecs(60 * 1000);
break;
case SampleRate15Mins:
time.setHMS(time.hour(), time.minute() - (time.minute() % 15), 0);
next = QDateTime(date, time).addMSecs(15 * 60 * 1000);
break;
case SampleRate1Hour:
time.setHMS(time.hour(), 0, 0);
next = QDateTime(date, time).addMSecs(60 * 60 * 1000);
break;
case SampleRate3Hours:
time.setHMS(time.hour() - (time.hour() % 3), 0, 0);
next = QDateTime(date, time).addMSecs(3 * 60 * 60 * 1000);
break;
case SampleRate1Day:
next = QDateTime(date, QTime()).addDays(1);
break;
case SampleRate1Week:
date = date.addDays(-date.dayOfWeek() + 1);
next = QDateTime(date, QTime()).addDays(7);
break;
case SampleRate1Month:
date = date.addDays(-date.day() + 1);
next = QDateTime(date, QTime()).addMonths(1);
break;
case SampleRate1Year:
date.setDate(date.year(), 1, 1);
next = QDateTime(date, QTime()).addYears(1);
break;
}
return next;
}
bool EnergyLogger::samplePowerBalance(SampleRate sampleRate, SampleRate baseSampleRate, const QDateTime &sampleEnd)
{
QDateTime sampleStart = sampleEnd.addMSecs(-sampleRate * 60 * 1000);
qCDebug(dcEnergyExperience()) << "Sampling" << sampleRate << "from" << sampleStart << "to" << sampleEnd;
QSqlQuery query(m_db);
query.prepare("SELECT * FROM powerBalance WHERE sampleRate = ? AND timestamp >= ? AND timestamp < ?;");
query.addBindValue(baseSampleRate);
query.addBindValue(sampleStart.toMSecsSinceEpoch());
query.addBindValue(sampleEnd.toMSecsSinceEpoch());
query.exec();
if (query.lastError().isValid()) {
qCWarning(dcEnergyExperience()) << "Error fetching power balance samples for" << baseSampleRate << "from" << sampleStart.toString() << "to" << sampleEnd.toString();
qCWarning(dcEnergyExperience()) << "SQL error was:" << query.lastError() << "executed query:" << query.executedQuery();
return false;
}
double medianConsumption = 0;
double medianProduction = 0;
double medianAcquisition = 0;
double medianStorage = 0;
while (query.next()) {
qCDebug(dcEnergyExperience()) << "Frame:" << query.value("consumption").toDouble() << query.value("production").toDouble() << query.value("acquisition").toDouble() << QDateTime::fromMSecsSinceEpoch(query.value("timestamp").toLongLong()).toString();
medianConsumption += query.value("consumption").toDouble();
medianProduction += query.value("production").toDouble();
medianAcquisition += query.value("acquisition").toDouble();
medianStorage += query.value("storage").toDouble();
}
qCDebug(dcEnergyExperience()) << "Totals:" << medianConsumption << medianProduction << medianAcquisition << medianStorage << "base samplerate" << baseSampleRate << "samplerate:" << sampleRate;
medianConsumption = medianConsumption * baseSampleRate / sampleRate;
medianProduction = medianProduction * baseSampleRate / sampleRate;
medianAcquisition = medianAcquisition * baseSampleRate / sampleRate;
medianStorage = medianStorage * baseSampleRate / sampleRate;
qCDebug(dcEnergyExperience()) << "Sampled:" << medianConsumption << medianProduction << medianAcquisition << medianStorage;
return insertPowerBalance(sampleEnd, sampleRate, medianConsumption, medianProduction, medianAcquisition, medianStorage);
}
bool EnergyLogger::insertPowerBalance(const QDateTime &timestamp, SampleRate sampleRate, double consumption, double production, double acquisition, double storage)
{
QSqlQuery query = QSqlQuery(m_db);
query.prepare("INSERT INTO powerBalance (timestamp, sampleRate, consumption, production, acquisition, storage) values (?, ?, ?, ?, ?, ?);");
query.addBindValue(timestamp.toMSecsSinceEpoch());
query.addBindValue(sampleRate);
query.addBindValue(consumption);
query.addBindValue(production);
query.addBindValue(acquisition);
query.addBindValue(storage);
query.exec();
if (query.lastError().isValid()) {
qCWarning(dcEnergyExperience()) << "Error logging consumption sample:" << query.lastError();
return false;
}
emit powerBalanceEntryAdded(sampleRate, PowerBalanceLogEntry(timestamp, consumption, production, acquisition, storage));
return true;
}
bool EnergyLogger::sampleThingsPower(SampleRate sampleRate, SampleRate baseSampleRate, const QDateTime &sampleEnd)
{
bool ret = true;
foreach (const ThingId &thingId, m_thingsPowerLiveLogs.keys()) {
ret &= sampleThingPower(thingId, sampleRate, baseSampleRate, sampleEnd);
}
return ret;
}
bool EnergyLogger::sampleThingPower(const ThingId &thingId, SampleRate sampleRate, SampleRate baseSampleRate, const QDateTime &sampleEnd)
{
QDateTime sampleStart = sampleEnd.addMSecs(-sampleRate * 60 * 1000);
qCDebug(dcEnergyExperience()) << "Sampling" << sampleRate << "from" << sampleStart << "to" << sampleEnd;
QSqlQuery query(m_db);
query.prepare("SELECT * FROM thingPower WHERE thingId = ? AND sampleRate = ? AND timestamp >= ? AND timestamp < ?;");
query.addBindValue(thingId);
query.addBindValue(baseSampleRate);
query.addBindValue(sampleStart.toMSecsSinceEpoch());
query.addBindValue(sampleEnd.toMSecsSinceEpoch());
query.exec();
if (query.lastError().isValid()) {
qCWarning(dcEnergyExperience()) << "Error fetching thing power samples for" << baseSampleRate << "from" << sampleStart.toString() << "to" << sampleEnd.toString();
qCWarning(dcEnergyExperience()) << "SQL error was:" << query.lastError() << "executed query:" << query.executedQuery();
return false;
}
double medianCurrentPower = 0;
while (query.next()) {
qCDebug(dcEnergyExperience()) << "Frame:" << query.value("currentPower").toDouble() << QDateTime::fromMSecsSinceEpoch(query.value("timestamp").toLongLong()).toString();
medianCurrentPower += query.value("currentPower").toDouble();
}
qCDebug(dcEnergyExperience()) << "Total:" << medianCurrentPower << "base samplerate" << baseSampleRate << "samplerate:" << sampleRate;
medianCurrentPower = medianCurrentPower * baseSampleRate / sampleRate;
double totalConsumption = query.value("totalConsumption").toDouble();
double totalProduction = query.value("totalProduction").toDouble();
qCDebug(dcEnergyExperience()) << "Sampled:" << medianCurrentPower;
return insertThingPower(sampleEnd, sampleRate, thingId, medianCurrentPower, totalConsumption, totalProduction);
}
bool EnergyLogger::insertThingPower(const QDateTime &timestamp, SampleRate sampleRate, const ThingId &thingId, double currentPower, double totalConsumption, double totalProduction)
{
QSqlQuery query = QSqlQuery(m_db);
query.prepare("INSERT INTO thingPower (timestamp, sampleRate, thingId, currentPower, totalConsumption, totalProduction) values (?, ?, ?, ?, ?, ?);");
query.addBindValue(timestamp.toMSecsSinceEpoch());
query.addBindValue(sampleRate);
query.addBindValue(thingId);
query.addBindValue(currentPower);
query.addBindValue(totalConsumption);
query.addBindValue(totalProduction);
query.exec();
if (query.lastError().isValid()) {
qCWarning(dcEnergyExperience()) << "Error logging thing power sample:" << query.lastError() << query.executedQuery();
return false;
}
emit thingPowerEntryAdded(sampleRate, ThingPowerLogEntry(timestamp, thingId, currentPower, totalConsumption, totalProduction));
return true;
}
void EnergyLogger::trimPowerBalance(SampleRate sampleRate, const QDateTime &beforeTime)
{
QSqlQuery query(m_db);
query.prepare("DELETE FROM powerBalance WHERE sampleRate = ? AND timestamp < ?;");
query.addBindValue(sampleRate);
query.addBindValue(beforeTime.toMSecsSinceEpoch());
query.exec();
if (query.numRowsAffected() > 0) {
qCDebug(dcEnergyExperience()).nospace() << "Trimmed " << query.numRowsAffected() << " from power balance series: " << sampleRate << " (Older than: " << beforeTime.toString() << ")";
}
}
void EnergyLogger::trimThingPower(const ThingId &thingId, SampleRate sampleRate, const QDateTime &beforeTime)
{
QSqlQuery query(m_db);
query.prepare("DELETE FROM thingPower WHERE thingId = ? AND sampleRate = ? AND timestamp < ?;");
query.addBindValue(thingId);
query.addBindValue(sampleRate);
query.addBindValue(beforeTime.toMSecsSinceEpoch());
query.exec();
if (query.numRowsAffected() > 0) {
qCDebug(dcEnergyExperience()).nospace() << "Trimmed " << query.numRowsAffected() << " from thing power series for: " << thingId << sampleRate << " (Older than: " << beforeTime.toString() << ")";
}
}

68
plugin/energylogger.h Normal file
View File

@ -0,0 +1,68 @@
#ifndef ENERGYLOGGER_H
#define ENERGYLOGGER_H
#include "energylogs.h"
#include <typeutils.h>
#include <QObject>
#include <QDateTime>
#include <QSqlDatabase>
#include <QTimer>
#include <QMap>
class EnergyLogger : public EnergyLogs
{
Q_OBJECT
public:
explicit EnergyLogger(QObject *parent = nullptr);
void logPowerBalance(double consumption, double production, double acquisition, double storage);
void logThingPower(const ThingId &thingId, double currentPower, double totalConsumption, double totalProduction);
PowerBalanceLogEntries powerBalanceLogs(SampleRate sampleRate, const QDateTime &from = QDateTime(), const QDateTime &to = QDateTime()) const override;
ThingPowerLogEntries thingPowerLogs(SampleRate sampleRate, const QList<ThingId> &thingIds, const QDateTime &from = QDateTime(), const QDateTime &to = QDateTime()) const override;
private slots:
void sample();
private:
bool initDB();
void addConfig(SampleRate sampleRate, SampleRate baseSampleRate, int maxSamples);
QDateTime getOldestPowerBalanceSampleTimestamp(SampleRate sampleRate);
QDateTime getNewestPowerBalanceSampleTimestamp(SampleRate sampleRate);
QDateTime getOldestThingPowerSampleTimestamp(const ThingId &thingId, SampleRate sampleRate);
QDateTime getNewestThingPowerSampleTimestamp(const ThingId &thingId, SampleRate sampleRate);
QDateTime nextSampleTimestamp(SampleRate sampleRate, const QDateTime &dateTime);
void scheduleNextSample(SampleRate sampleRate);
void rectifySamples(SampleRate sampleRate, EnergyLogger::SampleRate baseSampleRate);
bool samplePowerBalance(SampleRate sampleRate, SampleRate baseSampleRate, const QDateTime &sampleEnd);
bool insertPowerBalance(const QDateTime &timestamp, SampleRate sampleRate, double consumption, double production, double acquisition, double storage);
bool sampleThingsPower(SampleRate sampleRate, SampleRate baseSampleRate, const QDateTime &sampleEnd);
bool sampleThingPower(const ThingId &thingId, SampleRate sampleRate, SampleRate baseSampleRate, const QDateTime &sampleEnd);
bool insertThingPower(const QDateTime &timestamp, SampleRate sampleRate, const ThingId &thingId, double currentPower, double totalConsumption, double totalProduction);
void trimPowerBalance(SampleRate sampleRate, const QDateTime &beforeTime);
void trimThingPower(const ThingId &thingId, SampleRate sampleRate, const QDateTime &beforeTime);
private:
struct SampleConfig {
SampleRate baseSampleRate;
int maxSamples = 0;
};
PowerBalanceLogEntries m_balanceLiveLog;
QHash<ThingId, ThingPowerLogEntries> m_thingsPowerLiveLogs;
QTimer m_sampleTimer;
QHash<SampleRate, QDateTime> m_nextSamples;
QSqlDatabase m_db;
int m_maxMinuteSamples = 0;
QMap<SampleRate, SampleConfig> m_configs;
};
#endif // ENERGYLOGGER_H

View File

@ -1,5 +1,7 @@
#include "energymanagerimpl.h"
#include "nymeasettings.h"
#include "energylogger.h"
#include <nymeasettings.h>
#include <qmath.h>
@ -7,7 +9,8 @@ Q_DECLARE_LOGGING_CATEGORY(dcEnergyExperience)
EnergyManagerImpl::EnergyManagerImpl(ThingManager *thingManager, QObject *parent):
EnergyManager(parent),
m_thingManager(thingManager)
m_thingManager(thingManager),
m_logger(new EnergyLogger(this))
{
// Most of the time we get a bunch of signals at the same time (root meter, producers, consumers etc)
// In order to decrease some load on the system, we'll wait for wee bit until we actually update to
@ -67,6 +70,16 @@ double EnergyManagerImpl::currentPowerAcquisition() const
return m_currentPowerAcquisition;
}
double EnergyManagerImpl::currentPowerStorage() const
{
return m_currentPowerStorage;
}
EnergyLogs *EnergyManagerImpl::logs() const
{
return m_logger;
}
void EnergyManagerImpl::watchThing(Thing *thing)
{
// If we don't have a root meter yet, we'll be auto-setting the first energymeter that appears.
@ -76,8 +89,10 @@ void EnergyManagerImpl::watchThing(Thing *thing)
}
qCDebug(dcEnergyExperience()) << "Wathing thing:" << thing->name();
if (thing->thingClass().interfaces().contains("smartmeterproducer")
|| thing->thingClass().interfaces().contains("energymeter")
// React on things that requie us updating the power balance
if (thing->thingClass().interfaces().contains("energymeter")
|| thing->thingClass().interfaces().contains("smartmeterproducer")
|| thing->thingClass().interfaces().contains("energystorage")) {
connect(thing, &Thing::stateValueChanged, this, [=](const StateTypeId &stateTypeId){
if (thing->thingClass().getStateType(stateTypeId).name() == "currentPower") {
@ -85,6 +100,18 @@ void EnergyManagerImpl::watchThing(Thing *thing)
}
});
}
// React on things that need to be logged
if (thing->thingClass().interfaces().contains("energymeter")
|| thing->thingClass().interfaces().contains("smartmeterconsumer")
|| thing->thingClass().interfaces().contains("smartmeterproducer")
|| thing->thingClass().interfaces().contains("energystorage")) {
connect(thing, &Thing::stateValueChanged, this, [=](const StateTypeId &stateTypeId, const QVariant &value){
if (thing->thingClass().getStateType(stateTypeId).name() == "currentPower") {
m_logger->logThingPower(thing->id(), value.toDouble(), thing->state("totalEnergyConsumed").value().toDouble(), thing->state("totalEnergyProduced").value().toDouble());
}
});
}
}
void EnergyManagerImpl::unwatchThing(const ThingId &thingId)
@ -107,21 +134,25 @@ void EnergyManagerImpl::updatePowerBalance()
currentPowerProduction += thing->stateValue("currentPower").toDouble();
}
double currentBatteryBalance = 0;
double currentPowerStorage = 0;
foreach (Thing *thing, m_thingManager->configuredThings().filterByInterface("energystorage")) {
currentBatteryBalance += thing->stateValue("currentPower").toDouble();
currentPowerStorage += thing->stateValue("currentPower").toDouble();
}
double currentPowerConsumption = -currentPowerProduction + currentPowerAcquisition - currentBatteryBalance;
double currentPowerConsumption = -currentPowerProduction + currentPowerAcquisition - currentPowerStorage;
qCDebug(dcEnergyExperience()) << "Consumption:" << currentPowerConsumption << "Production:" << currentPowerProduction << "Acquisition:" << currentPowerAcquisition << "Battery:" << currentBatteryBalance;
qCDebug(dcEnergyExperience()) << "Consumption:" << currentPowerConsumption << "Production:" << currentPowerProduction << "Acquisition:" << currentPowerAcquisition << "Storage:" << currentPowerStorage;
if (currentPowerAcquisition != m_currentPowerAcquisition
|| currentPowerConsumption != m_currentPowerConsumption
|| currentPowerProduction != m_currentPowerProduction) {
|| currentPowerProduction != m_currentPowerProduction
|| currentPowerStorage != m_currentPowerStorage) {
m_currentPowerAcquisition = currentPowerAcquisition;
m_currentPowerProduction = currentPowerProduction;
m_currentPowerConsumption = currentPowerConsumption;
m_currentPowerStorage = currentPowerStorage;
emit powerBalanceChanged();
m_logger->logPowerBalance(m_currentPowerConsumption, m_currentPowerProduction, m_currentPowerAcquisition, m_currentPowerStorage);
}
}

View File

@ -9,6 +9,8 @@
#include "energymanager.h"
class EnergyLogger;
class EnergyManagerImpl : public EnergyManager
{
Q_OBJECT
@ -21,6 +23,9 @@ public:
double currentPowerConsumption() const override;
double currentPowerProduction() const override;
double currentPowerAcquisition() const override;
double currentPowerStorage() const override;
EnergyLogs* logs() const override;
private:
void watchThing(Thing *thing);
@ -40,6 +45,9 @@ private:
double m_currentPowerConsumption;
double m_currentPowerProduction;
double m_currentPowerAcquisition;
double m_currentPowerStorage;
EnergyLogger *m_logger = nullptr;
};
#endif // ENERGYMANAGERIMPL_H

View File

@ -5,7 +5,7 @@ CONFIG += plugin link_pkgconfig c++11
PKGCONFIG += nymea
QT -= gui
QT += network
QT += network sql
include(../config.pri)
@ -14,10 +14,12 @@ LIBS += -L$$top_builddir/libnymea-energy -lnymea-energy
HEADERS += experiencepluginenergy.h \
energyjsonhandler.h \
energylogger.h \
energymanagerimpl.h
SOURCES += experiencepluginenergy.cpp \
energyjsonhandler.cpp \
energylogger.cpp \
energymanagerimpl.cpp
target.path = $$[QT_INSTALL_LIBS]/nymea/experiences/