Skip to content

Commit

Permalink
setup stats and use them
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreasBoehm committed Jan 4, 2025
1 parent 9bee467 commit d5775dc
Show file tree
Hide file tree
Showing 16 changed files with 377 additions and 550 deletions.
3 changes: 0 additions & 3 deletions include/WebApi_ws_solarcharger_live.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,15 @@ class WebApiWsSolarChargerLiveClass {

private:
void generateCommonJsonResponse(JsonVariant& root, bool fullUpdate);
static void populateJson(const JsonObject &root, const VeDirectMpptController::data_t &mpptData);
void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);
bool hasUpdate(size_t idx);

AsyncWebServer* _server;
AsyncWebSocket _ws;
AuthenticationMiddleware _simpleDigestAuth;

uint32_t _lastFullPublish = 0;
uint32_t _lastPublish = 0;
uint16_t responseSize() const;

std::mutex _mutex;

Expand Down
26 changes: 2 additions & 24 deletions include/solarcharger/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <mutex>
#include <TaskSchedulerDeclarations.h>
#include <solarcharger/Provider.h>
#include <solarcharger/Stats.h>
#include <VeDirectMpptController.h>

namespace SolarChargers {
Expand All @@ -14,30 +15,7 @@ class Controller {
void init(Scheduler&);
void updateSettings();

// TODO(andreasboehm): below methods are taken from VictronMppt to start abstracting
// solar chargers without breaking everything.
size_t controllerAmount();
uint32_t getDataAgeMillis();
uint32_t getDataAgeMillis(size_t idx);

// total output of all MPPT charge controllers in Watts
int32_t getOutputPowerWatts();

// total panel input power of all MPPT charge controllers in Watts
int32_t getPanelPowerWatts();

// sum of total yield of all MPPT charge controllers in kWh
float getYieldTotal();

// sum of today's yield of all MPPT charge controllers in kWh
float getYieldDay();

// minimum of all MPPT charge controllers' output voltages in V
float getOutputVoltage();

std::optional<VeDirectMpptController::data_t> getData(size_t idx = 0);

bool isDataValid();
std::shared_ptr<Stats const> getStats() const;

private:
void loop();
Expand Down
20 changes: 20 additions & 0 deletions include/solarcharger/DummyStats.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <solarcharger/Stats.h>

namespace SolarChargers {

class DummyStats : public Stats {
public:
uint32_t getAgeMillis() const override { return 0; }
std::optional<int32_t> getOutputPowerWatts() const override { return std::nullopt; }
std::optional<float> getOutputVoltage() const override { return std::nullopt; }
int32_t getPanelPowerWatts() const override { return 0; }
float getYieldTotal() const override { return 0; }
float getYieldDay() const override { return 0; }
void getLiveViewData(JsonVariant& root, boolean fullUpdate, uint32_t lastPublish) const override {}
void mqttPublish() const override {}
};

} // namespace SolarChargers
24 changes: 0 additions & 24 deletions include/solarcharger/Provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,6 @@ class Provider {
virtual void loop() = 0;
virtual std::shared_ptr<Stats> getStats() const = 0;
virtual HassIntegration const& getHassIntegration() const = 0;

// TODO(andreasboehm): below methods are taken from VictronMppt to start abstracting
// solar chargers without breaking everything.
virtual size_t controllerAmount() const = 0;
virtual uint32_t getDataAgeMillis() const = 0;
virtual uint32_t getDataAgeMillis(size_t idx) const = 0;
// total output of all MPPT charge controllers in Watts
virtual int32_t getOutputPowerWatts() const = 0;

// total panel input power of all MPPT charge controllers in Watts
virtual int32_t getPanelPowerWatts() const = 0;

// sum of total yield of all MPPT charge controllers in kWh
virtual float getYieldTotal() const = 0;

// sum of today's yield of all MPPT charge controllers in kWh
virtual float getYieldDay() const = 0;

// minimum of all MPPT charge controllers' output voltages in V
virtual float getOutputVoltage() const = 0;

virtual std::optional<VeDirectMpptController::data_t> getData(size_t idx = 0) const = 0;

virtual bool isDataValid() const = 0;
};

} // namespace SolarChargers
24 changes: 23 additions & 1 deletion include/solarcharger/Stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,38 @@
#pragma once

#include <Arduino.h>
#include <AsyncJson.h>

namespace SolarChargers {

class Stats {
public:
// the last time *any* data was updated
virtual uint32_t getAgeMillis() const;

// total output of all MPPT charge controllers in Watts
virtual std::optional<int32_t> getOutputPowerWatts() const;

// minimum of all MPPT charge controllers' output voltages in V
virtual std::optional<float> getOutputVoltage() const;

// total panel input power of all MPPT charge controllers in Watts
virtual int32_t getPanelPowerWatts() const;

// sum of total yield of all MPPT charge controllers in kWh
virtual float getYieldTotal() const;

// sum of today's yield of all MPPT charge controllers in Wh
virtual float getYieldDay() const;

// convert stats to JSON for web application live view
virtual void getLiveViewData(JsonVariant& root, boolean fullUpdate, uint32_t lastPublish) const;

void mqttLoop();

// the interval at which all data will be re-published, even
// if they did not change. used to calculate Home Assistent expiration.
virtual uint32_t getMqttFullPublishIntervalMs() const;
uint32_t getMqttFullPublishIntervalMs() const;

protected:
virtual void mqttPublish() const;
Expand Down
39 changes: 1 addition & 38 deletions include/solarcharger/victron/Provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,42 +24,6 @@ class Provider : public ::SolarChargers::Provider {
std::shared_ptr<::SolarChargers::Stats> getStats() const final { return _stats; }
::SolarChargers::HassIntegration const& getHassIntegration() const final { return _hassIntegration; }

bool isDataValid() const final;

// returns the data age of all controllers,
// i.e, the youngest data's age is returned.
uint32_t getDataAgeMillis() const final;
uint32_t getDataAgeMillis(size_t idx) const final;

size_t controllerAmount() const final { return _controllers.size(); }
std::optional<VeDirectMpptController::data_t> getData(size_t idx = 0) const final;

// total output of all MPPT charge controllers in Watts
int32_t getOutputPowerWatts() const final;

// total panel input power of all MPPT charge controllers in Watts
int32_t getPanelPowerWatts() const final;

// sum of total yield of all MPPT charge controllers in kWh
float getYieldTotal() const final;

// sum of today's yield of all MPPT charge controllers in kWh
float getYieldDay() const final;

// minimum of all MPPT charge controllers' output voltages in V
float getOutputVoltage() const final;

// returns the state of operation from the first available controller
std::optional<uint8_t> getStateOfOperation() const;

// returns the requested value from the first available controller in mV
enum class MPPTVoltage : uint8_t {
ABSORPTION = 0,
FLOAT = 1,
BATTERY = 2
};
std::optional<float> getVoltage(MPPTVoltage kindOf) const;

private:
Provider(Provider const& other) = delete;
Provider(Provider&& other) = delete;
Expand All @@ -73,8 +37,7 @@ class Provider : public ::SolarChargers::Provider {
std::shared_ptr<Stats> _stats = std::make_shared<Stats>();
HassIntegration _hassIntegration;

bool initController(int8_t rx, int8_t tx, bool logging,
uint8_t instance);
bool initController(int8_t rx, int8_t tx, bool logging, uint8_t instance);
};

} // namespace SolarChargers::Victron
21 changes: 18 additions & 3 deletions include/solarcharger/victron/Stats.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,24 @@ namespace SolarChargers::Victron {

class Stats : public ::SolarChargers::Stats {
public:
uint32_t getAgeMillis() const final;
std::optional<int32_t> getOutputPowerWatts() const final;
std::optional<float> getOutputVoltage() const final;
int32_t getPanelPowerWatts() const final;
float getYieldTotal() const final;
float getYieldDay() const final;

void getLiveViewData(JsonVariant& root, boolean fullUpdate, uint32_t lastPublish) const final;
void mqttPublish() const final;

void update(const String serial, const std::optional<VeDirectMpptController::data_t> mpptData, uint32_t lastUpdate) const;

private:
mutable std::map<std::string, VeDirectMpptController::data_t> _kvFrames;
// TODO(andreasboehm): _data and _lastUpdate in two different structures is not ideal and needs to change
mutable std::map<String, std::optional<VeDirectMpptController::data_t>> _data;
mutable std::map<String, uint32_t> _lastUpdate;

mutable std::map<String, VeDirectMpptController::data_t> _previousData;

// point of time in millis() when updated values will be published
mutable uint32_t _nextPublishUpdatesOnly = 0;
Expand All @@ -22,8 +36,9 @@ class Stats : public ::SolarChargers::Stats {

mutable bool _PublishFull;

void publish_mppt_data(const VeDirectMpptController::data_t &mpptData,
const VeDirectMpptController::data_t &frame) const;
void populateJsonWithInstanceStats(const JsonObject &root, const VeDirectMpptController::data_t &mpptData) const;

void publish_mppt_data(const VeDirectMpptController::data_t &mpptData, const VeDirectMpptController::data_t &frame) const;
};

} // namespace SolarChargers::Victron
2 changes: 1 addition & 1 deletion lib/VeDirectFrameHandler/VeDirectData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ frozen::string const& veMpptStruct::getErrAsString() const
{ 39, "Input shutdown (due to current flow during off mode)" },
{ 40, "Input" },
{ 65, "Lost communication with one of devices" },
{ 67, "Synchronisedcharging device configuration issue" },
{ 67, "Synchronised charging device configuration issue" },
{ 68, "BMS connection lost" },
{ 116, "Factory calibration data lost" },
{ 117, "Invalid/incompatible firmware" },
Expand Down
18 changes: 12 additions & 6 deletions src/PowerLimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,11 @@ float PowerLimiterClass::getBatteryVoltage(bool log) {
if (inverter.first > 0) { res = inverter.first; }

float chargeControllerVoltage = -1;
if (SolarCharger.isDataValid()) {
res = chargeControllerVoltage = static_cast<float>(SolarCharger.getOutputVoltage());

auto chargerOutputVoltage = SolarCharger.getStats()->getOutputVoltage();

if (chargerOutputVoltage) {
res = chargeControllerVoltage = *chargerOutputVoltage;
}

float bmsVoltage = -1;
Expand Down Expand Up @@ -425,8 +428,9 @@ void PowerLimiterClass::fullSolarPassthrough(PowerLimiterClass::Status reason)

uint16_t targetOutput = 0;

if (SolarCharger.isDataValid()) {
targetOutput = static_cast<uint16_t>(std::max<int32_t>(0, SolarCharger.getOutputPowerWatts()));
auto solarChargerOuput = SolarCharger.getStats()->getOutputPowerWatts();
if (solarChargerOuput) {
targetOutput = static_cast<uint16_t>(std::max<int32_t>(0, *solarChargerOuput));
targetOutput = dcPowerBusToInverterAc(targetOutput);
}

Expand Down Expand Up @@ -675,14 +679,16 @@ bool PowerLimiterClass::updateInverters()
uint16_t PowerLimiterClass::getSolarPassthroughPower()
{
auto const& config = Configuration.get();
auto solarChargerOutput = SolarCharger.getStats()->getOutputPowerWatts();

if (!config.PowerLimiter.SolarPassThroughEnabled
|| isBelowStopThreshold()
|| !SolarCharger.isDataValid()) {
|| !solarChargerOutput
) {
return 0;
}

return SolarCharger.getOutputPowerWatts();
return *solarChargerOutput;
}

float PowerLimiterClass::getBatteryInvertersOutputAcWatts()
Expand Down
17 changes: 13 additions & 4 deletions src/WebApi_ws_live.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,25 @@ void WebApiWsLiveClass::generateOnBatteryJsonResponse(JsonVariant& root, bool al
auto const& config = Configuration.get();
auto constexpr halfOfAllMillis = std::numeric_limits<uint32_t>::max() / 2;

auto solarChargerAge = SolarCharger.getDataAgeMillis();
auto solarChargerAge = SolarCharger.getStats()->getAgeMillis();
if (all || (solarChargerAge > 0 && (millis() - _lastPublishSolarCharger) > solarChargerAge)) {
auto solarchargerObj = root["solarcharger"].to<JsonObject>();
solarchargerObj["enabled"] = config.SolarCharger.Enabled;

if (config.SolarCharger.Enabled) {
auto totalVeObj = solarchargerObj["total"].to<JsonObject>();
addTotalField(totalVeObj, "Power", SolarCharger.getPanelPowerWatts(), "W", 1);
addTotalField(totalVeObj, "YieldDay", SolarCharger.getYieldDay() * 1000, "Wh", 0);
addTotalField(totalVeObj, "YieldTotal", SolarCharger.getYieldTotal(), "kWh", 2);

auto power = SolarCharger.getStats()->getPanelPowerWatts();
auto outputPower = SolarCharger.getStats()->getOutputPowerWatts();

// use output power if available, because it is more accurate
if (outputPower) {
power = *outputPower;
}

addTotalField(totalVeObj, "Power", power, "W", 1);
addTotalField(totalVeObj, "YieldDay", SolarCharger.getStats()->getYieldDay(), "Wh", 0);
addTotalField(totalVeObj, "YieldTotal", SolarCharger.getStats()->getYieldTotal(), "kWh", 2);
}

if (!all) { _lastPublishSolarCharger = millis(); }
Expand Down
Loading

0 comments on commit d5775dc

Please sign in to comment.