Merge branch 'development'

This commit is contained in:
helgeerbe 2023-10-24 16:40:47 +02:00
commit eb578f08c5
33 changed files with 633 additions and 655 deletions

View File

@ -111,6 +111,7 @@ class VictronSmartShuntStats : public BatteryStats {
float _voltage;
float _current;
float _temperature;
bool _tempPresent;
uint8_t _chargeCycles;
uint32_t _timeToGo;
float _chargedEnergy;

View File

@ -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;

View File

@ -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);

View File

@ -62,7 +62,6 @@ public:
bool isValidNrf24Config();
bool isValidCmt2300Config();
bool isValidEthConfig();
bool isValidVictronConfig();
bool isValidHuaweiConfig();
private:

48
include/VictronMppt.h Normal file
View 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;

View File

@ -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;
};

View File

@ -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

View File

@ -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);
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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
};

View File

@ -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);

View File

@ -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;

View File

@ -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;
};

View File

@ -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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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
View 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;
}

View File

@ -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) {

View File

@ -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!");

View File

@ -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);

View File

@ -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)

View File

@ -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();

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -1,5 +1,6 @@
export interface AcChargerConfig {
enabled: boolean;
can_controller_frequency: number;
auto_power_enabled: boolean;
voltage_limit: number;
enable_voltage_limit: number;

View File

@ -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.