the DPL is interested in the battery's voltage to make decisions about draining the battery or letting it charge (if the user opts to use voltage thresholds rather than SoC thresholds). using the DC input voltage reported by the inverter under control has disadvantages: * the data might be quite old due to the communication protocol implementation. more inverters being polled means even more lag. the connection being wireless makes this even worse, due to the need to retry the occasional lost packet, etc. * the data is not very accurate, since the DC input of the inverter is actually some cabling and a couple of junctions away from the actual battery. this voltage drop can mostly only be estimated and is worse with higher load. the load correction factor is there to mitigate this, but it has its own problems and is cumbersome to calibrate. instead, this change aims to use more accurate battery voltage readings, if possible. the DPL now prefers the voltage as reported by the BMS, since it is for sure the closest to the battery of all measuring points and measures its voltage accurately regardless of the load (the voltage reading will still drop with higher loads, but this will be only due to the battery's internal resistance, not that of cabling or junctions). if no BMS voltage reading is available, the DPL will instead use the charge controller's voltage reading, as it is available with much higher frequency and is assumed to be more accurate as it offers a resolution of 10mV. only if none of these two sources can be used, the inverter DC input voltage is assumed as the battery voltage. closes #655.
109 lines
3.5 KiB
C++
109 lines
3.5 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
#pragma once
|
|
|
|
#include "Configuration.h"
|
|
#include <espMqttClient.h>
|
|
#include <Arduino.h>
|
|
#include <Hoymiles.h>
|
|
#include <memory>
|
|
#include <functional>
|
|
#include <TaskSchedulerDeclarations.h>
|
|
#include <frozen/string.h>
|
|
|
|
#define PL_UI_STATE_INACTIVE 0
|
|
#define PL_UI_STATE_CHARGING 1
|
|
#define PL_UI_STATE_USE_SOLAR_ONLY 2
|
|
#define PL_UI_STATE_USE_SOLAR_AND_BATTERY 3
|
|
|
|
#define PL_MODE_ENABLE_NORMAL_OP 0
|
|
#define PL_MODE_FULL_DISABLE 1
|
|
#define PL_MODE_SOLAR_PT_ONLY 2
|
|
|
|
typedef enum {
|
|
EMPTY_WHEN_FULL= 0,
|
|
EMPTY_AT_NIGHT
|
|
} batDrainStrategy;
|
|
|
|
|
|
class PowerLimiterClass {
|
|
public:
|
|
enum class Status : unsigned {
|
|
Initializing,
|
|
DisabledByConfig,
|
|
DisabledByMqtt,
|
|
WaitingForValidTimestamp,
|
|
PowerMeterDisabled,
|
|
PowerMeterTimeout,
|
|
PowerMeterPending,
|
|
InverterInvalid,
|
|
InverterChanged,
|
|
InverterOffline,
|
|
InverterCommandsDisabled,
|
|
InverterLimitPending,
|
|
InverterPowerCmdPending,
|
|
InverterDevInfoPending,
|
|
InverterStatsPending,
|
|
UnconditionalSolarPassthrough,
|
|
NoVeDirect,
|
|
Settling,
|
|
Stable,
|
|
};
|
|
|
|
void init(Scheduler& scheduler);
|
|
uint8_t getPowerLimiterState();
|
|
int32_t getLastRequestedPowerLimit();
|
|
|
|
enum class Mode : unsigned {
|
|
Normal = 0,
|
|
Disabled = 1,
|
|
UnconditionalFullSolarPassthrough = 2
|
|
};
|
|
|
|
void setMode(Mode m) { _mode = m; }
|
|
Mode getMode() const { return _mode; }
|
|
void calcNextInverterRestart();
|
|
|
|
private:
|
|
void loop();
|
|
|
|
Task _loopTask;
|
|
|
|
int32_t _lastRequestedPowerLimit = 0;
|
|
uint32_t _lastPowerLimitMillis = 0;
|
|
uint32_t _shutdownTimeout = 0;
|
|
Status _lastStatus = Status::Initializing;
|
|
uint32_t _lastStatusPrinted = 0;
|
|
uint32_t _lastCalculation = 0;
|
|
static constexpr uint32_t _calculationBackoffMsDefault = 128;
|
|
uint32_t _calculationBackoffMs = _calculationBackoffMsDefault;
|
|
Mode _mode = Mode::Normal;
|
|
std::shared_ptr<InverterAbstract> _inverter = nullptr;
|
|
bool _batteryDischargeEnabled = false;
|
|
uint32_t _nextInverterRestart = 0; // Values: 0->not calculated / 1->no restart configured / >1->time of next inverter restart in millis()
|
|
uint32_t _nextCalculateCheck = 5000; // time in millis for next NTP check to calulate restart
|
|
bool _fullSolarPassThroughEnabled = false;
|
|
bool _verboseLogging = true;
|
|
|
|
frozen::string const& getStatusText(Status status);
|
|
void announceStatus(Status status);
|
|
bool shutdown(Status status);
|
|
bool shutdown() { return shutdown(_lastStatus); }
|
|
float getBatteryVoltage(bool log = false);
|
|
int32_t inverterPowerDcToAc(std::shared_ptr<InverterAbstract> inverter, int32_t dcPower);
|
|
void unconditionalSolarPassthrough(std::shared_ptr<InverterAbstract> inverter);
|
|
bool canUseDirectSolarPower();
|
|
int32_t calcPowerLimit(std::shared_ptr<InverterAbstract> inverter, bool solarPowerEnabled, bool batteryDischargeEnabled);
|
|
void commitPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t limit, bool enablePowerProduction);
|
|
bool setNewPowerLimit(std::shared_ptr<InverterAbstract> inverter, int32_t newPowerLimit);
|
|
int32_t getSolarChargePower();
|
|
float getLoadCorrectedVoltage();
|
|
bool testThreshold(float socThreshold, float voltThreshold,
|
|
std::function<bool(float, float)> compare);
|
|
bool isStartThresholdReached();
|
|
bool isStopThresholdReached();
|
|
bool isBelowStopThreshold();
|
|
bool useFullSolarPassthrough();
|
|
};
|
|
|
|
extern PowerLimiterClass PowerLimiter;
|