Merge branch 'development'
This commit is contained in:
commit
eb578f08c5
@ -111,6 +111,7 @@ class VictronSmartShuntStats : public BatteryStats {
|
||||
float _voltage;
|
||||
float _current;
|
||||
float _temperature;
|
||||
bool _tempPresent;
|
||||
uint8_t _chargeCycles;
|
||||
uint32_t _timeToGo;
|
||||
float _chargedEnergy;
|
||||
|
||||
@ -177,6 +177,7 @@ struct CONFIG_T {
|
||||
uint8_t Battery_JkBmsPollingInterval;
|
||||
|
||||
bool Huawei_Enabled;
|
||||
uint32_t Huawei_CAN_Controller_Frequency;
|
||||
bool Huawei_Auto_Power_Enabled;
|
||||
float Huawei_Auto_Power_Voltage_Limit;
|
||||
float Huawei_Auto_Power_Enable_Voltage_Limit;
|
||||
|
||||
@ -90,7 +90,8 @@ typedef struct RectifierParameters {
|
||||
|
||||
class HuaweiCanCommClass {
|
||||
public:
|
||||
bool init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs);
|
||||
bool init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk,
|
||||
uint8_t huawei_irq, uint8_t huawei_cs, uint32_t frequency);
|
||||
void loop();
|
||||
bool gotNewRxDataFrame(bool clear);
|
||||
uint8_t getErrorCode(bool clear);
|
||||
|
||||
@ -62,7 +62,6 @@ public:
|
||||
bool isValidNrf24Config();
|
||||
bool isValidCmt2300Config();
|
||||
bool isValidEthConfig();
|
||||
bool isValidVictronConfig();
|
||||
bool isValidHuaweiConfig();
|
||||
|
||||
private:
|
||||
|
||||
48
include/VictronMppt.h
Normal file
48
include/VictronMppt.h
Normal file
@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
#include "VeDirectMpptController.h"
|
||||
|
||||
class VictronMpptClass {
|
||||
public:
|
||||
VictronMpptClass() = default;
|
||||
~VictronMpptClass() = default;
|
||||
|
||||
void init();
|
||||
void loop();
|
||||
|
||||
bool isDataValid() const;
|
||||
|
||||
// returns the data age of all controllers,
|
||||
// i.e, the youngest data's age is returned.
|
||||
uint32_t getDataAgeMillis() const;
|
||||
|
||||
VeDirectMpptController::spData_t getData(size_t idx = 0) const;
|
||||
|
||||
// total output of all MPPT charge controllers in Watts
|
||||
int32_t getPowerOutputWatts() const;
|
||||
|
||||
// total panel input power of all MPPT charge controllers in Watts
|
||||
int32_t getPanelPowerWatts() const;
|
||||
|
||||
// sum of total yield of all MPPT charge controllers in kWh
|
||||
double getYieldTotal() const;
|
||||
|
||||
// sum of today's yield of all MPPT charge controllers in kWh
|
||||
double getYieldDay() const;
|
||||
|
||||
private:
|
||||
VictronMpptClass(VictronMpptClass const& other) = delete;
|
||||
VictronMpptClass(VictronMpptClass&& other) = delete;
|
||||
VictronMpptClass& operator=(VictronMpptClass const& other) = delete;
|
||||
VictronMpptClass& operator=(VictronMpptClass&& other) = delete;
|
||||
|
||||
mutable std::mutex _mutex;
|
||||
using controller_t = std::unique_ptr<VeDirectMpptController>;
|
||||
std::vector<controller_t> _controllers;
|
||||
};
|
||||
|
||||
extern VictronMpptClass VictronMppt;
|
||||
@ -20,8 +20,7 @@ private:
|
||||
AsyncWebSocket _ws;
|
||||
|
||||
uint32_t _lastWsPublish = 0;
|
||||
uint32_t _lastVedirectUpdateCheck = 0;
|
||||
uint32_t _lastWsCleanup = 0;
|
||||
uint32_t _newestVedirectTimestamp = 0;
|
||||
uint32_t _dataAgeMillis = 0;
|
||||
static constexpr uint16_t _responseSize = 1024 + 128;
|
||||
};
|
||||
@ -137,6 +137,7 @@
|
||||
#define BATTERY_JKBMS_POLLING_INTERVAL 5
|
||||
|
||||
#define HUAWEI_ENABLED false
|
||||
#define HUAWEI_CAN_CONTROLLER_FREQUENCY 8000000UL
|
||||
#define HUAWEI_AUTO_POWER_VOLTAGE_LIMIT 42.0
|
||||
#define HUAWEI_AUTO_POWER_ENABLE_VOLTAGE_LIMIT 42.0
|
||||
#define HUAWEI_AUTO_POWER_LOWER_POWER_LIMIT 150
|
||||
|
||||
@ -72,19 +72,14 @@ VeDirectFrameHandler::VeDirectFrameHandler() :
|
||||
{
|
||||
}
|
||||
|
||||
void VeDirectFrameHandler::setVerboseLogging(bool verboseLogging)
|
||||
{
|
||||
_verboseLogging = verboseLogging;
|
||||
if (!_verboseLogging) { _debugIn = 0; }
|
||||
}
|
||||
|
||||
void VeDirectFrameHandler::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort)
|
||||
{
|
||||
_vedirectSerial = std::make_unique<HardwareSerial>(hwSerialPort);
|
||||
_vedirectSerial->begin(19200, SERIAL_8N1, rx, tx);
|
||||
_vedirectSerial->flush();
|
||||
_msgOut = msgOut;
|
||||
setVerboseLogging(verboseLogging);
|
||||
_verboseLogging = verboseLogging;
|
||||
_debugIn = 0;
|
||||
}
|
||||
|
||||
void VeDirectFrameHandler::dumpDebugBuffer() {
|
||||
@ -211,7 +206,7 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
|
||||
if (_verboseLogging) { dumpDebugBuffer(); }
|
||||
_checksum = 0;
|
||||
_state = IDLE;
|
||||
frameEndEvent(valid);
|
||||
if (valid) { frameValidEvent(); }
|
||||
break;
|
||||
}
|
||||
case RECORD_HEX:
|
||||
@ -224,22 +219,38 @@ void VeDirectFrameHandler::rxData(uint8_t inbyte)
|
||||
* textRxEvent
|
||||
* This function is called every time a new name/value is successfully parsed. It writes the values to the temporary buffer.
|
||||
*/
|
||||
void VeDirectFrameHandler::textRxEvent(char * name, char * value, veStruct& frame) {
|
||||
bool VeDirectFrameHandler::textRxEvent(std::string const& who, char* name, char* value, veStruct& frame) {
|
||||
if (_verboseLogging) {
|
||||
_msgOut->printf("[Victron %s] Text Event %s: Value: %s\r\n",
|
||||
who.c_str(), name, value );
|
||||
}
|
||||
|
||||
if (strcmp(name, "PID") == 0) {
|
||||
frame.PID = strtol(value, nullptr, 0);
|
||||
return true;
|
||||
}
|
||||
else if (strcmp(name, "SER") == 0) {
|
||||
|
||||
if (strcmp(name, "SER") == 0) {
|
||||
strcpy(frame.SER, value);
|
||||
return true;
|
||||
}
|
||||
else if (strcmp(name, "FW") == 0) {
|
||||
|
||||
if (strcmp(name, "FW") == 0) {
|
||||
strcpy(frame.FW, value);
|
||||
return true;
|
||||
}
|
||||
else if (strcmp(name, "V") == 0) {
|
||||
|
||||
if (strcmp(name, "V") == 0) {
|
||||
frame.V = round(atof(value) / 10.0) / 100.0;
|
||||
return true;
|
||||
}
|
||||
else if (strcmp(name, "I") == 0) {
|
||||
|
||||
if (strcmp(name, "I") == 0) {
|
||||
frame.I = round(atof(value) / 10.0) / 100.0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -269,7 +280,7 @@ int VeDirectFrameHandler::hexRxEvent(uint8_t inbyte) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool VeDirectFrameHandler::isDataValid(veStruct frame) {
|
||||
bool VeDirectFrameHandler::isDataValid(veStruct const& frame) const {
|
||||
if (_lastUpdate == 0) {
|
||||
return false;
|
||||
}
|
||||
@ -279,330 +290,112 @@ bool VeDirectFrameHandler::isDataValid(veStruct frame) {
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned long VeDirectFrameHandler::getLastUpdate()
|
||||
uint32_t VeDirectFrameHandler::getLastUpdate() const
|
||||
{
|
||||
return _lastUpdate;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
String const& VeDirectFrameHandler::getAsString(std::map<T, String> const& values, T val)
|
||||
{
|
||||
auto pos = values.find(val);
|
||||
if (pos == values.end()) {
|
||||
static String dummy;
|
||||
dummy = val;
|
||||
return dummy;
|
||||
}
|
||||
return pos->second;
|
||||
}
|
||||
|
||||
template String const& VeDirectFrameHandler::getAsString(std::map<uint8_t, String> const& values, uint8_t val);
|
||||
template String const& VeDirectFrameHandler::getAsString(std::map<uint16_t, String> const& values, uint16_t val);
|
||||
template String const& VeDirectFrameHandler::getAsString(std::map<uint32_t, String> const& values, uint32_t val);
|
||||
|
||||
/*
|
||||
* getPidAsString
|
||||
* This function returns the product id (PID) as readable text.
|
||||
*/
|
||||
String VeDirectFrameHandler::getPidAsString(uint16_t pid)
|
||||
String VeDirectFrameHandler::veStruct::getPidAsString() const
|
||||
{
|
||||
String strPID ="";
|
||||
static const std::map<uint16_t, String> values = {
|
||||
{ 0x0300, F("BlueSolar MPPT 70|15") },
|
||||
{ 0xA040, F("BlueSolar MPPT 75|50") },
|
||||
{ 0xA041, F("BlueSolar MPPT 150|35") },
|
||||
{ 0xA042, F("BlueSolar MPPT 75|15") },
|
||||
{ 0xA043, F("BlueSolar MPPT 100|15") },
|
||||
{ 0xA044, F("BlueSolar MPPT 100|30") },
|
||||
{ 0xA045, F("BlueSolar MPPT 100|50") },
|
||||
{ 0xA046, F("BlueSolar MPPT 100|70") },
|
||||
{ 0xA047, F("BlueSolar MPPT 150|100") },
|
||||
{ 0xA049, F("BlueSolar MPPT 100|50 rev2") },
|
||||
{ 0xA04A, F("BlueSolar MPPT 100|30 rev2") },
|
||||
{ 0xA04B, F("BlueSolar MPPT 150|35 rev2") },
|
||||
{ 0xA04C, F("BlueSolar MPPT 75|10") },
|
||||
{ 0xA04D, F("BlueSolar MPPT 150|45") },
|
||||
{ 0xA04E, F("BlueSolar MPPT 150|60") },
|
||||
{ 0xA04F, F("BlueSolar MPPT 150|85") },
|
||||
{ 0xA050, F("SmartSolar MPPT 250|100") },
|
||||
{ 0xA051, F("SmartSolar MPPT 150|100") },
|
||||
{ 0xA052, F("SmartSolar MPPT 150|85") },
|
||||
{ 0xA053, F("SmartSolar MPPT 75|15") },
|
||||
{ 0xA054, F("SmartSolar MPPT 75|10") },
|
||||
{ 0xA055, F("SmartSolar MPPT 100|15") },
|
||||
{ 0xA056, F("SmartSolar MPPT 100|30") },
|
||||
{ 0xA057, F("SmartSolar MPPT 100|50") },
|
||||
{ 0xA058, F("SmartSolar MPPT 150|35") },
|
||||
{ 0xA059, F("SmartSolar MPPT 150|10 rev2") },
|
||||
{ 0xA05A, F("SmartSolar MPPT 150|85 rev2") },
|
||||
{ 0xA05B, F("SmartSolar MPPT 250|70") },
|
||||
{ 0xA05C, F("SmartSolar MPPT 250|85") },
|
||||
{ 0xA05D, F("SmartSolar MPPT 250|60") },
|
||||
{ 0xA05E, F("SmartSolar MPPT 250|45") },
|
||||
{ 0xA05F, F("SmartSolar MPPT 100|20") },
|
||||
{ 0xA060, F("SmartSolar MPPT 100|20 48V") },
|
||||
{ 0xA061, F("SmartSolar MPPT 150|45") },
|
||||
{ 0xA062, F("SmartSolar MPPT 150|60") },
|
||||
{ 0xA063, F("SmartSolar MPPT 150|70") },
|
||||
{ 0xA064, F("SmartSolar MPPT 250|85 rev2") },
|
||||
{ 0xA065, F("SmartSolar MPPT 250|100 rev2") },
|
||||
{ 0xA066, F("BlueSolar MPPT 100|20") },
|
||||
{ 0xA067, F("BlueSolar MPPT 100|20 48V") },
|
||||
{ 0xA068, F("SmartSolar MPPT 250|60 rev2") },
|
||||
{ 0xA069, F("SmartSolar MPPT 250|70 rev2") },
|
||||
{ 0xA06A, F("SmartSolar MPPT 150|45 rev2") },
|
||||
{ 0xA06B, F("SmartSolar MPPT 150|60 rev2") },
|
||||
{ 0xA06C, F("SmartSolar MPPT 150|70 rev2") },
|
||||
{ 0xA06D, F("SmartSolar MPPT 150|85 rev3") },
|
||||
{ 0xA06E, F("SmartSolar MPPT 150|100 rev3") },
|
||||
{ 0xA06F, F("BlueSolar MPPT 150|45 rev2") },
|
||||
{ 0xA070, F("BlueSolar MPPT 150|60 rev2") },
|
||||
{ 0xA071, F("BlueSolar MPPT 150|70 rev2") },
|
||||
{ 0xA102, F("SmartSolar MPPT VE.Can 150|70") },
|
||||
{ 0xA103, F("SmartSolar MPPT VE.Can 150|45") },
|
||||
{ 0xA104, F("SmartSolar MPPT VE.Can 150|60") },
|
||||
{ 0xA105, F("SmartSolar MPPT VE.Can 150|85") },
|
||||
{ 0xA106, F("SmartSolar MPPT VE.Can 150|100") },
|
||||
{ 0xA107, F("SmartSolar MPPT VE.Can 250|45") },
|
||||
{ 0xA108, F("SmartSolar MPPT VE.Can 250|60") },
|
||||
{ 0xA109, F("SmartSolar MPPT VE.Can 250|80") },
|
||||
{ 0xA10A, F("SmartSolar MPPT VE.Can 250|85") },
|
||||
{ 0xA10B, F("SmartSolar MPPT VE.Can 250|100") },
|
||||
{ 0xA10C, F("SmartSolar MPPT VE.Can 150|70 rev2") },
|
||||
{ 0xA10D, F("SmartSolar MPPT VE.Can 150|85 rev2") },
|
||||
{ 0xA10E, F("SmartSolar MPPT VE.Can 150|100 rev2") },
|
||||
{ 0xA10F, F("BlueSolar MPPT VE.Can 150|100") },
|
||||
{ 0xA110, F("SmartSolar MPPT RS 450|100") },
|
||||
{ 0xA112, F("BlueSolar MPPT VE.Can 250|70") },
|
||||
{ 0xA113, F("BlueSolar MPPT VE.Can 250|100") },
|
||||
{ 0xA114, F("SmartSolar MPPT VE.Can 250|70 rev2") },
|
||||
{ 0xA115, F("SmartSolar MPPT VE.Can 250|100 rev2") },
|
||||
{ 0xA116, F("SmartSolar MPPT VE.Can 250|85 rev2") },
|
||||
{ 0xA381, F("BMV-712 Smart") },
|
||||
{ 0xA382, F("BMV-710H Smart") },
|
||||
{ 0xA383, F("BMV-712 Smart Rev2") },
|
||||
{ 0xA389, F("SmartShunt 500A/50mV") },
|
||||
{ 0xA38A, F("SmartShunt 1000A/50mV") },
|
||||
{ 0xA38B, F("SmartShunt 2000A/50mV") },
|
||||
{ 0xA3F0, F("SmartShunt 2000A/50mV" ) }
|
||||
};
|
||||
|
||||
switch(pid) {
|
||||
case 0x0300:
|
||||
strPID = "BlueSolar MPPT 70|15";
|
||||
break;
|
||||
case 0xA040:
|
||||
strPID = "BlueSolar MPPT 75|50";
|
||||
break;
|
||||
case 0xA041:
|
||||
strPID = "BlueSolar MPPT 150|35";
|
||||
break;
|
||||
case 0xA042:
|
||||
strPID = "BlueSolar MPPT 75|15";
|
||||
break;
|
||||
case 0xA043:
|
||||
strPID = "BlueSolar MPPT 100|15";
|
||||
break;
|
||||
case 0xA044:
|
||||
strPID = "BlueSolar MPPT 100|30";
|
||||
break;
|
||||
case 0xA045:
|
||||
strPID = "BlueSolar MPPT 100|50";
|
||||
break;
|
||||
case 0xA046:
|
||||
strPID = "BlueSolar MPPT 100|70";
|
||||
break;
|
||||
case 0xA047:
|
||||
strPID = "BlueSolar MPPT 150|100";
|
||||
break;
|
||||
case 0xA049:
|
||||
strPID = "BlueSolar MPPT 100|50 rev2";
|
||||
break;
|
||||
case 0xA04A:
|
||||
strPID = "BlueSolar MPPT 100|30 rev2";
|
||||
break;
|
||||
case 0xA04B:
|
||||
strPID = "BlueSolar MPPT 150|35 rev2";
|
||||
break;
|
||||
case 0XA04C:
|
||||
strPID = "BlueSolar MPPT 75|10";
|
||||
break;
|
||||
case 0XA04D:
|
||||
strPID = "BlueSolar MPPT 150|45";
|
||||
break;
|
||||
case 0XA04E:
|
||||
strPID = "BlueSolar MPPT 150|60";
|
||||
break;
|
||||
case 0XA04F:
|
||||
strPID = "BlueSolar MPPT 150|85";
|
||||
break;
|
||||
case 0XA050:
|
||||
strPID = "SmartSolar MPPT 250|100";
|
||||
break;
|
||||
case 0XA051:
|
||||
strPID = "SmartSolar MPPT 150|100";
|
||||
break;
|
||||
case 0XA052:
|
||||
strPID = "SmartSolar MPPT 150|85";
|
||||
break;
|
||||
case 0XA053:
|
||||
strPID = "SmartSolar MPPT 75|15";
|
||||
break;
|
||||
case 0XA054:
|
||||
strPID = "SmartSolar MPPT 75|10";
|
||||
break;
|
||||
case 0XA055:
|
||||
strPID = "SmartSolar MPPT 100|15";
|
||||
break;
|
||||
case 0XA056:
|
||||
strPID = "SmartSolar MPPT 100|30";
|
||||
break;
|
||||
case 0XA057:
|
||||
strPID = "SmartSolar MPPT 100|50";
|
||||
break;
|
||||
case 0XA058:
|
||||
strPID = "SmartSolar MPPT 150|35";
|
||||
break;
|
||||
case 0XA059:
|
||||
strPID = "SmartSolar MPPT 150|10 rev2";
|
||||
break;
|
||||
case 0XA05A:
|
||||
strPID = "SmartSolar MPPT 150|85 rev2";
|
||||
break;
|
||||
case 0XA05B:
|
||||
strPID = "SmartSolar MPPT 250|70";
|
||||
break;
|
||||
case 0XA05C:
|
||||
strPID = "SmartSolar MPPT 250|85";
|
||||
break;
|
||||
case 0XA05D:
|
||||
strPID = "SmartSolar MPPT 250|60";
|
||||
break;
|
||||
case 0XA05E:
|
||||
strPID = "SmartSolar MPPT 250|45";
|
||||
break;
|
||||
case 0XA05F:
|
||||
strPID = "SmartSolar MPPT 100|20";
|
||||
break;
|
||||
case 0XA060:
|
||||
strPID = "SmartSolar MPPT 100|20 48V";
|
||||
break;
|
||||
case 0XA061:
|
||||
strPID = "SmartSolar MPPT 150|45";
|
||||
break;
|
||||
case 0XA062:
|
||||
strPID = "SmartSolar MPPT 150|60";
|
||||
break;
|
||||
case 0XA063:
|
||||
strPID = "SmartSolar MPPT 150|70";
|
||||
break;
|
||||
case 0XA064:
|
||||
strPID = "SmartSolar MPPT 250|85 rev2";
|
||||
break;
|
||||
case 0XA065:
|
||||
strPID = "SmartSolar MPPT 250|100 rev2";
|
||||
break;
|
||||
case 0XA066:
|
||||
strPID = "BlueSolar MPPT 100|20";
|
||||
break;
|
||||
case 0XA067:
|
||||
strPID = "BlueSolar MPPT 100|20 48V";
|
||||
break;
|
||||
case 0XA068:
|
||||
strPID = "SmartSolar MPPT 250|60 rev2";
|
||||
break;
|
||||
case 0XA069:
|
||||
strPID = "SmartSolar MPPT 250|70 rev2";
|
||||
break;
|
||||
case 0XA06A:
|
||||
strPID = "SmartSolar MPPT 150|45 rev2";
|
||||
break;
|
||||
case 0XA06B:
|
||||
strPID = "SmartSolar MPPT 150|60 rev2";
|
||||
break;
|
||||
case 0XA06C:
|
||||
strPID = "SmartSolar MPPT 150|70 rev2";
|
||||
break;
|
||||
case 0XA06D:
|
||||
strPID = "SmartSolar MPPT 150|85 rev3";
|
||||
break;
|
||||
case 0XA06E:
|
||||
strPID = "SmartSolar MPPT 150|100 rev3";
|
||||
break;
|
||||
case 0XA06F:
|
||||
strPID = "BlueSolar MPPT 150|45 rev2";
|
||||
break;
|
||||
case 0XA070:
|
||||
strPID = "BlueSolar MPPT 150|60 rev2";
|
||||
break;
|
||||
case 0XA071:
|
||||
strPID = "BlueSolar MPPT 150|70 rev2";
|
||||
break;
|
||||
case 0XA102:
|
||||
strPID = "SmartSolar MPPT VE.Can 150|70";
|
||||
break;
|
||||
case 0XA103:
|
||||
strPID = "SmartSolar MPPT VE.Can 150|45";
|
||||
break;
|
||||
case 0XA104:
|
||||
strPID = "SmartSolar MPPT VE.Can 150|60";
|
||||
break;
|
||||
case 0XA105:
|
||||
strPID = "SmartSolar MPPT VE.Can 150|85";
|
||||
break;
|
||||
case 0XA106:
|
||||
strPID = "SmartSolar MPPT VE.Can 150|100";
|
||||
break;
|
||||
case 0XA107:
|
||||
strPID = "SmartSolar MPPT VE.Can 250|45";
|
||||
break;
|
||||
case 0XA108:
|
||||
strPID = "SmartSolar MPPT VE.Can 250|60";
|
||||
break;
|
||||
case 0XA109:
|
||||
strPID = "SmartSolar MPPT VE.Can 250|80";
|
||||
break;
|
||||
case 0XA10A:
|
||||
strPID = "SmartSolar MPPT VE.Can 250|85";
|
||||
break;
|
||||
case 0XA10B:
|
||||
strPID = "SmartSolar MPPT VE.Can 250|100";
|
||||
break;
|
||||
case 0XA10C:
|
||||
strPID = "SmartSolar MPPT VE.Can 150|70 rev2";
|
||||
break;
|
||||
case 0XA10D:
|
||||
strPID = "SmartSolar MPPT VE.Can 150|85 rev2";
|
||||
break;
|
||||
case 0XA10E:
|
||||
strPID = "SmartSolar MPPT VE.Can 150|100 rev2";
|
||||
break;
|
||||
case 0XA10F:
|
||||
strPID = "BlueSolar MPPT VE.Can 150|100";
|
||||
break;
|
||||
case 0XA110:
|
||||
strPID = "SmartSolar MPPT RS 450|100";
|
||||
break;
|
||||
case 0XA112:
|
||||
strPID = "BlueSolar MPPT VE.Can 250|70";
|
||||
break;
|
||||
case 0XA113:
|
||||
strPID = "BlueSolar MPPT VE.Can 250|100";
|
||||
break;
|
||||
case 0XA114:
|
||||
strPID = "SmartSolar MPPT VE.Can 250|70 rev2";
|
||||
break;
|
||||
case 0XA115:
|
||||
strPID = "SmartSolar MPPT VE.Can 250|100 rev2";
|
||||
break;
|
||||
case 0XA116:
|
||||
strPID = "SmartSolar MPPT VE.Can 250|85 rev2";
|
||||
break;
|
||||
case 0xA381:
|
||||
strPID = "BMV-712 Smart";
|
||||
break;
|
||||
case 0xA382:
|
||||
strPID = "BMV-710H Smart";
|
||||
break;
|
||||
case 0xA383:
|
||||
strPID = "BMV-712 Smart Rev2";
|
||||
break;
|
||||
case 0xA389:
|
||||
strPID = "SmartShunt 500A/50mV";
|
||||
break;
|
||||
case 0xA38A:
|
||||
strPID = "SmartShunt 1000A/50mV";
|
||||
break;
|
||||
case 0xA38B:
|
||||
strPID = "SmartShunt 2000A/50mV";
|
||||
break;
|
||||
case 0xA3F0:
|
||||
strPID = "SmartShunt 2000A/50mV" ;
|
||||
break;
|
||||
default:
|
||||
strPID = pid;
|
||||
}
|
||||
return strPID;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* getErrAsString
|
||||
* This function returns error state (ERR) as readable text.
|
||||
*/
|
||||
String VeDirectFrameHandler::getErrAsString(uint8_t err)
|
||||
{
|
||||
String strERR ="";
|
||||
|
||||
switch(err) {
|
||||
case 0:
|
||||
strERR = "No error";
|
||||
break;
|
||||
case 2:
|
||||
strERR = "Battery voltage too high";
|
||||
break;
|
||||
case 17:
|
||||
strERR = "Charger temperature too high";
|
||||
break;
|
||||
case 18:
|
||||
strERR = "Charger over current";
|
||||
break;
|
||||
case 19:
|
||||
strERR = "Charger current reversed";
|
||||
break;
|
||||
case 20:
|
||||
strERR = "Bulk time limit exceeded";
|
||||
break;
|
||||
case 21:
|
||||
strERR = "Current sensor issue(sensor bias/sensor broken)";
|
||||
break;
|
||||
case 26:
|
||||
strERR = "Terminals overheated";
|
||||
break;
|
||||
case 28:
|
||||
strERR = "Converter issue (dual converter models only)";
|
||||
break;
|
||||
case 33:
|
||||
strERR = "Input voltage too high (solar panel)";
|
||||
break;
|
||||
case 34:
|
||||
strERR = "Input current too high (solar panel)";
|
||||
break;
|
||||
case 38:
|
||||
strERR = "Input shutdown (due to excessive battery voltage)";
|
||||
break;
|
||||
case 39:
|
||||
strERR = "Input shutdown (due to current flow during off mode)";
|
||||
break;
|
||||
case 40:
|
||||
strERR = "Input";
|
||||
break;
|
||||
case 65:
|
||||
strERR = "Lost communication with one of devices";
|
||||
break;
|
||||
case 67:
|
||||
strERR = "Synchronisedcharging device configuration issue";
|
||||
break;
|
||||
case 68:
|
||||
strERR = "BMS connection lost";
|
||||
break;
|
||||
case 116:
|
||||
strERR = "Factory calibration data lost";
|
||||
break;
|
||||
case 117:
|
||||
strERR = "Invalid/incompatible firmware";
|
||||
break;
|
||||
case 118:
|
||||
strERR = "User settings invalid";
|
||||
break;
|
||||
default:
|
||||
strERR = err;
|
||||
}
|
||||
return strERR;
|
||||
return getAsString(values, PID);
|
||||
}
|
||||
|
||||
@ -13,45 +13,47 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#define VE_MAX_VALUE_LEN 33 // VE.Direct Protocol: max value size is 33 including /0
|
||||
#define VE_MAX_HEX_LEN 100 // Maximum size of hex frame - max payload 34 byte (=68 char) + safe buffer
|
||||
|
||||
typedef struct {
|
||||
uint16_t PID = 0; // product id
|
||||
char SER[VE_MAX_VALUE_LEN]; // serial number
|
||||
char FW[VE_MAX_VALUE_LEN]; // firmware release number
|
||||
int32_t P = 0; // battery output power in W (calculated)
|
||||
double V = 0; // battery voltage in V
|
||||
double I = 0; // battery current in A
|
||||
double E = 0; // efficiency in percent (calculated, moving average)
|
||||
} veStruct;
|
||||
|
||||
class VeDirectFrameHandler {
|
||||
public:
|
||||
VeDirectFrameHandler();
|
||||
void setVerboseLogging(bool verboseLogging);
|
||||
virtual void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging, uint16_t hwSerialPort);
|
||||
void loop(); // main loop to read ve.direct data
|
||||
unsigned long getLastUpdate(); // timestamp of last successful frame read
|
||||
bool isDataValid(veStruct frame); // return true if data valid and not outdated
|
||||
String getPidAsString(uint16_t pid); // product id as string
|
||||
String getErrAsString(uint8_t err); // errer state as string
|
||||
uint32_t getLastUpdate() const; // timestamp of last successful frame read
|
||||
|
||||
protected:
|
||||
void textRxEvent(char *, char *, veStruct& );
|
||||
|
||||
bool _verboseLogging;
|
||||
Print* _msgOut;
|
||||
uint32_t _lastUpdate;
|
||||
|
||||
typedef struct {
|
||||
uint16_t PID = 0; // product id
|
||||
char SER[VE_MAX_VALUE_LEN]; // serial number
|
||||
char FW[VE_MAX_VALUE_LEN]; // firmware release number
|
||||
double V = 0; // battery voltage in V
|
||||
double I = 0; // battery current in A
|
||||
double E = 0; // efficiency in percent (calculated, moving average)
|
||||
|
||||
String getPidAsString() const; // product id as string
|
||||
} veStruct;
|
||||
|
||||
bool textRxEvent(std::string const& who, char* name, char* value, veStruct& frame);
|
||||
bool isDataValid(veStruct const& frame) const; // return true if data valid and not outdated
|
||||
|
||||
template<typename T>
|
||||
static String const& getAsString(std::map<T, String> const& values, T val);
|
||||
|
||||
private:
|
||||
void setLastUpdate(); // set timestampt after successful frame read
|
||||
void dumpDebugBuffer();
|
||||
void rxData(uint8_t inbyte); // byte of serial data
|
||||
virtual void textRxEvent(char *, char *) = 0;
|
||||
virtual void frameEndEvent(bool) = 0; // copy temp struct to public struct
|
||||
virtual void frameValidEvent() = 0;
|
||||
int hexRxEvent(uint8_t);
|
||||
|
||||
std::unique_ptr<HardwareSerial> _vedirectSerial;
|
||||
|
||||
@ -1,25 +1,24 @@
|
||||
#include <Arduino.h>
|
||||
#include <map>
|
||||
#include "VeDirectMpptController.h"
|
||||
|
||||
VeDirectMpptController VeDirectMppt;
|
||||
|
||||
VeDirectMpptController::VeDirectMpptController()
|
||||
{
|
||||
}
|
||||
|
||||
void VeDirectMpptController::init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging)
|
||||
{
|
||||
VeDirectFrameHandler::init(rx, tx, msgOut, verboseLogging, 1);
|
||||
_spData = std::make_shared<veMpptStruct>();
|
||||
if (_verboseLogging) { _msgOut->println("Finished init MPPTController"); }
|
||||
}
|
||||
|
||||
bool VeDirectMpptController::isDataValid() {
|
||||
return VeDirectFrameHandler::isDataValid(veFrame);
|
||||
bool VeDirectMpptController::isDataValid() const {
|
||||
return VeDirectFrameHandler::isDataValid(*_spData);
|
||||
}
|
||||
|
||||
void VeDirectMpptController::textRxEvent(char * name, char * value) {
|
||||
if (_verboseLogging) { _msgOut->printf("[Victron MPPT] Received Text Event %s: Value: %s\r\n", name, value ); }
|
||||
VeDirectFrameHandler::textRxEvent(name, value, _tmpFrame);
|
||||
void VeDirectMpptController::textRxEvent(char* name, char* value)
|
||||
{
|
||||
if (VeDirectFrameHandler::textRxEvent("MPPT", name, value, _tmpFrame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(name, "LOAD") == 0) {
|
||||
if (strcmp(value, "ON") == 0)
|
||||
_tmpFrame.LOAD = true;
|
||||
@ -65,139 +64,114 @@ void VeDirectMpptController::textRxEvent(char * name, char * value) {
|
||||
}
|
||||
|
||||
/*
|
||||
* frameEndEvent
|
||||
* This function is called at the end of the received frame. If the checksum is valid, the temp buffer is read line by line.
|
||||
* If the name exists in the public buffer, the new value is copied to the public buffer. If not, a new name/value entry
|
||||
* is created in the public buffer.
|
||||
* frameValidEvent
|
||||
* This function is called at the end of the received frame.
|
||||
*/
|
||||
void VeDirectMpptController::frameEndEvent(bool valid) {
|
||||
if (valid) {
|
||||
_tmpFrame.P = _tmpFrame.V * _tmpFrame.I;
|
||||
void VeDirectMpptController::frameValidEvent() {
|
||||
_tmpFrame.P = _tmpFrame.V * _tmpFrame.I;
|
||||
|
||||
_tmpFrame.IPV = 0;
|
||||
if (_tmpFrame.VPV > 0) {
|
||||
_tmpFrame.IPV = _tmpFrame.PPV / _tmpFrame.VPV;
|
||||
}
|
||||
|
||||
_tmpFrame.E = 0;
|
||||
if ( _tmpFrame.PPV > 0) {
|
||||
_efficiency.addNumber(static_cast<double>(_tmpFrame.P * 100) / _tmpFrame.PPV);
|
||||
_tmpFrame.E = _efficiency.getAverage();
|
||||
}
|
||||
|
||||
veFrame = _tmpFrame;
|
||||
_tmpFrame = {};
|
||||
_lastUpdate = millis();
|
||||
_tmpFrame.IPV = 0;
|
||||
if (_tmpFrame.VPV > 0) {
|
||||
_tmpFrame.IPV = _tmpFrame.PPV / _tmpFrame.VPV;
|
||||
}
|
||||
|
||||
_tmpFrame.E = 0;
|
||||
if ( _tmpFrame.PPV > 0) {
|
||||
_efficiency.addNumber(static_cast<double>(_tmpFrame.P * 100) / _tmpFrame.PPV);
|
||||
_tmpFrame.E = _efficiency.getAverage();
|
||||
}
|
||||
|
||||
_spData = std::make_shared<veMpptStruct>(_tmpFrame);
|
||||
_tmpFrame = {};
|
||||
_lastUpdate = millis();
|
||||
}
|
||||
|
||||
/*
|
||||
* getCsAsString
|
||||
* This function returns the state of operations (CS) as readable text.
|
||||
*/
|
||||
String VeDirectMpptController::getCsAsString(uint8_t cs)
|
||||
String VeDirectMpptController::veMpptStruct::getCsAsString() const
|
||||
{
|
||||
String strCS ="";
|
||||
static const std::map<uint8_t, String> values = {
|
||||
{ 0, F("OFF") },
|
||||
{ 2, F("Fault") },
|
||||
{ 3, F("Bulk") },
|
||||
{ 4, F("Absorbtion") },
|
||||
{ 5, F("Float") },
|
||||
{ 7, F("Equalize (manual)") },
|
||||
{ 245, F("Starting-up") },
|
||||
{ 247, F("Auto equalize / Recondition") },
|
||||
{ 252, F("External Control") }
|
||||
};
|
||||
|
||||
switch(cs) {
|
||||
case 0:
|
||||
strCS = "OFF";
|
||||
break;
|
||||
case 2:
|
||||
strCS = "Fault";
|
||||
break;
|
||||
case 3:
|
||||
strCS = "Bulk";
|
||||
break;
|
||||
case 4:
|
||||
strCS = "Absorbtion";
|
||||
break;
|
||||
case 5:
|
||||
strCS = "Float";
|
||||
break;
|
||||
case 7:
|
||||
strCS = "Equalize (manual)";
|
||||
break;
|
||||
case 245:
|
||||
strCS = "Starting-up";
|
||||
break;
|
||||
case 247:
|
||||
strCS = "Auto equalize / Recondition";
|
||||
break;
|
||||
case 252:
|
||||
strCS = "External Control";
|
||||
break;
|
||||
default:
|
||||
strCS = cs;
|
||||
}
|
||||
return strCS;
|
||||
return getAsString(values, CS);
|
||||
}
|
||||
|
||||
/*
|
||||
* getMpptAsString
|
||||
* This function returns the state of MPPT (MPPT) as readable text.
|
||||
*/
|
||||
String VeDirectMpptController::getMpptAsString(uint8_t mppt)
|
||||
String VeDirectMpptController::veMpptStruct::getMpptAsString() const
|
||||
{
|
||||
String strMPPT ="";
|
||||
static const std::map<uint8_t, String> values = {
|
||||
{ 0, F("OFF") },
|
||||
{ 1, F("Voltage or current limited") },
|
||||
{ 2, F("MPP Tracker active") }
|
||||
};
|
||||
|
||||
switch(mppt) {
|
||||
case 0:
|
||||
strMPPT = "OFF";
|
||||
break;
|
||||
case 1:
|
||||
strMPPT = "Voltage or current limited";
|
||||
break;
|
||||
case 2:
|
||||
strMPPT = "MPP Tracker active";
|
||||
break;
|
||||
default:
|
||||
strMPPT = mppt;
|
||||
}
|
||||
return strMPPT;
|
||||
return getAsString(values, MPPT);
|
||||
}
|
||||
|
||||
/*
|
||||
* getErrAsString
|
||||
* This function returns error state (ERR) as readable text.
|
||||
*/
|
||||
String VeDirectMpptController::veMpptStruct::getErrAsString() const
|
||||
{
|
||||
static const std::map<uint8_t, String> values = {
|
||||
{ 0, F("No error") },
|
||||
{ 2, F("Battery voltage too high") },
|
||||
{ 17, F("Charger temperature too high") },
|
||||
{ 18, F("Charger over current") },
|
||||
{ 19, F("Charger current reversed") },
|
||||
{ 20, F("Bulk time limit exceeded") },
|
||||
{ 21, F("Current sensor issue(sensor bias/sensor broken)") },
|
||||
{ 26, F("Terminals overheated") },
|
||||
{ 28, F("Converter issue (dual converter models only)") },
|
||||
{ 33, F("Input voltage too high (solar panel)") },
|
||||
{ 34, F("Input current too high (solar panel)") },
|
||||
{ 38, F("Input shutdown (due to excessive battery voltage)") },
|
||||
{ 39, F("Input shutdown (due to current flow during off mode)") },
|
||||
{ 40, F("Input") },
|
||||
{ 65, F("Lost communication with one of devices") },
|
||||
{ 67, F("Synchronisedcharging device configuration issue") },
|
||||
{ 68, F("BMS connection lost") },
|
||||
{ 116, F("Factory calibration data lost") },
|
||||
{ 117, F("Invalid/incompatible firmware") },
|
||||
{ 118, F("User settings invalid") }
|
||||
};
|
||||
|
||||
return getAsString(values, ERR);
|
||||
}
|
||||
|
||||
/*
|
||||
* getOrAsString
|
||||
* This function returns the off reason (OR) as readable text.
|
||||
*/
|
||||
String VeDirectMpptController::getOrAsString(uint32_t offReason)
|
||||
String VeDirectMpptController::veMpptStruct::getOrAsString() const
|
||||
{
|
||||
String strOR ="";
|
||||
static const std::map<uint32_t, String> values = {
|
||||
{ 0x00000000, F("Not off") },
|
||||
{ 0x00000001, F("No input power") },
|
||||
{ 0x00000002, F("Switched off (power switch)") },
|
||||
{ 0x00000004, F("Switched off (device moderegister)") },
|
||||
{ 0x00000008, F("Remote input") },
|
||||
{ 0x00000010, F("Protection active") },
|
||||
{ 0x00000020, F("Paygo") },
|
||||
{ 0x00000040, F("BMS") },
|
||||
{ 0x00000080, F("Engine shutdown detection") },
|
||||
{ 0x00000100, F("Analysing input voltage") }
|
||||
};
|
||||
|
||||
switch(offReason) {
|
||||
case 0x00000000:
|
||||
strOR = "Not off";
|
||||
break;
|
||||
case 0x00000001:
|
||||
strOR = "No input power";
|
||||
break;
|
||||
case 0x00000002:
|
||||
strOR = "Switched off (power switch)";
|
||||
break;
|
||||
case 0x00000004:
|
||||
strOR = "Switched off (device moderegister)";
|
||||
break;
|
||||
case 0x00000008:
|
||||
strOR = "Remote input";
|
||||
break;
|
||||
case 0x00000010:
|
||||
strOR = "Protection active";
|
||||
break;
|
||||
case 0x00000020:
|
||||
strOR = "Paygo";
|
||||
break;
|
||||
case 0x00000040:
|
||||
strOR = "BMS";
|
||||
break;
|
||||
case 0x00000080:
|
||||
strOR = "Engine shutdown detection";
|
||||
break;
|
||||
case 0x00000100:
|
||||
strOR = "Analysing input voltage";
|
||||
break;
|
||||
default:
|
||||
strOR = offReason;
|
||||
}
|
||||
return strOR;
|
||||
return getAsString(values, OR);
|
||||
}
|
||||
|
||||
@ -37,17 +37,15 @@ private:
|
||||
|
||||
class VeDirectMpptController : public VeDirectFrameHandler {
|
||||
public:
|
||||
VeDirectMpptController();
|
||||
VeDirectMpptController() = default;
|
||||
|
||||
void init(int8_t rx, int8_t tx, Print* msgOut, bool verboseLogging);
|
||||
String getMpptAsString(uint8_t mppt); // state of mppt as string
|
||||
String getCsAsString(uint8_t cs); // current state as string
|
||||
String getOrAsString(uint32_t offReason); // off reason as string
|
||||
bool isDataValid(); // return true if data valid and not outdated
|
||||
bool isDataValid() const; // return true if data valid and not outdated
|
||||
|
||||
struct veMpptStruct : veStruct {
|
||||
uint8_t MPPT; // state of MPP tracker
|
||||
int32_t PPV; // panel power in W
|
||||
int32_t P; // battery output power in W (calculated)
|
||||
double VPV; // panel voltage in V
|
||||
double IPV; // panel current in A (calculated)
|
||||
bool LOAD; // virtual load output state (on if battery voltage reaches upper limit, off if battery reaches lower limit)
|
||||
@ -60,15 +58,20 @@ public:
|
||||
int32_t H21; // maximum power today W
|
||||
double H22; // yield yesterday kWh
|
||||
int32_t H23; // maximum power yesterday W
|
||||
|
||||
String getMpptAsString() const; // state of mppt as string
|
||||
String getCsAsString() const; // current state as string
|
||||
String getErrAsString() const; // error state as string
|
||||
String getOrAsString() const; // off reason as string
|
||||
};
|
||||
|
||||
veMpptStruct veFrame{};
|
||||
using spData_t = std::shared_ptr<veMpptStruct const>;
|
||||
spData_t getData() const { return _spData; }
|
||||
|
||||
private:
|
||||
void textRxEvent(char * name, char * value) final;
|
||||
void frameEndEvent(bool) final; // copy temp struct to public struct
|
||||
void textRxEvent(char* name, char* value) final;
|
||||
void frameValidEvent() final;
|
||||
spData_t _spData = nullptr;
|
||||
veMpptStruct _tmpFrame{}; // private struct for received name and value pairs
|
||||
MovingAverage<double, 5> _efficiency;
|
||||
};
|
||||
|
||||
extern VeDirectMpptController VeDirectMppt;
|
||||
@ -17,12 +17,13 @@ void VeDirectShuntController::init(int8_t rx, int8_t tx, Print* msgOut, bool ver
|
||||
|
||||
void VeDirectShuntController::textRxEvent(char* name, char* value)
|
||||
{
|
||||
VeDirectFrameHandler::textRxEvent(name, value, _tmpFrame);
|
||||
if (_verboseLogging) {
|
||||
_msgOut->printf("[Victron SmartShunt] Received Text Event %s: Value: %s\r\n", name, value );
|
||||
if (VeDirectFrameHandler::textRxEvent("SmartShunt", name, value, _tmpFrame)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(name, "T") == 0) {
|
||||
_tmpFrame.T = atoi(value);
|
||||
_tmpFrame.tempPresent = true;
|
||||
}
|
||||
else if (strcmp(name, "P") == 0) {
|
||||
_tmpFrame.P = atoi(value);
|
||||
@ -96,18 +97,16 @@ void VeDirectShuntController::textRxEvent(char* name, char* value)
|
||||
}
|
||||
|
||||
/*
|
||||
* frameEndEvent
|
||||
* This function is called at the end of the received frame. If the checksum is valid, the temp buffer is read line by line.
|
||||
* If the name exists in the public buffer, the new value is copied to the public buffer. If not, a new name/value entry
|
||||
* is created in the public buffer.
|
||||
* frameValidEvent
|
||||
* This function is called at the end of the received frame.
|
||||
*/
|
||||
void VeDirectShuntController::frameEndEvent(bool valid) {
|
||||
void VeDirectShuntController::frameValidEvent() {
|
||||
// other than in the MPPT controller, the SmartShunt seems to split all data
|
||||
// into two seperate messagesas. Thus we update veFrame only every second message
|
||||
// after a value for PID has been received
|
||||
if (valid && _tmpFrame.PID != 0) {
|
||||
veFrame = _tmpFrame;
|
||||
_tmpFrame = {};
|
||||
_lastUpdate = millis();
|
||||
}
|
||||
if (_tmpFrame.PID == 0) { return; }
|
||||
|
||||
veFrame = _tmpFrame;
|
||||
_tmpFrame = {};
|
||||
_lastUpdate = millis();
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ public:
|
||||
|
||||
struct veShuntStruct : veStruct {
|
||||
int32_t T; // Battery temperature
|
||||
bool tempPresent = false; // Battery temperature sensor is attached to the shunt
|
||||
int32_t P; // Instantaneous power
|
||||
int32_t CE; // Consumed Amp Hours
|
||||
int32_t SOC; // State-of-charge
|
||||
@ -41,7 +42,7 @@ public:
|
||||
|
||||
private:
|
||||
void textRxEvent(char * name, char * value) final;
|
||||
void frameEndEvent(bool) final; // copy temp struct to public struct
|
||||
void frameValidEvent() final;
|
||||
veShuntStruct _tmpFrame{}; // private struct for received name and value pairs
|
||||
};
|
||||
|
||||
|
||||
@ -208,12 +208,14 @@ void VictronSmartShuntStats::updateFrom(VeDirectShuntController::veShuntStruct c
|
||||
_SoC = shuntData.SOC / 10;
|
||||
_voltage = shuntData.V;
|
||||
_current = shuntData.I;
|
||||
_modelName = VeDirectShunt.getPidAsString(shuntData.PID);
|
||||
_modelName = shuntData.getPidAsString();
|
||||
_chargeCycles = shuntData.H4;
|
||||
_timeToGo = shuntData.TTG / 60;
|
||||
_chargedEnergy = shuntData.H18 / 100;
|
||||
_dischargedEnergy = shuntData.H17 / 100;
|
||||
_manufacturer = "Victron " + _modelName;
|
||||
_temperature = shuntData.T;
|
||||
_tempPresent = shuntData.tempPresent;
|
||||
|
||||
// shuntData.AR is a bitfield, so we need to check each bit individually
|
||||
_alarmLowVoltage = shuntData.AR & 1;
|
||||
@ -235,6 +237,9 @@ void VictronSmartShuntStats::getLiveViewData(JsonVariant& root) const {
|
||||
addLiveViewValue(root, "chargeCycles", _chargeCycles, "", 0);
|
||||
addLiveViewValue(root, "chargedEnergy", _chargedEnergy, "KWh", 1);
|
||||
addLiveViewValue(root, "dischargedEnergy", _dischargedEnergy, "KWh", 1);
|
||||
if (_tempPresent) {
|
||||
addLiveViewValue(root, "temperature", _temperature, "°C", 0);
|
||||
}
|
||||
|
||||
addLiveViewAlarm(root, "lowVoltage", _alarmLowVoltage);
|
||||
addLiveViewAlarm(root, "highVoltage", _alarmHighVoltage);
|
||||
|
||||
@ -190,6 +190,7 @@ bool ConfigurationClass::write()
|
||||
|
||||
JsonObject huawei = doc.createNestedObject("huawei");
|
||||
huawei["enabled"] = config.Huawei_Enabled;
|
||||
huawei["can_controller_frequency"] = config.Huawei_CAN_Controller_Frequency;
|
||||
huawei["auto_power_enabled"] = config.Huawei_Auto_Power_Enabled;
|
||||
huawei["voltage_limit"] = config.Huawei_Auto_Power_Voltage_Limit;
|
||||
huawei["enable_voltage_limit"] = config.Huawei_Auto_Power_Enable_Voltage_Limit;
|
||||
@ -413,6 +414,7 @@ bool ConfigurationClass::read()
|
||||
|
||||
JsonObject huawei = doc["huawei"];
|
||||
config.Huawei_Enabled = huawei["enabled"] | HUAWEI_ENABLED;
|
||||
config.Huawei_CAN_Controller_Frequency = huawei["can_controller_frequency"] | HUAWEI_CAN_CONTROLLER_FREQUENCY;
|
||||
config.Huawei_Auto_Power_Enabled = huawei["auto_power_enabled"] | false;
|
||||
config.Huawei_Auto_Power_Voltage_Limit = huawei["voltage_limit"] | HUAWEI_AUTO_POWER_VOLTAGE_LIMIT;
|
||||
config.Huawei_Auto_Power_Enable_Voltage_Limit = huawei["enable_voltage_limit"] | HUAWEI_AUTO_POWER_ENABLE_VOLTAGE_LIMIT;
|
||||
|
||||
@ -30,7 +30,8 @@ void HuaweiCanCommunicationTask(void* parameter) {
|
||||
}
|
||||
}
|
||||
|
||||
bool HuaweiCanCommClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk, uint8_t huawei_irq, uint8_t huawei_cs) {
|
||||
bool HuaweiCanCommClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huawei_clk,
|
||||
uint8_t huawei_irq, uint8_t huawei_cs, uint32_t frequency) {
|
||||
SPI = new SPIClass(HSPI);
|
||||
SPI->begin(huawei_clk, huawei_miso, huawei_mosi, huawei_cs);
|
||||
pinMode(huawei_cs, OUTPUT);
|
||||
@ -39,8 +40,14 @@ bool HuaweiCanCommClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t
|
||||
pinMode(huawei_irq, INPUT_PULLUP);
|
||||
_huaweiIrq = huawei_irq;
|
||||
|
||||
auto mcp_frequency = MCP_8MHZ;
|
||||
if (16000000UL == frequency) { mcp_frequency = MCP_16MHZ; }
|
||||
else if (8000000UL != frequency) {
|
||||
MessageOutput.printf("Huawei CAN: unknown frequency %d Hz, using 8 MHz\r\n", mcp_frequency);
|
||||
}
|
||||
|
||||
_CAN = new MCP_CAN(SPI, huawei_cs);
|
||||
if (!_CAN->begin(MCP_STDEXT, CAN_125KBPS, MCP_8MHZ) == CAN_OK) {
|
||||
if (!_CAN->begin(MCP_STDEXT, CAN_125KBPS, mcp_frequency) == CAN_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -198,7 +205,7 @@ void HuaweiCanClass::init(uint8_t huawei_miso, uint8_t huawei_mosi, uint8_t huaw
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HuaweiCanComm.init(huawei_miso, huawei_mosi, huawei_clk, huawei_irq, huawei_cs)) {
|
||||
if (!HuaweiCanComm.init(huawei_miso, huawei_mosi, huawei_clk, huawei_irq, huawei_cs, config.Huawei_CAN_Controller_Frequency)) {
|
||||
MessageOutput.println("[HuaweiCanClass::init] Error Initializing Huawei CAN communication...");
|
||||
return;
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "VictronMppt.h"
|
||||
|
||||
MqttHandleVedirectHassClass MqttHandleVedirectHass;
|
||||
|
||||
@ -50,7 +51,7 @@ void MqttHandleVedirectHassClass::publishConfig()
|
||||
return;
|
||||
}
|
||||
// ensure data is revieved from victron
|
||||
if (!VeDirectMppt.isDataValid()) {
|
||||
if (!VictronMppt.isDataValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -67,9 +68,12 @@ void MqttHandleVedirectHassClass::publishConfig()
|
||||
// battery info
|
||||
publishSensor("Battery voltage", NULL, "V", "voltage", "measurement", "V");
|
||||
publishSensor("Battery current", NULL, "I", "current", "measurement", "A");
|
||||
|
||||
publishSensor("Battery power (calculated)", NULL, "P", "power", "measurement", "W");
|
||||
publishSensor("Battery efficiency (calculated)", NULL, "E", "efficiency", "measurement", "%");
|
||||
|
||||
// panel info
|
||||
publishSensor("Panel voltage", NULL, "VPV", "voltage", "measurement", "V");
|
||||
publishSensor("Panel current (calculated)", NULL, "IPV", "current", "measurement", "A");
|
||||
publishSensor("Panel power", NULL, "PPV", "power", "measurement", "W");
|
||||
publishSensor("Panel yield total", NULL, "H19", "energy", "total_increasing", "kWh");
|
||||
publishSensor("Panel yield today", NULL, "H20", "energy", "total", "kWh");
|
||||
@ -82,7 +86,7 @@ void MqttHandleVedirectHassClass::publishConfig()
|
||||
|
||||
void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char* icon, const char* subTopic, const char* deviceClass, const char* stateClass, const char* unitOfMeasurement )
|
||||
{
|
||||
String serial = VeDirectMppt.veFrame.SER;
|
||||
String serial = VictronMppt.getData()->SER;
|
||||
|
||||
String sensorId = caption;
|
||||
sensorId.replace(" ", "_");
|
||||
@ -94,9 +98,9 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char*
|
||||
String configTopic = "sensor/dtu_victron_" + serial
|
||||
+ "/" + sensorId
|
||||
+ "/config";
|
||||
|
||||
|
||||
String statTopic = MqttSettings.getPrefix() + "victron/";
|
||||
statTopic.concat(VeDirectMppt.veFrame.SER);
|
||||
statTopic.concat(serial);
|
||||
statTopic.concat("/");
|
||||
statTopic.concat(subTopic);
|
||||
|
||||
@ -133,7 +137,7 @@ void MqttHandleVedirectHassClass::publishSensor(const char* caption, const char*
|
||||
}
|
||||
void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const char* icon, const char* subTopic, const char* payload_on, const char* payload_off)
|
||||
{
|
||||
String serial = VeDirectMppt.veFrame.SER;
|
||||
String serial = VictronMppt.getData()->SER;
|
||||
|
||||
String sensorId = caption;
|
||||
sensorId.replace(" ", "_");
|
||||
@ -147,7 +151,7 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const
|
||||
+ "/config";
|
||||
|
||||
String statTopic = MqttSettings.getPrefix() + "victron/";
|
||||
statTopic.concat(VeDirectMppt.veFrame.SER);
|
||||
statTopic.concat(serial);
|
||||
statTopic.concat("/");
|
||||
statTopic.concat(subTopic);
|
||||
|
||||
@ -172,12 +176,13 @@ void MqttHandleVedirectHassClass::publishBinarySensor(const char* caption, const
|
||||
|
||||
void MqttHandleVedirectHassClass::createDeviceInfo(JsonObject& object)
|
||||
{
|
||||
String serial = VeDirectMppt.veFrame.SER;
|
||||
auto spMpptData = VictronMppt.getData();
|
||||
String serial = spMpptData->SER;
|
||||
object[F("name")] = "Victron(" + serial + ")";
|
||||
object[F("ids")] = serial;
|
||||
object[F("cu")] = String(F("http://")) + NetworkSettings.localIP().toString();
|
||||
object[F("mf")] = F("OpenDTU");
|
||||
object[F("mdl")] = VeDirectMppt.getPidAsString(VeDirectMppt.veFrame.PID);
|
||||
object[F("mdl")] = spMpptData->getPidAsString();
|
||||
object[F("sw")] = AUTO_GIT_HASH;
|
||||
}
|
||||
|
||||
@ -186,4 +191,4 @@ void MqttHandleVedirectHassClass::publish(const String& subtopic, const String&
|
||||
String topic = Configuration.get().Mqtt_Hass_Topic;
|
||||
topic += subtopic;
|
||||
MqttSettings.publishGeneric(topic.c_str(), payload.c_str(), Configuration.get().Mqtt_Hass_Retain);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Helge Erbe and others
|
||||
*/
|
||||
#include "VeDirectMpptController.h"
|
||||
#include "VictronMppt.h"
|
||||
#include "MqttHandleVedirect.h"
|
||||
#include "MqttSettings.h"
|
||||
#include "MessageOutput.h"
|
||||
@ -29,7 +29,7 @@ void MqttHandleVedirectClass::loop()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!VeDirectMppt.isDataValid()) {
|
||||
if (!VictronMppt.isDataValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -50,81 +50,82 @@ void MqttHandleVedirectClass::loop()
|
||||
}
|
||||
#endif
|
||||
|
||||
auto spMpptData = VictronMppt.getData();
|
||||
String value;
|
||||
String topic = "victron/";
|
||||
topic.concat(VeDirectMppt.veFrame.SER);
|
||||
topic.concat(spMpptData->SER);
|
||||
topic.concat("/");
|
||||
|
||||
if (_PublishFull || VeDirectMppt.veFrame.PID != _kvFrame.PID)
|
||||
MqttSettings.publish(topic + "PID", VeDirectMppt.getPidAsString(VeDirectMppt.veFrame.PID));
|
||||
if (_PublishFull || strcmp(VeDirectMppt.veFrame.SER, _kvFrame.SER) != 0)
|
||||
MqttSettings.publish(topic + "SER", VeDirectMppt.veFrame.SER );
|
||||
if (_PublishFull || strcmp(VeDirectMppt.veFrame.FW, _kvFrame.FW) != 0)
|
||||
MqttSettings.publish(topic + "FW", VeDirectMppt.veFrame.FW);
|
||||
if (_PublishFull || VeDirectMppt.veFrame.LOAD != _kvFrame.LOAD)
|
||||
MqttSettings.publish(topic + "LOAD", VeDirectMppt.veFrame.LOAD == true ? "ON": "OFF");
|
||||
if (_PublishFull || VeDirectMppt.veFrame.CS != _kvFrame.CS)
|
||||
MqttSettings.publish(topic + "CS", VeDirectMppt.getCsAsString(VeDirectMppt.veFrame.CS));
|
||||
if (_PublishFull || VeDirectMppt.veFrame.ERR != _kvFrame.ERR)
|
||||
MqttSettings.publish(topic + "ERR", VeDirectMppt.getErrAsString(VeDirectMppt.veFrame.ERR));
|
||||
if (_PublishFull || VeDirectMppt.veFrame.OR != _kvFrame.OR)
|
||||
MqttSettings.publish(topic + "OR", VeDirectMppt.getOrAsString(VeDirectMppt.veFrame.OR));
|
||||
if (_PublishFull || VeDirectMppt.veFrame.MPPT != _kvFrame.MPPT)
|
||||
MqttSettings.publish(topic + "MPPT", VeDirectMppt.getMpptAsString(VeDirectMppt.veFrame.MPPT));
|
||||
if (_PublishFull || VeDirectMppt.veFrame.HSDS != _kvFrame.HSDS) {
|
||||
value = VeDirectMppt.veFrame.HSDS;
|
||||
if (_PublishFull || spMpptData->PID != _kvFrame.PID)
|
||||
MqttSettings.publish(topic + "PID", spMpptData->getPidAsString());
|
||||
if (_PublishFull || strcmp(spMpptData->SER, _kvFrame.SER) != 0)
|
||||
MqttSettings.publish(topic + "SER", spMpptData->SER );
|
||||
if (_PublishFull || strcmp(spMpptData->FW, _kvFrame.FW) != 0)
|
||||
MqttSettings.publish(topic + "FW", spMpptData->FW);
|
||||
if (_PublishFull || spMpptData->LOAD != _kvFrame.LOAD)
|
||||
MqttSettings.publish(topic + "LOAD", spMpptData->LOAD == true ? "ON": "OFF");
|
||||
if (_PublishFull || spMpptData->CS != _kvFrame.CS)
|
||||
MqttSettings.publish(topic + "CS", spMpptData->getCsAsString());
|
||||
if (_PublishFull || spMpptData->ERR != _kvFrame.ERR)
|
||||
MqttSettings.publish(topic + "ERR", spMpptData->getErrAsString());
|
||||
if (_PublishFull || spMpptData->OR != _kvFrame.OR)
|
||||
MqttSettings.publish(topic + "OR", spMpptData->getOrAsString());
|
||||
if (_PublishFull || spMpptData->MPPT != _kvFrame.MPPT)
|
||||
MqttSettings.publish(topic + "MPPT", spMpptData->getMpptAsString());
|
||||
if (_PublishFull || spMpptData->HSDS != _kvFrame.HSDS) {
|
||||
value = spMpptData->HSDS;
|
||||
MqttSettings.publish(topic + "HSDS", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.V != _kvFrame.V) {
|
||||
value = VeDirectMppt.veFrame.V;
|
||||
if (_PublishFull || spMpptData->V != _kvFrame.V) {
|
||||
value = spMpptData->V;
|
||||
MqttSettings.publish(topic + "V", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.I != _kvFrame.I) {
|
||||
value = VeDirectMppt.veFrame.I;
|
||||
if (_PublishFull || spMpptData->I != _kvFrame.I) {
|
||||
value = spMpptData->I;
|
||||
MqttSettings.publish(topic + "I", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.P != _kvFrame.P) {
|
||||
value = VeDirectMppt.veFrame.P;
|
||||
if (_PublishFull || spMpptData->P != _kvFrame.P) {
|
||||
value = spMpptData->P;
|
||||
MqttSettings.publish(topic + "P", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.VPV != _kvFrame.VPV) {
|
||||
value = VeDirectMppt.veFrame.VPV;
|
||||
if (_PublishFull || spMpptData->VPV != _kvFrame.VPV) {
|
||||
value = spMpptData->VPV;
|
||||
MqttSettings.publish(topic + "VPV", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.IPV != _kvFrame.IPV) {
|
||||
value = VeDirectMppt.veFrame.IPV;
|
||||
if (_PublishFull || spMpptData->IPV != _kvFrame.IPV) {
|
||||
value = spMpptData->IPV;
|
||||
MqttSettings.publish(topic + "IPV", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.PPV != _kvFrame.PPV) {
|
||||
value = VeDirectMppt.veFrame.PPV;
|
||||
if (_PublishFull || spMpptData->PPV != _kvFrame.PPV) {
|
||||
value = spMpptData->PPV;
|
||||
MqttSettings.publish(topic + "PPV", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.E != _kvFrame.E) {
|
||||
value = VeDirectMppt.veFrame.E;
|
||||
if (_PublishFull || spMpptData->E != _kvFrame.E) {
|
||||
value = spMpptData->E;
|
||||
MqttSettings.publish(topic + "E", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.H19 != _kvFrame.H19) {
|
||||
value = VeDirectMppt.veFrame.H19;
|
||||
if (_PublishFull || spMpptData->H19 != _kvFrame.H19) {
|
||||
value = spMpptData->H19;
|
||||
MqttSettings.publish(topic + "H19", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.H20 != _kvFrame.H20) {
|
||||
value = VeDirectMppt.veFrame.H20;
|
||||
if (_PublishFull || spMpptData->H20 != _kvFrame.H20) {
|
||||
value = spMpptData->H20;
|
||||
MqttSettings.publish(topic + "H20", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.H21 != _kvFrame.H21) {
|
||||
value = VeDirectMppt.veFrame.H21;
|
||||
if (_PublishFull || spMpptData->H21 != _kvFrame.H21) {
|
||||
value = spMpptData->H21;
|
||||
MqttSettings.publish(topic + "H21", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.H22 != _kvFrame.H22) {
|
||||
value = VeDirectMppt.veFrame.H22;
|
||||
if (_PublishFull || spMpptData->H22 != _kvFrame.H22) {
|
||||
value = spMpptData->H22;
|
||||
MqttSettings.publish(topic + "H22", value);
|
||||
}
|
||||
if (_PublishFull || VeDirectMppt.veFrame.H23 != _kvFrame.H23) {
|
||||
value = VeDirectMppt.veFrame.H23;
|
||||
if (_PublishFull || spMpptData->H23 != _kvFrame.H23) {
|
||||
value = spMpptData->H23;
|
||||
MqttSettings.publish(topic + "H23", value);
|
||||
}
|
||||
if (!_PublishFull) {
|
||||
_kvFrame= VeDirectMppt.veFrame;
|
||||
_kvFrame = *spMpptData;
|
||||
}
|
||||
|
||||
// now calculate next points of time to publish
|
||||
|
||||
@ -305,11 +305,6 @@ bool PinMappingClass::isValidEthConfig()
|
||||
return _pinMapping.eth_enabled;
|
||||
}
|
||||
|
||||
bool PinMappingClass::isValidVictronConfig()
|
||||
{
|
||||
return _pinMapping.victron_rx >= 0;
|
||||
}
|
||||
|
||||
bool PinMappingClass::isValidHuaweiConfig()
|
||||
{
|
||||
return _pinMapping.huawei_miso >= 0
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
#include "MqttSettings.h"
|
||||
#include "NetworkSettings.h"
|
||||
#include "Huawei_can.h"
|
||||
#include <VeDirectMpptController.h>
|
||||
#include <VictronMppt.h>
|
||||
#include "MessageOutput.h"
|
||||
#include <ctime>
|
||||
#include <cmath>
|
||||
@ -364,14 +364,12 @@ int32_t PowerLimiterClass::inverterPowerDcToAc(std::shared_ptr<InverterAbstract>
|
||||
*/
|
||||
void PowerLimiterClass::unconditionalSolarPassthrough(std::shared_ptr<InverterAbstract> inverter)
|
||||
{
|
||||
CONFIG_T& config = Configuration.get();
|
||||
|
||||
if (!config.Vedirect_Enabled || !VeDirectMppt.isDataValid()) {
|
||||
if (!VictronMppt.isDataValid()) {
|
||||
shutdown(Status::NoVeDirect);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t solarPower = VeDirectMppt.veFrame.V * VeDirectMppt.veFrame.I;
|
||||
int32_t solarPower = VictronMppt.getPowerOutputWatts();
|
||||
setNewPowerLimit(inverter, inverterPowerDcToAc(inverter, solarPower));
|
||||
announceStatus(Status::UnconditionalSolarPassthrough);
|
||||
}
|
||||
@ -406,12 +404,11 @@ bool PowerLimiterClass::canUseDirectSolarPower()
|
||||
|
||||
if (!config.PowerLimiter_SolarPassThroughEnabled
|
||||
|| isBelowStopThreshold()
|
||||
|| !config.Vedirect_Enabled
|
||||
|| !VeDirectMppt.isDataValid()) {
|
||||
|| !VictronMppt.isDataValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return VeDirectMppt.veFrame.PPV >= 20; // enough power?
|
||||
return VictronMppt.getPowerOutputWatts() >= 20; // enough power?
|
||||
}
|
||||
|
||||
|
||||
@ -576,7 +573,7 @@ int32_t PowerLimiterClass::getSolarChargePower()
|
||||
return 0;
|
||||
}
|
||||
|
||||
return VeDirectMppt.veFrame.V * VeDirectMppt.veFrame.I;
|
||||
return VictronMppt.getPowerOutputWatts();
|
||||
}
|
||||
|
||||
float PowerLimiterClass::getLoadCorrectedVoltage()
|
||||
|
||||
@ -134,14 +134,17 @@ void PowerMeterClass::loop()
|
||||
CONFIG_T const& config = Configuration.get();
|
||||
_verboseLogging = config.PowerMeter_VerboseLogging;
|
||||
|
||||
if (config.PowerMeter_Enabled && config.PowerMeter_Source == SOURCE_SML) {
|
||||
if (!config.PowerMeter_Enabled) { return; }
|
||||
|
||||
if (config.PowerMeter_Source == SOURCE_SML) {
|
||||
if (!smlReadLoop()) {
|
||||
return;
|
||||
} else {
|
||||
_lastPowerMeterUpdate = millis();
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.PowerMeter_Enabled
|
||||
|| (millis() - _lastPowerMeterCheck) < (config.PowerMeter_Interval * 1000)) {
|
||||
if ((millis() - _lastPowerMeterCheck) < (config.PowerMeter_Interval * 1000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
129
src/VictronMppt.cpp
Normal file
129
src/VictronMppt.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "VictronMppt.h"
|
||||
#include "Configuration.h"
|
||||
#include "PinMapping.h"
|
||||
#include "MessageOutput.h"
|
||||
|
||||
VictronMpptClass VictronMppt;
|
||||
|
||||
void VictronMpptClass::init()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
_controllers.clear();
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
if (!config.Vedirect_Enabled) { return; }
|
||||
|
||||
const PinMapping_t& pin = PinMapping.get();
|
||||
int8_t rx = pin.victron_rx;
|
||||
int8_t tx = pin.victron_tx;
|
||||
|
||||
MessageOutput.printf("[VictronMppt] rx = %d, tx = %d\r\n", rx, tx);
|
||||
|
||||
if (rx < 0) {
|
||||
MessageOutput.println(F("[VictronMppt] invalid pin config"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto upController = std::make_unique<VeDirectMpptController>();
|
||||
upController->init(rx, tx, &MessageOutput, config.Vedirect_VerboseLogging);
|
||||
_controllers.push_back(std::move(upController));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
VeDirectMpptController::spData_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::make_shared<VeDirectMpptController::veMpptStruct>();
|
||||
}
|
||||
|
||||
return _controllers[idx]->getData();
|
||||
}
|
||||
|
||||
int32_t VictronMpptClass::getPowerOutputWatts() const
|
||||
{
|
||||
int32_t sum = 0;
|
||||
|
||||
for (const auto& upController : _controllers) {
|
||||
sum += upController->getData()->P;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
int32_t VictronMpptClass::getPanelPowerWatts() const
|
||||
{
|
||||
int32_t sum = 0;
|
||||
|
||||
for (const auto& upController : _controllers) {
|
||||
sum += upController->getData()->PPV;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
double VictronMpptClass::getYieldTotal() const
|
||||
{
|
||||
double sum = 0;
|
||||
|
||||
for (const auto& upController : _controllers) {
|
||||
sum += upController->getData()->H19;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
double VictronMpptClass::getYieldDay() const
|
||||
{
|
||||
double sum = 0;
|
||||
|
||||
for (const auto& upController : _controllers) {
|
||||
sum += upController->getData()->H20;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
@ -190,6 +190,7 @@ void WebApiHuaweiClass::onAdminGet(AsyncWebServerRequest* request)
|
||||
const CONFIG_T& config = Configuration.get();
|
||||
|
||||
root[F("enabled")] = config.Huawei_Enabled;
|
||||
root[F("can_controller_frequency")] = config.Huawei_CAN_Controller_Frequency;
|
||||
root[F("auto_power_enabled")] = config.Huawei_Auto_Power_Enabled;
|
||||
root[F("voltage_limit")] = static_cast<int>(config.Huawei_Auto_Power_Voltage_Limit * 100) / 100.0;
|
||||
root[F("enable_voltage_limit")] = static_cast<int>(config.Huawei_Auto_Power_Enable_Voltage_Limit * 100) / 100.0;
|
||||
@ -240,6 +241,7 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
}
|
||||
|
||||
if (!(root.containsKey("enabled")) ||
|
||||
!(root.containsKey("can_controller_frequency")) ||
|
||||
!(root.containsKey("auto_power_enabled")) ||
|
||||
!(root.containsKey("voltage_limit")) ||
|
||||
!(root.containsKey("lower_power_limit")) ||
|
||||
@ -253,6 +255,7 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
|
||||
CONFIG_T& config = Configuration.get();
|
||||
config.Huawei_Enabled = root[F("enabled")].as<bool>();
|
||||
config.Huawei_CAN_Controller_Frequency = root[F("can_controller_frequency")].as<uint32_t>();
|
||||
config.Huawei_Auto_Power_Enabled = root[F("auto_power_enabled")].as<bool>();
|
||||
config.Huawei_Auto_Power_Voltage_Limit = root[F("voltage_limit")].as<float>();
|
||||
config.Huawei_Auto_Power_Enable_Voltage_Limit = root[F("enable_voltage_limit")].as<float>();
|
||||
@ -267,6 +270,14 @@ void WebApiHuaweiClass::onAdminPost(AsyncWebServerRequest* request)
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
|
||||
// TODO(schlimmchen): HuaweiCan has no real concept of the fact that the
|
||||
// config might change. at least not regarding CAN parameters. until that
|
||||
// changes, the ESP must restart for configuration changes to take effect.
|
||||
yield();
|
||||
delay(1000);
|
||||
yield();
|
||||
ESP.restart();
|
||||
|
||||
const PinMapping_t& pin = PinMapping.get();
|
||||
// Properly turn this on
|
||||
if (config.Huawei_Enabled) {
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* Copyright (C) 2022 Thomas Basler and others
|
||||
*/
|
||||
#include "WebApi_vedirect.h"
|
||||
#include "VeDirectMpptController.h"
|
||||
#include "VictronMppt.h"
|
||||
#include "ArduinoJson.h"
|
||||
#include "AsyncJson.h"
|
||||
#include "Configuration.h"
|
||||
@ -117,7 +117,7 @@ void WebApiVedirectClass::onVedirectAdminPost(AsyncWebServerRequest* request)
|
||||
config.Vedirect_UpdatesOnly = root[F("vedirect_updatesonly")].as<bool>();
|
||||
Configuration.write();
|
||||
|
||||
VeDirectMppt.setVerboseLogging(config.Vedirect_VerboseLogging);
|
||||
VictronMppt.init();
|
||||
|
||||
retMsg[F("type")] = F("success");
|
||||
retMsg[F("message")] = F("Settings saved!");
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
#include "Battery.h"
|
||||
#include "Huawei_can.h"
|
||||
#include "PowerMeter.h"
|
||||
#include "VeDirectMpptController.h"
|
||||
#include "VictronMppt.h"
|
||||
#include "defaults.h"
|
||||
#include <AsyncJson.h>
|
||||
|
||||
@ -67,20 +67,14 @@ void WebApiWsLiveClass::loop()
|
||||
|
||||
try {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
DynamicJsonDocument root(4096 * INV_MAX_COUNT);
|
||||
DynamicJsonDocument root(4200 * INV_MAX_COUNT); // TODO(helge) check if this calculation is correct
|
||||
JsonVariant var = root;
|
||||
generateJsonResponse(var);
|
||||
|
||||
|
||||
String buffer;
|
||||
// free JsonDocument as soon as possible
|
||||
{
|
||||
DynamicJsonDocument root(4096 * INV_MAX_COUNT); // TODO(helge) check if this calculation is correct
|
||||
JsonVariant var = root;
|
||||
generateJsonResponse(var);
|
||||
serializeJson(root, buffer);
|
||||
}
|
||||
|
||||
if (buffer) {
|
||||
serializeJson(root, buffer);
|
||||
|
||||
if (Configuration.get().Security_AllowReadonly) {
|
||||
_ws.setAuthentication("", "");
|
||||
} else {
|
||||
@ -191,10 +185,11 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
JsonObject vedirectObj = root.createNestedObject("vedirect");
|
||||
vedirectObj[F("enabled")] = Configuration.get().Vedirect_Enabled;
|
||||
JsonObject totalVeObj = vedirectObj.createNestedObject("total");
|
||||
addTotalField(totalVeObj, "Power", VeDirectMppt.veFrame.PPV, "W", 1);
|
||||
addTotalField(totalVeObj, "YieldDay", VeDirectMppt.veFrame.H20 * 1000, "Wh", 0);
|
||||
addTotalField(totalVeObj, "YieldTotal", VeDirectMppt.veFrame.H19, "kWh", 2);
|
||||
|
||||
|
||||
addTotalField(totalVeObj, "Power", VictronMppt.getPanelPowerWatts(), "W", 1);
|
||||
addTotalField(totalVeObj, "YieldDay", VictronMppt.getYieldDay() * 1000, "Wh", 0);
|
||||
addTotalField(totalVeObj, "YieldTotal", VictronMppt.getYieldTotal(), "kWh", 2);
|
||||
|
||||
JsonObject huaweiObj = root.createNestedObject("huawei");
|
||||
huaweiObj[F("enabled")] = Configuration.get().Huawei_Enabled;
|
||||
const RectifierParameters_t * rp = HuaweiCan.get();
|
||||
@ -251,7 +246,7 @@ void WebApiWsLiveClass::onLivedataStatus(AsyncWebServerRequest* request)
|
||||
|
||||
try {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096 * INV_MAX_COUNT);
|
||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 4200 * INV_MAX_COUNT);
|
||||
JsonVariant root = response->getRoot();
|
||||
|
||||
generateJsonResponse(root);
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
#include "WebApi.h"
|
||||
#include "defaults.h"
|
||||
#include "PowerLimiter.h"
|
||||
#include "VictronMppt.h"
|
||||
|
||||
WebApiWsVedirectLiveClass::WebApiWsVedirectLiveClass()
|
||||
: _ws("/vedirectlivedata")
|
||||
@ -44,18 +45,14 @@ void WebApiWsVedirectLiveClass::loop()
|
||||
return;
|
||||
}
|
||||
|
||||
if (millis() - _lastVedirectUpdateCheck < 1000) {
|
||||
return;
|
||||
}
|
||||
_lastVedirectUpdateCheck = millis();
|
||||
|
||||
uint32_t maxTimeStamp = 0;
|
||||
if (VeDirectMppt.getLastUpdate() > maxTimeStamp) {
|
||||
maxTimeStamp = VeDirectMppt.getLastUpdate();
|
||||
}
|
||||
// we assume this loop to be running at least twice for every
|
||||
// update from a VE.Direct MPPT data producer, so _dataAgeMillis
|
||||
// acutally grows in between updates.
|
||||
auto lastDataAgeMillis = _dataAgeMillis;
|
||||
_dataAgeMillis = VictronMppt.getDataAgeMillis();
|
||||
|
||||
// Update on ve.direct change or at least after 10 seconds
|
||||
if (millis() - _lastWsPublish > (10 * 1000) || (maxTimeStamp != _newestVedirectTimestamp)) {
|
||||
if (millis() - _lastWsPublish > (10 * 1000) || lastDataAgeMillis > _dataAgeMillis) {
|
||||
|
||||
try {
|
||||
String buffer;
|
||||
@ -87,57 +84,59 @@ void WebApiWsVedirectLiveClass::loop()
|
||||
|
||||
void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
{
|
||||
auto spMpptData = VictronMppt.getData();
|
||||
|
||||
// device info
|
||||
root["device"]["data_age"] = (millis() - VeDirectMppt.getLastUpdate() ) / 1000;
|
||||
root["device"]["age_critical"] = !VeDirectMppt.isDataValid();
|
||||
root["device"]["PID"] = VeDirectMppt.getPidAsString(VeDirectMppt.veFrame.PID);
|
||||
root["device"]["SER"] = VeDirectMppt.veFrame.SER;
|
||||
root["device"]["FW"] = VeDirectMppt.veFrame.FW;
|
||||
root["device"]["LOAD"] = VeDirectMppt.veFrame.LOAD == true ? "ON" : "OFF";
|
||||
root["device"]["CS"] = VeDirectMppt.getCsAsString(VeDirectMppt.veFrame.CS);
|
||||
root["device"]["ERR"] = VeDirectMppt.getErrAsString(VeDirectMppt.veFrame.ERR);
|
||||
root["device"]["OR"] = VeDirectMppt.getOrAsString(VeDirectMppt.veFrame.OR);
|
||||
root["device"]["MPPT"] = VeDirectMppt.getMpptAsString(VeDirectMppt.veFrame.MPPT);
|
||||
root["device"]["HSDS"]["v"] = VeDirectMppt.veFrame.HSDS;
|
||||
root["device"]["data_age"] = VictronMppt.getDataAgeMillis() / 1000;
|
||||
root["device"]["age_critical"] = !VictronMppt.isDataValid();
|
||||
root["device"]["PID"] = spMpptData->getPidAsString();
|
||||
root["device"]["SER"] = spMpptData->SER;
|
||||
root["device"]["FW"] = spMpptData->FW;
|
||||
root["device"]["LOAD"] = spMpptData->LOAD == true ? "ON" : "OFF";
|
||||
root["device"]["CS"] = spMpptData->getCsAsString();
|
||||
root["device"]["ERR"] = spMpptData->getErrAsString();
|
||||
root["device"]["OR"] = spMpptData->getOrAsString();
|
||||
root["device"]["MPPT"] = spMpptData->getMpptAsString();
|
||||
root["device"]["HSDS"]["v"] = spMpptData->HSDS;
|
||||
root["device"]["HSDS"]["u"] = "d";
|
||||
|
||||
// battery info
|
||||
root["output"]["P"]["v"] = VeDirectMppt.veFrame.P;
|
||||
root["output"]["P"]["v"] = spMpptData->P;
|
||||
root["output"]["P"]["u"] = "W";
|
||||
root["output"]["P"]["d"] = 0;
|
||||
root["output"]["V"]["v"] = VeDirectMppt.veFrame.V;
|
||||
root["output"]["V"]["v"] = spMpptData->V;
|
||||
root["output"]["V"]["u"] = "V";
|
||||
root["output"]["V"]["d"] = 2;
|
||||
root["output"]["I"]["v"] = VeDirectMppt.veFrame.I;
|
||||
root["output"]["I"]["v"] = spMpptData->I;
|
||||
root["output"]["I"]["u"] = "A";
|
||||
root["output"]["I"]["d"] = 2;
|
||||
root["output"]["E"]["v"] = VeDirectMppt.veFrame.E;
|
||||
root["output"]["E"]["v"] = spMpptData->E;
|
||||
root["output"]["E"]["u"] = "%";
|
||||
root["output"]["E"]["d"] = 1;
|
||||
|
||||
// panel info
|
||||
root["input"]["PPV"]["v"] = VeDirectMppt.veFrame.PPV;
|
||||
root["input"]["PPV"]["v"] = spMpptData->PPV;
|
||||
root["input"]["PPV"]["u"] = "W";
|
||||
root["input"]["PPV"]["d"] = 0;
|
||||
root["input"]["VPV"]["v"] = VeDirectMppt.veFrame.VPV;
|
||||
root["input"]["VPV"]["v"] = spMpptData->VPV;
|
||||
root["input"]["VPV"]["u"] = "V";
|
||||
root["input"]["VPV"]["d"] = 2;
|
||||
root["input"]["IPV"]["v"] = VeDirectMppt.veFrame.IPV;
|
||||
root["input"]["IPV"]["v"] = spMpptData->IPV;
|
||||
root["input"]["IPV"]["u"] = "A";
|
||||
root["input"]["IPV"]["d"] = 2;
|
||||
root["input"]["YieldToday"]["v"] = VeDirectMppt.veFrame.H20;
|
||||
root["input"]["YieldToday"]["v"] = spMpptData->H20;
|
||||
root["input"]["YieldToday"]["u"] = "kWh";
|
||||
root["input"]["YieldToday"]["d"] = 3;
|
||||
root["input"]["YieldYesterday"]["v"] = VeDirectMppt.veFrame.H22;
|
||||
root["input"]["YieldYesterday"]["v"] = spMpptData->H22;
|
||||
root["input"]["YieldYesterday"]["u"] = "kWh";
|
||||
root["input"]["YieldYesterday"]["d"] = 3;
|
||||
root["input"]["YieldTotal"]["v"] = VeDirectMppt.veFrame.H19;
|
||||
root["input"]["YieldTotal"]["v"] = spMpptData->H19;
|
||||
root["input"]["YieldTotal"]["u"] = "kWh";
|
||||
root["input"]["YieldTotal"]["d"] = 3;
|
||||
root["input"]["MaximumPowerToday"]["v"] = VeDirectMppt.veFrame.H21;
|
||||
root["input"]["MaximumPowerToday"]["v"] = spMpptData->H21;
|
||||
root["input"]["MaximumPowerToday"]["u"] = "W";
|
||||
root["input"]["MaximumPowerToday"]["d"] = 0;
|
||||
root["input"]["MaximumPowerYesterday"]["v"] = VeDirectMppt.veFrame.H23;
|
||||
root["input"]["MaximumPowerYesterday"]["v"] = spMpptData->H23;
|
||||
root["input"]["MaximumPowerYesterday"]["u"] = "W";
|
||||
root["input"]["MaximumPowerYesterday"]["d"] = 0;
|
||||
|
||||
@ -146,10 +145,6 @@ void WebApiWsVedirectLiveClass::generateJsonResponse(JsonVariant& root)
|
||||
if (Configuration.get().PowerLimiter_Enabled)
|
||||
root["dpl"]["PLSTATE"] = PowerLimiter.getPowerLimiterState();
|
||||
root["dpl"]["PLLIMIT"] = PowerLimiter.getLastRequestedPowerLimit();
|
||||
|
||||
if (VeDirectMppt.getLastUpdate() > _newestVedirectTimestamp) {
|
||||
_newestVedirectTimestamp = VeDirectMppt.getLastUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void WebApiWsVedirectLiveClass::onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len)
|
||||
|
||||
21
src/main.cpp
21
src/main.cpp
@ -8,7 +8,7 @@
|
||||
#include "InverterSettings.h"
|
||||
#include "Led_Single.h"
|
||||
#include "MessageOutput.h"
|
||||
#include "VeDirectMpptController.h"
|
||||
#include "VictronMppt.h"
|
||||
#include "Battery.h"
|
||||
#include "Huawei_can.h"
|
||||
#include "MqttHandleDtu.h"
|
||||
@ -161,16 +161,8 @@ void setup()
|
||||
|
||||
Datastore.init();
|
||||
|
||||
// Initialize ve.direct communication
|
||||
MessageOutput.println(F("Initialize ve.direct interface... "));
|
||||
if (PinMapping.isValidVictronConfig()) {
|
||||
MessageOutput.printf("ve.direct rx = %d, tx = %d\r\n", pin.victron_rx, pin.victron_tx);
|
||||
VeDirectMppt.init(pin.victron_rx, pin.victron_tx,
|
||||
&MessageOutput, config.Vedirect_VerboseLogging);
|
||||
MessageOutput.println(F("done"));
|
||||
} else {
|
||||
MessageOutput.println(F("Invalid pin config"));
|
||||
}
|
||||
VictronMppt.init();
|
||||
|
||||
// Power meter
|
||||
PowerMeter.init();
|
||||
|
||||
@ -202,11 +194,8 @@ void loop()
|
||||
yield();
|
||||
Datastore.loop();
|
||||
yield();
|
||||
// Vedirect_Enabled is unknown to lib. Therefor check has to be done here
|
||||
if (Configuration.get().Vedirect_Enabled) {
|
||||
VeDirectMppt.loop();
|
||||
yield();
|
||||
}
|
||||
VictronMppt.loop();
|
||||
yield();
|
||||
MqttSettings.loop();
|
||||
yield();
|
||||
MqttHandleDtu.loop();
|
||||
|
||||
@ -388,7 +388,7 @@
|
||||
"Cancel": "Abbrechen",
|
||||
"RebootOpenDTU": "OpenDTU neustarten",
|
||||
"RebootQuestion": "Möchten Sie das Gerät wirklich neu starten?",
|
||||
"RebootHint": "<b>Hinweis:</b> Ein manueller Neustart muss normalerweise nicht durchgeführt werden. OpenDTU führt jeden erforderlichen Neustart (z. B. nach einem Firmware-Update) automatisch durch. Einstellungen werden auch ohne Neustart übernommen. Wenn Sie aufgrund eines Fehlers einen Neustart durchführen müssen, denken Sie bitte daran, diesen unter <a href=\"https://github.com/tbnobody/OpenDTU/issues\" class=\"alert-link\" target=\"_blank\">https://github.com/tbnobody/OpenDTU/issues</a> zu melden."
|
||||
"RebootHint": "<b>Hinweis:</b> Ein manueller Neustart muss normalerweise nicht durchgeführt werden. OpenDTU führt jeden erforderlichen Neustart (z. B. nach einem Firmware-Update) automatisch durch. Einstellungen werden auch ohne Neustart übernommen. Wenn Sie aufgrund eines Fehlers einen Neustart durchführen müssen, denken Sie bitte daran, diesen unter <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/issues\" class=\"alert-link\" target=\"_blank\">Github</a> zu melden."
|
||||
},
|
||||
"dtuadmin": {
|
||||
"DtuSettings": "DTU-Einstellungen",
|
||||
@ -701,11 +701,11 @@
|
||||
"ProjectOriginBody3": "Die Software wurde nach bestem Wissen und Gewissen entwickelt. Dennoch kann keine Haftung für eine Fehlfunktion oder einen Garantieverlust des Wechselrichters übernommen werden.",
|
||||
"ProjectOriginBody4": "OpenDTU ist frei verfügbar. Wenn Sie Geld für die Software bezahlt haben, wurden Sie wahrscheinlich abgezockt.",
|
||||
"NewsUpdates": "Neuigkeiten und Updates",
|
||||
"NewsUpdatesBody": "Neue Updates sind auf Github zu finden: <a href=\"https://github.com/tbnobody/OpenDTU\" target=\"_blank\">https://github.com/tbnobody/OpenDTU</a>",
|
||||
"NewsUpdatesBody": "Neue Updates sind auf <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/releases\" target=\"_blank\">Github</a> zu finden.",
|
||||
"ErrorReporting": "Fehlerberichte",
|
||||
"ErrorReportingBody": "Bitte melden Sie Probleme über die Ticketverwaltung von <a href=\"https://github.com/tbnobody/OpenDTU/issues\" target=\"_blank\">Github</a>.",
|
||||
"ErrorReportingBody": "Bitte melden Sie Probleme über die Ticketverwaltung von <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/issues\" target=\"_blank\">Github</a>.",
|
||||
"Discussion": "Diskussion",
|
||||
"DiscussionBody": "Diskutieren Sie mit uns auf <a href=\"https://discord.gg/WzhxEY62mB\" target=\"_blank\">Discord</a> oder <a href=\"https://github.com/tbnobody/OpenDTU/discussions\" target=\"_blank\">Github</a>"
|
||||
"DiscussionBody": "Diskutieren Sie mit uns auf <a href=\"https://discord.gg/WzhxEY62mB\" target=\"_blank\">Discord</a> oder <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/discussions\" target=\"_blank\">Github</a>."
|
||||
},
|
||||
"hints": {
|
||||
"RadioProblem": "Es konnte keine Verbindung zu einem der konfigurierten Funkmodule hergestellt werden. Bitte überprüfen Sie die Verdrahtung.",
|
||||
@ -777,6 +777,7 @@
|
||||
"ChargerSettings": "AC Ladegerät Einstellungen",
|
||||
"Configuration": "AC Ladegerät Konfiguration",
|
||||
"EnableHuawei": "Huawei R4850G2 an CAN Bus Interface aktiv",
|
||||
"CanControllerFrequency": "Frequenz des Quarzes am CAN Controller",
|
||||
"EnableAutoPower": "Automatische Leistungssteuerung",
|
||||
"Limits": "Limits",
|
||||
"VoltageLimit": "Ladespannungslimit",
|
||||
|
||||
@ -390,7 +390,7 @@
|
||||
"Cancel": "Cancel",
|
||||
"RebootOpenDTU": "Reboot OpenDTU",
|
||||
"RebootQuestion": "Do you really want to reboot the device?",
|
||||
"RebootHint": "<b>Note:</b> A manual reboot does not normally have to be performed. OpenDTU performs any required reboot (e.g. after a firmware update) automatically. Settings are also adopted without rebooting. If you need to reboot due to an error, please consider reporting it at <a href=\"https://github.com/tbnobody/OpenDTU/issues\" class=\"alert-link\" target=\"_blank\">https://github.com/tbnobody/OpenDTU/issues</a>."
|
||||
"RebootHint": "<b>Note:</b> A manual reboot does not normally have to be performed. OpenDTU performs any required reboot (e.g. after a firmware update) automatically. Settings are also adopted without rebooting. If you need to reboot due to an error, please consider reporting it at <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/issues\" class=\"alert-link\" target=\"_blank\">Github</a>."
|
||||
},
|
||||
"dtuadmin": {
|
||||
"DtuSettings": "DTU Settings",
|
||||
@ -710,11 +710,11 @@
|
||||
"ProjectOriginBody3": "The software was developed to the best of our knowledge and belief. Nevertheless, no liability can be accepted for a malfunction or guarantee loss of the inverter.",
|
||||
"ProjectOriginBody4": "OpenDTU is freely available. If you paid money for the software, you probably got ripped off.",
|
||||
"NewsUpdates": "News & Updates",
|
||||
"NewsUpdatesBody": "New updates can be found on Github: <a href=\"https://github.com/tbnobody/OpenDTU\" target=\"_blank\">https://github.com/tbnobody/OpenDTU</a>",
|
||||
"NewsUpdatesBody": "New updates can be found on <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/releases\" target=\"_blank\">Github</a>.",
|
||||
"ErrorReporting": "Error Reporting",
|
||||
"ErrorReportingBody": "Please report issues using the feature provided by <a href=\"https://github.com/tbnobody/OpenDTU/issues\" target=\"_blank\">Github</a>",
|
||||
"ErrorReportingBody": "Please report issues using the feature provided by <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/issues\" target=\"_blank\">Github</a>.",
|
||||
"Discussion": "Discussion",
|
||||
"DiscussionBody": "Discuss with us on <a href=\"https://discord.gg/WzhxEY62mB\" target=\"_blank\">Discord</a> or <a href=\"https://github.com/tbnobody/OpenDTU/discussions\" target=\"_blank\">Github</a>"
|
||||
"DiscussionBody": "Discuss with us on <a href=\"https://discord.gg/WzhxEY62mB\" target=\"_blank\">Discord</a> or <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/discussions\" target=\"_blank\">Github</a>."
|
||||
},
|
||||
"hints": {
|
||||
"RadioProblem": "Could not connect to a configured radio module. Please check the wiring.",
|
||||
@ -787,6 +787,7 @@
|
||||
"ChargerSettings": "AC Charger Settings",
|
||||
"Configuration": "AC Charger Configuration",
|
||||
"EnableHuawei": "Enable Huawei R4850G2 on CAN Bus Interface",
|
||||
"CanControllerFrequency": "CAN controller quarz frequency",
|
||||
"EnableAutoPower": "Automatic power control",
|
||||
"Limits": "Limits",
|
||||
"VoltageLimit": "Charge Voltage limit",
|
||||
|
||||
@ -388,7 +388,7 @@
|
||||
"Cancel": "Annuler",
|
||||
"RebootOpenDTU": "Redémarrer OpenDTU",
|
||||
"RebootQuestion": "Voulez-vous vraiment redémarrer l'appareil ?",
|
||||
"RebootHint": "<b>Astuce :</b> Normalement, il n'est pas nécessaire de procéder à un redémarrage manuel. OpenDTU effectue automatiquement tout redémarrage nécessaire (par exemple, après une mise à jour du firmware). Les paramètres sont également adoptés sans redémarrage. Si vous devez redémarrer en raison d'une erreur, veuillez envisager de la signaler à l'adresse suivante <a href=\"https://github.com/tbnobody/OpenDTU/issues\" class=\"alert-link\" target=\"_blank\">https://github.com/tbnobody/OpenDTU/issues</a>."
|
||||
"RebootHint": "<b>Astuce :</b> Normalement, il n'est pas nécessaire de procéder à un redémarrage manuel. OpenDTU effectue automatiquement tout redémarrage nécessaire (par exemple, après une mise à jour du firmware). Les paramètres sont également adoptés sans redémarrage. Si vous devez redémarrer en raison d'une erreur, veuillez envisager de la signaler à l'adresse suivante <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/issues\" class=\"alert-link\" target=\"_blank\">Github</a>."
|
||||
},
|
||||
"dtuadmin": {
|
||||
"DtuSettings": "Paramètres du DTU",
|
||||
@ -667,11 +667,11 @@
|
||||
"ProjectOriginBody3": "Le logiciel a été développé au mieux de nos connaissances et de nos convictions. Néanmoins, aucune responsabilité ne peut être acceptée en cas de dysfonctionnement ou de perte de garantie de l'onduleur.",
|
||||
"ProjectOriginBody4": "OpenDTU est disponible gratuitement. Si vous avez payé pour le logiciel, vous avez probablement été arnaqué.",
|
||||
"NewsUpdates": "Actualités et mises à jour",
|
||||
"NewsUpdatesBody": "Les nouvelles mises à jour peuvent être trouvées sur <a href=\"https://github.com/tbnobody/OpenDTU\" target=\"_blank\">https://github.com/tbnobody/OpenDTU</a>",
|
||||
"NewsUpdatesBody": "Les nouvelles mises à jour peuvent être trouvées sur <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/releases\" target=\"_blank\">Github</a>.",
|
||||
"ErrorReporting": "Rapport d'erreurs",
|
||||
"ErrorReportingBody": "Veuillez signaler les problèmes en utilisant la fonction fournie par <a href=\"https://github.com/tbnobody/OpenDTU/issues\" target=\"_blank\">Github</a>.",
|
||||
"ErrorReportingBody": "Veuillez signaler les problèmes en utilisant la fonction fournie par <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/issues\" target=\"_blank\">Github</a>.",
|
||||
"Discussion": "Discussion",
|
||||
"DiscussionBody": "Discutez avec nous sur <a href=\"https://discord.gg/WzhxEY62mB\" target=\"_blank\">Discord</a> ou sur <a href=\"https://github.com/tbnobody/OpenDTU/discussions\" target=\"_blank\">Github</a>"
|
||||
"DiscussionBody": "Discutez avec nous sur <a href=\"https://discord.gg/WzhxEY62mB\" target=\"_blank\">Discord</a> ou sur <a href=\"https://github.com/helgeerbe/OpenDTU-OnBattery/discussions\" target=\"_blank\">Github</a>."
|
||||
},
|
||||
"hints": {
|
||||
"RadioProblem": "Impossible de se connecter à un module radio configuré.. Veuillez vérifier le câblage.",
|
||||
@ -743,6 +743,7 @@
|
||||
"ChargerSettings": "AC Charger Settings",
|
||||
"Configuration": "AC Charger Configuration",
|
||||
"EnableHuawei": "Enable Huawei R4850G2 on CAN Bus Interface",
|
||||
"CanControllerFrequency": "CAN controller quarz frequency",
|
||||
"EnableAutoPower": "Automatic power control",
|
||||
"Limits": "Limits",
|
||||
"VoltageLimit": "Charge Voltage limit",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export interface AcChargerConfig {
|
||||
enabled: boolean;
|
||||
can_controller_frequency: number;
|
||||
auto_power_enabled: boolean;
|
||||
voltage_limit: number;
|
||||
enable_voltage_limit: number;
|
||||
|
||||
@ -9,6 +9,20 @@
|
||||
<InputElement :label="$t('acchargeradmin.EnableHuawei')"
|
||||
v-model="acChargerConfigList.enabled"
|
||||
type="checkbox" wide/>
|
||||
|
||||
<div class="row mb-3" v-show="acChargerConfigList.enabled">
|
||||
<label class="col-sm-4 col-form-label">
|
||||
{{ $t('acchargeradmin.CanControllerFrequency') }}
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="form-select" v-model="acChargerConfigList.can_controller_frequency">
|
||||
<option v-for="frequency in frequencyTypeList" :key="frequency.key" :value="frequency.value">
|
||||
{{ frequency.key }} MHz
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InputElement v-show="acChargerConfigList.enabled"
|
||||
:label="$t('acchargeradmin.EnableAutoPower')"
|
||||
v-model="acChargerConfigList.auto_power_enabled"
|
||||
@ -89,6 +103,10 @@ export default defineComponent({
|
||||
alertMessage: "",
|
||||
alertType: "info",
|
||||
showAlert: false,
|
||||
frequencyTypeList: [
|
||||
{ key: 8, value: 8000000 },
|
||||
{ key: 16, value: 16000000 },
|
||||
],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user