Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,16 @@ struct CONFIG_T {
PowerMeterUdpVictronConfig UdpVictron;
} PowerMeter;

struct InverterMeterConfig {
bool Enabled;
uint32_t Source;
PowerMeterMqttConfig Mqtt;
PowerMeterSerialSdmConfig SerialSdm;
PowerMeterHttpJsonConfig HttpJson;
PowerMeterHttpSmlConfig HttpSml;
PowerMeterUdpVictronConfig UdpVictron;
} InverterMeter;

PowerLimiterConfig PowerLimiter;

BatteryConfig Battery;
Expand Down
2 changes: 2 additions & 0 deletions include/WebApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "WebApi_ntp.h"
#include "WebApi_power.h"
#include "WebApi_powermeter.h"
#include "WebApi_invertermeter.h"
#include "WebApi_powerlimiter.h"
#include "WebApi_prometheus.h"
#include "WebApi_security.h"
Expand Down Expand Up @@ -74,6 +75,7 @@ class WebApiClass {
WebApiNtpClass _webApiNtp;
WebApiPowerClass _webApiPower;
WebApiPowerMeterClass _webApiPowerMeter;
WebApiInverterMeterClass _webApiInverterMeter;
WebApiPowerLimiterClass _webApiPowerLimiter;
WebApiPrometheusClass _webApiPrometheus;
WebApiSecurityClass _webApiSecurity;
Expand Down
21 changes: 21 additions & 0 deletions include/WebApi_invertermeter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>
#include <ArduinoJson.h>
#include "Configuration.h"

class WebApiInverterMeterClass {
public:
void init(AsyncWebServer& server, Scheduler& scheduler);

private:
void onStatus(AsyncWebServerRequest* request);
void onAdminGet(AsyncWebServerRequest* request);
void onAdminPost(AsyncWebServerRequest* request);
void onTestHttpJsonRequest(AsyncWebServerRequest* request);
void onTestHttpSmlRequest(AsyncWebServerRequest* request);

AsyncWebServer* _server;
};
31 changes: 31 additions & 0 deletions include/invertermeter/Controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <powermeter/Provider.h>
#include <TaskSchedulerDeclarations.h>
#include <memory>
#include <mutex>

namespace InverterMeters {

class Controller {
public:
void init(Scheduler& scheduler);

void updateSettings();

float getPowerTotal() const;
uint32_t getLastUpdate() const;
bool isDataValid() const;

private:
void loop();

Task _loopTask;
mutable std::mutex _mutex;
std::unique_ptr<PowerMeters::Provider> _upProvider = nullptr;
};

} // namespace InverterMeters

extern InverterMeters::Controller InverterMeter;
29 changes: 29 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,25 @@ bool ConfigurationClass::write()
JsonObject powermeter_udp_victron = powermeter["udp_victron"].to<JsonObject>();
serializePowerMeterUdpVictronConfig(config.PowerMeter.UdpVictron, powermeter_udp_victron);

JsonObject invertermeter = doc["invertermeter"].to<JsonObject>();
invertermeter["enabled"] = config.InverterMeter.Enabled;
invertermeter["source"] = config.InverterMeter.Source;

JsonObject invertermeter_mqtt = invertermeter["mqtt"].to<JsonObject>();
serializePowerMeterMqttConfig(config.InverterMeter.Mqtt, invertermeter_mqtt);

JsonObject invertermeter_serial_sdm = invertermeter["serial_sdm"].to<JsonObject>();
serializePowerMeterSerialSdmConfig(config.InverterMeter.SerialSdm, invertermeter_serial_sdm);

JsonObject invertermeter_http_json = invertermeter["http_json"].to<JsonObject>();
serializePowerMeterHttpJsonConfig(config.InverterMeter.HttpJson, invertermeter_http_json);

JsonObject invertermeter_http_sml = invertermeter["http_sml"].to<JsonObject>();
serializePowerMeterHttpSmlConfig(config.InverterMeter.HttpSml, invertermeter_http_sml);

JsonObject invertermeter_udp_victron = invertermeter["udp_victron"].to<JsonObject>();
serializePowerMeterUdpVictronConfig(config.InverterMeter.UdpVictron, invertermeter_udp_victron);

JsonObject powerlimiter = doc["powerlimiter"].to<JsonObject>();
serializePowerLimiterConfig(config.PowerLimiter, powerlimiter);

Expand Down Expand Up @@ -877,6 +896,16 @@ bool ConfigurationClass::read()

deserializePowerMeterUdpVictronConfig(powermeter["udp_victron"], config.PowerMeter.UdpVictron);

JsonObject invertermeter = doc["invertermeter"];
config.InverterMeter.Enabled = invertermeter["enabled"] | false; // Default to disabled
config.InverterMeter.Source = invertermeter["source"] | POWERMETER_SOURCE;

deserializePowerMeterMqttConfig(invertermeter["mqtt"], config.InverterMeter.Mqtt);
deserializePowerMeterSerialSdmConfig(invertermeter["serial_sdm"], config.InverterMeter.SerialSdm);
deserializePowerMeterHttpJsonConfig(invertermeter["http_json"], config.InverterMeter.HttpJson);
deserializePowerMeterHttpSmlConfig(invertermeter["http_sml"], config.InverterMeter.HttpSml);
deserializePowerMeterUdpVictronConfig(invertermeter["udp_victron"], config.InverterMeter.UdpVictron);

deserializePowerLimiterConfig(doc["powerlimiter"], config.PowerLimiter);

JsonObject battery = doc["battery"];
Expand Down
34 changes: 26 additions & 8 deletions src/PowerLimiter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <battery/Controller.h>
#include <battery/Stats.h>
#include <powermeter/Controller.h>
#include <invertermeter/Controller.h>
#include "PowerLimiter.h"
#include "Configuration.h"
#include "MqttSettings.h"
Expand Down Expand Up @@ -201,13 +202,17 @@ void PowerLimiterClass::loop()
}

