only on ESP32-S3-USB. this fiddles with the available hardware UARTs to make it possible to use a third Victron MPPT. if three MPPTs are defined int the pin mapping, you will not be able to use the SmartShunt and JK BMS battery interfaces. note that using a second MPPT will also conflict with the SDM power meter, and that conflict is not detected, yet.
221 lines
6.3 KiB
C++
221 lines
6.3 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
#include "VictronMppt.h"
|
|
#include "Configuration.h"
|
|
#include "PinMapping.h"
|
|
#include "MessageOutput.h"
|
|
#include "SerialPortManager.h"
|
|
|
|
VictronMpptClass VictronMppt;
|
|
|
|
void VictronMpptClass::init(Scheduler& scheduler)
|
|
{
|
|
scheduler.addTask(_loopTask);
|
|
_loopTask.setCallback([this] { loop(); });
|
|
_loopTask.setIterations(TASK_FOREVER);
|
|
_loopTask.enable();
|
|
|
|
this->updateSettings();
|
|
}
|
|
|
|
void VictronMpptClass::updateSettings()
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
|
|
_controllers.clear();
|
|
SerialPortManager.invalidateMpptPorts();
|
|
|
|
CONFIG_T& config = Configuration.get();
|
|
if (!config.Vedirect.Enabled) { return; }
|
|
|
|
const PinMapping_t& pin = PinMapping.get();
|
|
|
|
// HW UART 1 has always been the designated UART to connect a Victron MPPT
|
|
if (!initController(pin.victron_rx, pin.victron_tx,
|
|
config.Vedirect.VerboseLogging, 1, 1)) { return; }
|
|
|
|
// HW UART 2 conflicts with the SDM power meter and the battery interface
|
|
if (!initController(pin.victron_rx2, pin.victron_tx2,
|
|
config.Vedirect.VerboseLogging, 2, 2)) { return; }
|
|
|
|
// HW UART 0 is only available on ESP32-S3 with logging over USB CDC, and
|
|
// furthermore still conflicts with the battery interface in that case
|
|
initController(pin.victron_rx3, pin.victron_tx3,
|
|
config.Vedirect.VerboseLogging, 3, 0);
|
|
}
|
|
|
|
bool VictronMpptClass::initController(int8_t rx, int8_t tx, bool logging,
|
|
uint8_t instance, uint8_t hwSerialPort)
|
|
{
|
|
MessageOutput.printf("[VictronMppt Instance %d] rx = %d, tx = %d, "
|
|
"hwSerialPort = %d\r\n", instance, rx, tx, hwSerialPort);
|
|
|
|
if (rx < 0) {
|
|
MessageOutput.printf("[VictronMppt Instance %d] invalid pin config\r\n", instance);
|
|
return false;
|
|
}
|
|
|
|
if (!SerialPortManager.allocateMpptPort(hwSerialPort)) { return false; }
|
|
|
|
auto upController = std::make_unique<VeDirectMpptController>();
|
|
upController->init(rx, tx, &MessageOutput, logging, hwSerialPort);
|
|
_controllers.push_back(std::move(upController));
|
|
return true;
|
|
}
|
|
|
|
void VictronMpptClass::loop()
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
|
|
for (auto const& upController : _controllers) {
|
|
upController->loop();
|
|
}
|
|
}
|
|
|
|
bool VictronMpptClass::isDataValid() const
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
|
|
for (auto const& upController: _controllers) {
|
|
if (!upController->isDataValid()) { return false; }
|
|
}
|
|
|
|
return !_controllers.empty();
|
|
}
|
|
|
|
bool VictronMpptClass::isDataValid(size_t idx) const
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
|
|
if (_controllers.empty() || idx >= _controllers.size()) {
|
|
return false;
|
|
}
|
|
|
|
return _controllers[idx]->isDataValid();
|
|
}
|
|
|
|
uint32_t VictronMpptClass::getDataAgeMillis() const
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
|
|
if (_controllers.empty()) { return 0; }
|
|
|
|
auto now = millis();
|
|
|
|
auto iter = _controllers.cbegin();
|
|
uint32_t age = now - (*iter)->getLastUpdate();
|
|
++iter;
|
|
|
|
while (iter != _controllers.end()) {
|
|
age = std::min<uint32_t>(age, now - (*iter)->getLastUpdate());
|
|
++iter;
|
|
}
|
|
|
|
return age;
|
|
}
|
|
|
|
uint32_t VictronMpptClass::getDataAgeMillis(size_t idx) const
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
|
|
if (_controllers.empty() || idx >= _controllers.size()) { return 0; }
|
|
|
|
return millis() - _controllers[idx]->getLastUpdate();
|
|
}
|
|
|
|
std::optional<VeDirectMpptController::data_t> VictronMpptClass::getData(size_t idx) const
|
|
{
|
|
std::lock_guard<std::mutex> lock(_mutex);
|
|
|
|
if (_controllers.empty() || idx >= _controllers.size()) {
|
|
MessageOutput.printf("ERROR: MPPT controller index %d is out of bounds (%d controllers)\r\n",
|
|
idx, _controllers.size());
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (!_controllers[idx]->isDataValid()) { return std::nullopt; }
|
|
|
|
return _controllers[idx]->getData();
|
|
}
|
|
|
|
int32_t VictronMpptClass::getPowerOutputWatts() const
|
|
{
|
|
int32_t sum = 0;
|
|
|
|
for (const auto& upController : _controllers) {
|
|
if (!upController->isDataValid()) { continue; }
|
|
|
|
// if any charge controller is part of a VE.Smart network, and if the
|
|
// charge controller is connected in a way that allows to send
|
|
// requests, we should have the "network total DC input power"
|
|
// available. if so, to estimate the output power, we multiply by
|
|
// the calculated efficiency of the connected charge controller.
|
|
auto networkPower = upController->getData().NetworkTotalDcInputPowerMilliWatts;
|
|
if (networkPower.first > 0) {
|
|
return static_cast<int32_t>(networkPower.second / 1000.0 * upController->getData().mpptEfficiency_Percent / 100);
|
|
}
|
|
|
|
sum += upController->getData().batteryOutputPower_W;
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
int32_t VictronMpptClass::getPanelPowerWatts() const
|
|
{
|
|
int32_t sum = 0;
|
|
|
|
for (const auto& upController : _controllers) {
|
|
if (!upController->isDataValid()) { continue; }
|
|
|
|
// if any charge controller is part of a VE.Smart network, and if the
|
|
// charge controller is connected in a way that allows to send
|
|
// requests, we should have the "network total DC input power" available.
|
|
auto networkPower = upController->getData().NetworkTotalDcInputPowerMilliWatts;
|
|
if (networkPower.first > 0) {
|
|
return static_cast<int32_t>(networkPower.second / 1000.0);
|
|
}
|
|
|
|
sum += upController->getData().panelPower_PPV_W;
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
float VictronMpptClass::getYieldTotal() const
|
|
{
|
|
float sum = 0;
|
|
|
|
for (const auto& upController : _controllers) {
|
|
if (!upController->isDataValid()) { continue; }
|
|
sum += upController->getData().yieldTotal_H19_Wh / 1000.0;
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
float VictronMpptClass::getYieldDay() const
|
|
{
|
|
float sum = 0;
|
|
|
|
for (const auto& upController : _controllers) {
|
|
if (!upController->isDataValid()) { continue; }
|
|
sum += upController->getData().yieldToday_H20_Wh / 1000.0;
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
float VictronMpptClass::getOutputVoltage() const
|
|
{
|
|
float min = -1;
|
|
|
|
for (const auto& upController : _controllers) {
|
|
if (!upController->isDataValid()) { continue; }
|
|
float volts = upController->getData().batteryVoltage_V_mV / 1000.0;
|
|
if (min == -1) { min = volts; }
|
|
min = std::min(min, volts);
|
|
}
|
|
|
|
return min;
|
|
}
|