From bd6472664ebc63a69ec483bfd13873623a55a201 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 2 Nov 2022 23:02:49 +0100 Subject: [PATCH] Fix occationally lost samples after startup If a device doesn't produce any currentPower value change after startup until the first regular sample is created, we may miss the very first values. --- plugin/energymanagerimpl.cpp | 107 +++++++++++++++++++---------------- plugin/energymanagerimpl.h | 1 + 2 files changed, 58 insertions(+), 50 deletions(-) diff --git a/plugin/energymanagerimpl.cpp b/plugin/energymanagerimpl.cpp index 87ec0e1..a739542 100644 --- a/plugin/energymanagerimpl.cpp +++ b/plugin/energymanagerimpl.cpp @@ -154,58 +154,11 @@ void EnergyManagerImpl::watchThing(Thing *thing) m_thingsTotalEnergyProducedCache[thing] = qMakePair(stateEntry.totalProduction(), entry.totalProduction()); qCDebug(dcEnergyExperience()) << "Loaded thing power totals for" << thing->name() << "Consumption:" << entry.totalConsumption() << "Production:" << entry.totalProduction() << "Last thing state consumption:" << stateEntry.totalConsumption() << "production:" << stateEntry.totalProduction(); + updateThingPower(thing); + connect(thing, &Thing::stateValueChanged, this, [=](const StateTypeId &stateTypeId, const QVariant &/*value*/){ if (QStringList({"currentPower", "totalEnergyConsumed", "totalEnergyProduced"}).contains(thing->thingClass().getStateType(stateTypeId).name())) { - - // We'll be keeping our own counters, starting from 0 at the time they're added to nymea and increasing with the things counters. - // This way we'll have proper logs even if the thing counter is reset (some things may reset their counter on power loss, factory reset etc) - // and also won't start with huge values if the thing has been counting for a while and only added to nymea later on - - - // Consumption - double oldThingConsumptionState = m_thingsTotalEnergyConsumedCache.value(thing).first; - double oldThingConsumptionInternal = m_thingsTotalEnergyConsumedCache.value(thing).second; - double newThingConsumptionState = thing->stateValue("totalEnergyConsumed").toDouble(); - // For the very first cycle (oldConsumption is 0) we'll sync up on the meter, without actually adding it to our diff - if (oldThingConsumptionState == 0 && newThingConsumptionState != 0) { - qInfo(dcEnergyExperience()) << "Don't have a consumption counter for" << thing->name() << "Synching internal counters to initial value:" << newThingConsumptionState; - oldThingConsumptionState = newThingConsumptionState; - } - // If the thing's meter has been reset in the meantime (newConsumption < oldConsumption) we'll sync down, taking the whole diff from 0 to new value - if (newThingConsumptionState < oldThingConsumptionState) { - qCInfo(dcEnergyExperience()).nospace() << "Thing meter for " << thing->name() << " seems to have been reset. Old value: " << oldThingConsumptionState << " New value: " << newThingConsumptionState << ". Re-synching internal consumption counter."; - oldThingConsumptionState = newThingConsumptionState; - } - double consumptionDiff = newThingConsumptionState - oldThingConsumptionState; - double newThingConsumptionInternal = oldThingConsumptionInternal + consumptionDiff; - m_thingsTotalEnergyConsumedCache[thing] = qMakePair(newThingConsumptionState, newThingConsumptionInternal); - - - // Production - double oldThingProductionState = m_thingsTotalEnergyProducedCache.value(thing).first; - double oldThingProductionInternal = m_thingsTotalEnergyProducedCache.value(thing).second; - double newThingProductionState = thing->stateValue("totalEnergyProduced").toDouble(); - // For the very first cycle (oldProductino is 0) we'll sync up on the meter, without actually adding it to our diff - if (oldThingProductionState == 0 && newThingProductionState != 0) { - qInfo(dcEnergyExperience()) << "Don't have a production counter for" << thing->name() << "Synching internal counter to initial value:" << newThingProductionState; - oldThingProductionState = newThingProductionState; - } - // If the thing's meter has been reset in the meantime (newProduction < oldProduction) we'll sync down, taking the whole diff from 0 to new value - if (newThingProductionState < oldThingProductionState) { - qCInfo(dcEnergyExperience()) << "Thing meter for" << thing->name() << "seems to have been reset. Re-synching internal production counter."; - oldThingProductionState = newThingProductionState; - } - double productionDiff = newThingProductionState - oldThingProductionState; - double newThingProductionInternal = oldThingProductionInternal + productionDiff; - m_thingsTotalEnergyProducedCache[thing] = qMakePair(newThingProductionState, newThingProductionInternal); - - - // Write to log - qCDebug(dcEnergyExperience()) << "Logging thing" << thing->name() << "total consumption:" << newThingConsumptionInternal << "production:" << newThingProductionInternal; - m_logger->logThingPower(thing->id(), thing->state("currentPower").value().toDouble(), newThingConsumptionInternal, newThingProductionInternal); - - // Cache the thing state values in case nymea is restarted - m_logger->cacheThingEntry(thing->id(), newThingConsumptionState, newThingProductionState); + updateThingPower(thing); } }); } @@ -309,6 +262,60 @@ void EnergyManagerImpl::updatePowerBalance() } } +void EnergyManagerImpl::updateThingPower(Thing *thing) +{ + // We'll be keeping our own counters, starting from 0 at the time they're added to nymea and increasing with the things counters. + // This way we'll have proper logs even if the thing counter is reset (some things may reset their counter on power loss, factory reset etc) + // and also won't start with huge values if the thing has been counting for a while and only added to nymea later on + + + // Consumption + double oldThingConsumptionState = m_thingsTotalEnergyConsumedCache.value(thing).first; + double oldThingConsumptionInternal = m_thingsTotalEnergyConsumedCache.value(thing).second; + double newThingConsumptionState = thing->stateValue("totalEnergyConsumed").toDouble(); + // For the very first cycle (oldConsumption is 0) we'll sync up on the meter, without actually adding it to our diff + if (oldThingConsumptionState == 0 && newThingConsumptionState != 0) { + qInfo(dcEnergyExperience()) << "Don't have a consumption counter for" << thing->name() << "Synching internal counters to initial value:" << newThingConsumptionState; + oldThingConsumptionState = newThingConsumptionState; + } + // If the thing's meter has been reset in the meantime (newConsumption < oldConsumption) we'll sync down, taking the whole diff from 0 to new value + if (newThingConsumptionState < oldThingConsumptionState) { + qCInfo(dcEnergyExperience()).nospace() << "Thing meter for " << thing->name() << " seems to have been reset. Old value: " << oldThingConsumptionState << " New value: " << newThingConsumptionState << ". Re-synching internal consumption counter."; + oldThingConsumptionState = newThingConsumptionState; + } + double consumptionDiff = newThingConsumptionState - oldThingConsumptionState; + double newThingConsumptionInternal = oldThingConsumptionInternal + consumptionDiff; + m_thingsTotalEnergyConsumedCache[thing] = qMakePair(newThingConsumptionState, newThingConsumptionInternal); + + + // Production + double oldThingProductionState = m_thingsTotalEnergyProducedCache.value(thing).first; + double oldThingProductionInternal = m_thingsTotalEnergyProducedCache.value(thing).second; + double newThingProductionState = thing->stateValue("totalEnergyProduced").toDouble(); + // For the very first cycle (oldProductino is 0) we'll sync up on the meter, without actually adding it to our diff + if (oldThingProductionState == 0 && newThingProductionState != 0) { + qInfo(dcEnergyExperience()) << "Don't have a production counter for" << thing->name() << "Synching internal counter to initial value:" << newThingProductionState; + oldThingProductionState = newThingProductionState; + } + // If the thing's meter has been reset in the meantime (newProduction < oldProduction) we'll sync down, taking the whole diff from 0 to new value + if (newThingProductionState < oldThingProductionState) { + qCInfo(dcEnergyExperience()) << "Thing meter for" << thing->name() << "seems to have been reset. Re-synching internal production counter."; + oldThingProductionState = newThingProductionState; + } + double productionDiff = newThingProductionState - oldThingProductionState; + double newThingProductionInternal = oldThingProductionInternal + productionDiff; + m_thingsTotalEnergyProducedCache[thing] = qMakePair(newThingProductionState, newThingProductionInternal); + + + // Write to log + qCDebug(dcEnergyExperience()) << "Logging thing" << thing->name() << "total consumption:" << newThingConsumptionInternal << "production:" << newThingProductionInternal; + m_logger->logThingPower(thing->id(), thing->state("currentPower").value().toDouble(), newThingConsumptionInternal, newThingProductionInternal); + + // Cache the thing state values in case nymea is restarted + m_logger->cacheThingEntry(thing->id(), newThingConsumptionState, newThingProductionState); + +} + void EnergyManagerImpl::logDumpConsumers() { foreach (Thing *consumer, m_thingManager->configuredThings().filterByInterface("smartmeterconsumer")) { diff --git a/plugin/energymanagerimpl.h b/plugin/energymanagerimpl.h index 8964e9e..006b816 100644 --- a/plugin/energymanagerimpl.h +++ b/plugin/energymanagerimpl.h @@ -36,6 +36,7 @@ private: void unwatchThing(const ThingId &thingId); void updatePowerBalance(); + void updateThingPower(Thing *thing); private slots: void logDumpConsumers();