// if the power meter is being used, i.e., if its data is valid, we want to
// wait for a new reading after adjusting the inverter limit. otherwise, we
// proceed as we will use a fallback limit independent of the power meter.
// wait for a new reading after adjusting the inverter limit. however, if
// an inverter meter is available and provides recent data, we can proceed
// without waiting as we have a direct measurement of inverter output.
// the power meter reading is expected to be at most 2 seconds old when it
// arrives. this can be the case for readings provided by networked meter
// readers, where a packet needs to travel through the network for some
// time after the actual measurement was done by the reader.
if (PowerMeter.isDataValid() && PowerMeter.getLastUpdate() <= (latestInverterStats + 2000)) {
bool hasRecentInverterMeter = InverterMeter.isDataValid() &&
(millis() - InverterMeter.getLastUpdate()) < 5000;

if (PowerMeter.isDataValid() && PowerMeter.getLastUpdate() <= (latestInverterStats + 2000) && !hasRecentInverterMeter) {
return announceStatus(Status::PowerMeterPending);
}

Expand Down Expand Up @@ -567,12 +572,25 @@ uint16_t PowerLimiterClass::calcTargetOutput() const
}

int16_t currentTotalOutput = 0;
for (auto const& upInv : _inverters) {
// non-eligible inverters don't participate in this DPL round at all.
// inverters in standby report 0 W output, so we can iterate them.
if (!upInv->isEligible()) { continue; }

// If we have a valid inverter meter reading, use that for more accurate
// and faster measurements instead of summing individual inverter outputs
bool useInverterMeter = InverterMeter.isDataValid() &&
(millis() - InverterMeter.getLastUpdate()) < 5000;

if (useInverterMeter) {
currentTotalOutput = static_cast<int16_t>(InverterMeter.getPowerTotal());
DTU_LOGD("using inverter meter reading: %.1f W", InverterMeter.getPowerTotal());
} else {
// Fall back to summing individual inverter outputs from DTU stats
for (auto const& upInv : _inverters) {
// non-eligible inverters don't participate in this DPL round at all.
// inverters in standby report 0 W output, so we can iterate them.
if (!upInv->isEligible()) { continue; }

currentTotalOutput += upInv->getCurrentOutputAcWatts();
currentTotalOutput += upInv->getCurrentOutputAcWatts();
}
DTU_LOGD("using DTU stats total: %d W", currentTotalOutput);
}

// this value is negative if we are exporting more than "targetConsumption"
Expand Down
1 change: 1 addition & 0 deletions src/WebApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ void WebApiClass::init(Scheduler& scheduler)
_webApiWsLive.init(_server, scheduler);
_webApiBattery.init(_server, scheduler);
_webApiPowerMeter.init(_server, scheduler);
_webApiInverterMeter.init(_server, scheduler);
_webApiPowerLimiter.init(_server, scheduler);
_webApiWsSolarChargerLive.init(_server, scheduler);
_webApiSolarCharger.init(_server, scheduler);
Expand Down
Loading