From db20f078d4d29ab635c4a360331d19a191ef42c5 Mon Sep 17 00:00:00 2001 From: skippermeister Date: Sat, 17 Aug 2024 00:26:40 +0200 Subject: [PATCH] supporting I2C CAN Bus, MCP2515 and CAN0 for batteries and charger --- .../ESP32_S3_USB_CMT_HUAWEI.json | 18 +- include/BatteryCanReceiver.h | 4 +- include/Configuration.h | 3 + include/Huawei_can.h | 14 +- include/Led_Strip.h | 1 + include/MeanWell_can.h | 13 +- include/PinMapping.h | 6 +- include/Statistic.h | 47 ++++ include/SurplusPower.h | 56 ++++ include/VictronMppt.h | 1 + lib/Hoymiles/src/HoymilesRadio_CMT.cpp | 3 + lib/Hoymiles/src/HoymilesRadio_CMT.h | 2 + lib/Hoymiles/src/HoymilesRadio_NRF.cpp | 1 + lib/Hoymiles/src/HoymilesRadio_NRF.h | 3 +- .../VeDirectFrameHandler.cpp | 2 +- platformio.ini | 1 + src/Battery.cpp | 75 ++++-- src/BatteryCanReceiver.cpp | 166 +++++++----- src/BatteryStats.cpp | 4 +- src/Configuration.cpp | 6 + src/Huawei_can.cpp | 77 +++--- src/Led_Strip.cpp | 85 ++---- src/MeanWell_can.cpp | 242 +++++++++-------- src/MqttHandleBatteryHass.cpp | 111 ++++---- src/PinMapping.cpp | 5 + src/PowerLimiter.cpp | 18 +- src/PowerMeterSerialSdm.cpp | 1 - src/SurplusPower.cpp | 253 ++++++++++++++++++ src/VictronMppt.cpp | 8 +- src/WebApi_MeanWell.cpp | 1 + src/WebApi_battery.cpp | 1 + src/WebApi_device.cpp | 4 +- src/WebApi_powerlimiter.cpp | 6 + src/main.cpp | 2 +- webapp/src/locales/de.json | 19 +- webapp/src/locales/en.json | 19 +- webapp/src/locales/fr.json | 19 +- webapp/src/types/AcChargerConfig.ts | 1 + webapp/src/types/BatteryConfig.ts | 1 + webapp/src/types/PowerLimiterConfig.ts | 1 + webapp/src/views/AcChargerAdminView.vue | 2 +- webapp/src/views/BatteryAdminView.vue | 47 ++-- webapp/src/views/PowerLimiterAdminView.vue | 15 ++ webapp_dist/js/app.js.gz | Bin 228772 -> 229157 bytes 44 files changed, 918 insertions(+), 446 deletions(-) create mode 100644 include/Statistic.h create mode 100644 include/SurplusPower.h create mode 100644 src/SurplusPower.cpp diff --git a/docs/DeviceProfiles/ESP32_S3_USB_CMT_HUAWEI.json b/docs/DeviceProfiles/ESP32_S3_USB_CMT_HUAWEI.json index 5da707dbb..12d311b6c 100644 --- a/docs/DeviceProfiles/ESP32_S3_USB_CMT_HUAWEI.json +++ b/docs/DeviceProfiles/ESP32_S3_USB_CMT_HUAWEI.json @@ -28,8 +28,8 @@ "rs232_tx3": -1 }, "battery": { - "rs232_tx": 47, - "rs232_rx": 45, + "rs232_tx": 45, + "rs232_rx": 47, "wakeup": 21 }, "charger": { @@ -79,9 +79,9 @@ "rs232_tx3": -1 }, "battery": { - "rs232_tx": 47, - "rs232_rx": 45, - "wakeup": 21 + "rs485_tx": 45, + "rs485_rx": 47, + "rs485_rts": 21 }, "charger": { "can0_rx": 3, @@ -130,15 +130,15 @@ "rs232_tx3": -1 }, "battery": { - "rs485_tx": 47, - "rs485_rx": 45, + "rs485_tx": 45, + "rs485_rx": 47, "rs485_rts": 21 }, "charger": { "mcp2515_irq": 4, "mcp2515_clk": 5, - "mcp2515_miso": 6, - "mcp2515_mosi": 7, + "mcp2515_mosi": 6, + "mcp2515_miso": 7, "mcp2515_cs": 15, "power": 16 }, diff --git a/include/BatteryCanReceiver.h b/include/BatteryCanReceiver.h index 21c5b0f61..a4587c00c 100644 --- a/include/BatteryCanReceiver.h +++ b/include/BatteryCanReceiver.h @@ -7,6 +7,7 @@ #include #include #include +#include #include class BatteryCanReceiver : public BatteryProvider { @@ -25,7 +26,7 @@ class BatteryCanReceiver : public BatteryProvider { float scaleValue(int16_t value, float factor); bool getBit(uint8_t value, uint8_t bit); - char const* _providerName = "Battery CAN"; + char _providerName[32] = "Battery CAN"; bool _initialized = false; @@ -33,6 +34,7 @@ class BatteryCanReceiver : public BatteryProvider { int _mcp2515_irq; SPIClass *SPI = nullptr; MCP_CAN *_CAN = nullptr; + I2C_CAN *i2c_can = nullptr; }; #endif diff --git a/include/Configuration.h b/include/Configuration.h index 7f576819b..4a17b8a7a 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -229,6 +229,9 @@ struct PowerLimiter_CONFIG_T { uint32_t FullSolarPassThroughSoc; float FullSolarPassThroughStartVoltage; float FullSolarPassThroughStopVoltage; +#ifdef USE_SURPLUSPOWER + bool SurplusPowerEnabled; +#endif }; enum BatteryVoltageUnit { Volts = 0, DeciVolts = 1, CentiVolts = 2, MilliVolts = 3 }; diff --git a/include/Huawei_can.h b/include/Huawei_can.h index 2834ea9c9..52a2e5a65 100644 --- a/include/Huawei_can.h +++ b/include/Huawei_can.h @@ -10,6 +10,7 @@ #include #include #include +#include "PinMapping.h" #ifndef HUAWEI_PIN_MISO #define HUAWEI_PIN_MISO 12 @@ -102,27 +103,18 @@ class HuaweiCanCommClass { uint32_t getParameterValue(uint8_t parameter); void setParameterValue(uint16_t in, uint8_t parameterType); - bool isMCP2515Provider() { return _provider == CAN_Provider_t::MCP2515; } + bool isMCP2515Provider() { return PinMapping.get()charger.provider == Charger_Provider_t::MCP2515; } private: void sendRequest(); byte sendMsgBuf(uint32_t identifier, uint8_t extd, uint8_t len, uint8_t *data); - bool enable(void); - twai_general_config_t g_config; SPIClass *SPI; MCP_CAN *_CAN; I2C_CAN *i2c_can; uint8_t _mcp2515Irq; // IRQ pin - uint32_t _nextRequestMillis = 0; // When to send next data request to PSU - enum class CAN_Provider_t { - UNDEFINED=0, - CAN0=1, - I2C=2, - MCP2515=3 - }; - CAN_Provider_t _provider = CAN_Provider_t::UNDEFINED; + uint32_t _nextRequestMillis = 0; // When to send next data request to PSU std::mutex _mutex; diff --git a/include/Led_Strip.h b/include/Led_Strip.h index 6e1938970..5a4609bbe 100644 --- a/include/Led_Strip.h +++ b/include/Led_Strip.h @@ -268,6 +268,7 @@ class LedStripClass { LedRGBState_t _ledRGBState[LED_COUNT]; LedRGBState_t _allMode; + const Led_Config_T *cLed = nullptr; }; extern LedStripClass LedStrip; diff --git a/include/MeanWell_can.h b/include/MeanWell_can.h index 125857fde..54bef2e21 100644 --- a/include/MeanWell_can.h +++ b/include/MeanWell_can.h @@ -10,6 +10,7 @@ #include #include #include +#include "PinMapping.h" #define MEANWELL_MINIMAL_SET_VOLTAGE 42 @@ -171,7 +172,7 @@ class MeanWellCanClass { uint32_t getEEPROMwrites() { return EEPROMwrites;} - bool isMCP2515Provider() { return _provider == CAN_Provider_t::MCP2515; } + bool isMCP2515Provider() { return PinMapping.get().charger.provider == Charger_Provider_t::MCP2515; } private: void loop(); @@ -181,7 +182,6 @@ class MeanWellCanClass { void updateEEPROMwrites2NVS(); void switchChargerOff(const char* reason); - bool enable(void); bool getCanCharger(void); bool parseCanPackets(void); void calcPower(); @@ -198,7 +198,6 @@ class MeanWellCanClass { float calcEfficency(float x); void setupParameter(void); - twai_general_config_t g_config; I2C_CAN* i2c_can; SPIClass* spi; MCP_CAN* CAN; @@ -226,13 +225,7 @@ class MeanWellCanClass { bool _lastPowerCommandSuccess; bool _setupParameter = true; - enum class CAN_Provider_t { - UNDEFINED=0, - CAN0=1, - I2C=2, - MCP2515=3 - }; - CAN_Provider_t _provider = CAN_Provider_t::UNDEFINED; + char _providerName[32] = "Meanwell CAN"; const uint8_t ChargerID = 0x03; uint32_t EEPROMwrites; diff --git a/include/PinMapping.h b/include/PinMapping.h index a61b14a14..8f028f94e 100644 --- a/include/PinMapping.h +++ b/include/PinMapping.h @@ -18,9 +18,10 @@ struct MCP2515_t { int8_t cs; }; -enum Charger_Provider_t { undefined=0, CAN0=1, MCP2515=2, I2C0=3, I2C1=4}; +enum class Charger_Provider_t { undefined=0, CAN0=1, MCP2515=2, I2C0=3, I2C1=4}; struct CHARGER_t { Charger_Provider_t provider; + const char *providerName; union { struct { int8_t rx; @@ -43,6 +44,7 @@ struct CHARGER_t { enum class Battery_Provider_t { undefined=0, CAN0=1, MCP2515=2, I2C0=3, I2C1=4, RS232=5, RS485=6}; struct Battery_t { + const char *providerName; Battery_Provider_t provider; union { #if defined(USE_PYLONTECH_CAN_RECEIVER) || defined(USE_PYTES_CAN_RECEIVER) @@ -187,6 +189,8 @@ class PinMappingClass { void createPinMappingJson() const; PinMapping_t _pinMapping; + + const char* help[7] = {"unknown", "CAN0 Bus", "MCP2515", "I2C0/CAN", "I2C1/CAN", "RS232", "RS485"}; }; extern PinMappingClass PinMapping; diff --git a/include/Statistic.h b/include/Statistic.h new file mode 100644 index 000000000..c5cd46de9 --- /dev/null +++ b/include/Statistic.h @@ -0,0 +1,47 @@ +#pragma once + +#ifdef USE_SURPLUSPOWER + +template +class WeightedAVG { +public: + WeightedAVG(int16_t factor) + : _countMax(factor) + , _count(0), _countNum(0), _avgV(0), _minV(0), _maxV(0), _lastV(0) {} + + void addNumber(const T& num) { + if (_count == 0){ + _count++; + _avgV = num; + _minV = num; + _maxV = num; + _countNum = 1; + } else { + if (_count < _countMax) + _count++; + _avgV = (_avgV * (_count - 1) + num) / _count; + if (num < _minV) _minV = num; + if (num > _maxV) _maxV = num; + _countNum++; + } + _lastV = num; + } + + void reset(void) { _count = 0; _avgV = 0; _minV = 0; _maxV = 0; _lastV = 0; _countNum = 0; } + void reset(const T& num) { _count = 0; addNumber(num); } + T getAverage() const { return _avgV; } + T getMin() const { return _minV; } + T getMax() const { return _maxV; } + T getLast() const { return _lastV; } + int32_t getCounts() const { return _countNum; } + +private: + int16_t _countMax; // weighting factor (10 => 1/10 => 10%) + int16_t _count; // counter (0 - _countMax) + int32_t _countNum; // counts the amount of added numbers + T _avgV; // average value + T _minV; // minimum value + T _maxV; // maximum value + T _lastV; // last value +}; +#endif diff --git a/include/SurplusPower.h b/include/SurplusPower.h new file mode 100644 index 000000000..eecc5276b --- /dev/null +++ b/include/SurplusPower.h @@ -0,0 +1,56 @@ +#pragma once + +#ifdef USE_SURPLUSPOWER + +#include +#include +#include "Statistic.h" + + +class SurplusPowerClass { + public: + SurplusPowerClass() = default; + ~SurplusPowerClass() = default; + + bool useSurplusPower(void) const; + int32_t calcSurplusPower(int32_t const requestedPower); + + private: + enum class SurplusState : uint8_t { + IDLE = 0, + TRY_MORE = 1, + REDUCE_POWER = 2, + IN_TARGET = 3, + MAXIMUM_POWER = 4, + }; + + enum class Text : uint8_t { + Q_NODATA = 0, + Q_EXCELLENT = 1, + Q_GOOD = 2, + Q_BAD = 3, + T_HEAD = 4 + }; + + frozen::string const& getStatusText(SurplusPowerClass::SurplusState state); + frozen::string const& getText(SurplusPowerClass::Text tNr); + void handleQualityCounter(void); + + // to handle regulation + SurplusState _surplusState = SurplusState::IDLE; // actual regulation state + float _absorptionVoltage = -1.0f; // from MPPT + float _floatVoltage = -1.0f; // from MPPT + int32_t _powerStep = 50; // power step size in W (default) + int32_t _surplusPower = 0; // actual surplus power + int32_t _inTargetTime = 0; // records the time we hit the target power + WeightedAVG _avgMPPTVoltage {3}; // the average helps to smooth the regulation + + // to handle the quality counter + int8_t _qualityCounter = 0; // quality counter + WeightedAVG _qualityAVG {20}; // quality counter average + int32_t _lastAddPower = 0; // last power step +}; + +extern SurplusPowerClass SurplusPower; + +#endif diff --git a/include/VictronMppt.h b/include/VictronMppt.h index 3fd5f1c51..28a5e6f1c 100644 --- a/include/VictronMppt.h +++ b/include/VictronMppt.h @@ -49,6 +49,7 @@ class VictronMpptClass { enum class MPPTVoltage : uint8_t { ABSORPTION = 0, FLOAT = 1, + BATTERY = 2 }; float getVoltage(MPPTVoltage kindOf) const; diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp index 654b2d3ea..b8e578993 100644 --- a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp @@ -256,11 +256,14 @@ uint32_t HoymilesRadio_CMT::getInvBootFrequency() const return countryDefinition.at(_countryMode).Freq_StartUp; } +//void IRAM_ATTR HoymilesRadio_CMT::handleInt1() void ARDUINO_ISR_ATTR HoymilesRadio_CMT::handleInt1() { _packetSent = true; } + +//void IRAM_ATTR HoymilesRadio_CMT::handleInt2() void ARDUINO_ISR_ATTR HoymilesRadio_CMT::handleInt2() { _packetReceived = true; diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.h b/lib/Hoymiles/src/HoymilesRadio_CMT.h index eb4ab95f7..bc76d5e12 100644 --- a/lib/Hoymiles/src/HoymilesRadio_CMT.h +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.h @@ -71,6 +71,8 @@ class HoymilesRadio_CMT : public HoymilesRadio { std::vector getCountryFrequencyList() const; private: +// void IRAM_ATTR handleInt1(); +// void IRAM_ATTR handleInt2(); void ARDUINO_ISR_ATTR handleInt1(); void ARDUINO_ISR_ATTR handleInt2(); diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp index 635014d65..186762e22 100644 --- a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp @@ -143,6 +143,7 @@ void HoymilesRadio_NRF::openWritingPipe(const serial_u serial) _radio->openWritingPipe(s.u64); } +// void IRAM_ATTR HoymilesRadio_NRF::handleIntr() void ARDUINO_ISR_ATTR HoymilesRadio_NRF::handleIntr() { _packetReceived = true; diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.h b/lib/Hoymiles/src/HoymilesRadio_NRF.h index a6777ce52..234af0121 100644 --- a/lib/Hoymiles/src/HoymilesRadio_NRF.h +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.h @@ -24,6 +24,7 @@ class HoymilesRadio_NRF : public HoymilesRadio { private: void ARDUINO_ISR_ATTR handleIntr(); +// void IRAM_ATTR handleIntr(); uint8_t getRxNxtChannel(); uint8_t getTxNxtChannel(); void switchRxCh(); @@ -43,4 +44,4 @@ class HoymilesRadio_NRF : public HoymilesRadio { volatile bool _packetReceived = false; std::queue _rxBuffer; -}; \ No newline at end of file +}; diff --git a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp index 0636ec42a..bf97c57a1 100644 --- a/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp +++ b/lib/VeDirectFrameHandler/VeDirectFrameHandler.cpp @@ -70,7 +70,7 @@ void VeDirectFrameHandler::init(char const* who, int8_t rx, int8_t tx, _vedirectSerial = std::make_unique(hwSerialPort); _vedirectSerial->setRxBufferSize(512); // increased from default (256) to 512 Byte to avoid overflow _vedirectSerial->end(); // make sure the UART will be re-initialized - _vedirectSerial->begin(19200, SERIAL_8N1, rx, tx); + _vedirectSerial->begin(19200, SERIAL_8N1, rx, tx); _vedirectSerial->flush(); _canSend = (tx != -1); _msgOut = msgOut; diff --git a/platformio.ini b/platformio.ini index e4d942882..468f87423 100644 --- a/platformio.ini +++ b/platformio.ini @@ -149,6 +149,7 @@ board_build.flash_mode = qio board_build.partitions = partitions_custom_16mb.csv board_upload.flash_size = 16MB build_flags = ${env.build_flags} + -DUSE_SURPLUSPOWER=1 -DUSE_RADIO_NRF=1 -DUSE_RADIO_CMT=1 -DUSE_PYLONTECH_RS485_RECEIVER=1 diff --git a/src/Battery.cpp b/src/Battery.cpp index 7006bdab5..ef541ee40 100644 --- a/src/Battery.cpp +++ b/src/Battery.cpp @@ -52,50 +52,81 @@ void BatteryClass::updateSettings() return; } - MessageOutput.println("Activate Battery provider"); + MessageOutput.printf("Activate Battery provider using %s interface\r\n", PinMapping.get().battery.providerName); + Battery_Provider_t ioProvider = PinMapping.get().battery.provider; switch (cBattery.Provider) { +#if defined(USE_PYLONTECH_RS485_RECEIVER) || defined(USE_PYLONTECH_CAN_RECEIVER) + case 0: // Initialize Pylontech Battery + switch (ioProvider) { #ifdef USE_PYLONTECH_RS485_RECEIVER - case 0: // Initialize Pylontech Battery / RS485 bus - _upProvider = std::make_unique(); - break; + case Battery_Provider_t::RS485: + _upProvider = std::make_unique(); + break; #endif #ifdef USE_PYLONTECH_CAN_RECEIVER - case 1: // Initialize Pylontech Battery / CAN0 bus - case 2: // Initialize Pylontech Battery / MCP2515 bus - _upProvider = std::make_unique(); + case Battery_Provider_t::CAN0: + case Battery_Provider_t::MCP2515: + case Battery_Provider_t::I2C0: + case Battery_Provider_t::I2C1: + _upProvider = std::make_unique(); + break; +#endif + default:; + } + break; +#endif +#if defined(USE_PYTES_CAN_RECEIVER) || defined(USE_PYTES_RS485_RECEIVER) + case 1: // Initialize Pytes Battery + switch (ioProvider) { +#ifdef USE_PYTES_RS485_RECEIVER + case Battery_Provider_t::RS485: + break; +#endif +#ifdef USE_PYTES_CAN_RECEIVER + case Battery_Provider_t::CAN0: + case Battery_Provider_t::MCP2515: + case Battery_Provider_t::I2C0: + case Battery_Provider_t::I2C1: + _upProvider = std::make_unique(); + break; +#endif + default:; + } break; #endif + #ifdef USE_JKBMS_CONTROLLER case 3: - _upProvider = std::make_unique(); + if (ioProvider == Battery_Provider_t::RS232 || ioProvider == Battery_Provider_t::RS485) + _upProvider = std::make_unique(); break; #endif -#ifdef USE_VICTRON_SMART_SHUNT +#ifdef USE_DALYBMS_CONTROLLER case 4: - _upProvider = std::make_unique(); + if (ioProvider == Battery_Provider_t::RS232 || ioProvider == Battery_Provider_t::RS485) + _upProvider = std::make_unique(); break; #endif -#ifdef USE_DALYBMS_CONTROLLER + +#ifdef USE_VICTRON_SMART_SHUNT case 5: - _upProvider = std::make_unique(); + if (ioProvider == Battery_Provider_t::RS232) + _upProvider = std::make_unique(); break; #endif - #ifdef USE_MQTT_BATTERY case 6: _upProvider = std::make_unique(); break; #endif -#ifdef USE_PYTES_CAN_RECEIVER - case 7: // Initialize Pylontech Battery / CAN0 bus - case 8: // Initialize Pylontech Battery / MCP2515 bus - _upProvider = std::make_unique(); - break; -#endif - default: - MessageOutput.printf("Unknown battery provider: %d\r\n", cBattery.Provider); - return; + default:; + } + + if (_upProvider == nullptr) { + MessageOutput.printf("Unknown battery provider: %d at interface %s\r\n", + cBattery.Provider, PinMapping.get().battery.providerName); + return; } _upProvider->_verboseLogging = Configuration.get().Battery.VerboseLogging; diff --git a/src/BatteryCanReceiver.cpp b/src/BatteryCanReceiver.cpp index 7569fdf8f..a758f078f 100644 --- a/src/BatteryCanReceiver.cpp +++ b/src/BatteryCanReceiver.cpp @@ -9,37 +9,33 @@ bool BatteryCanReceiver::init(char const* providerName) { - const char* help[7] = {"unknown", "CAN0", "MCP2515", "I2C0", "I2C1", "RS232", "RS485"}; - static char helperString[32]; - snprintf(helperString, 31, "[%s %s]", providerName, help[(int)PinMapping.get().battery.provider]); - _providerName = helperString; + snprintf(_providerName, 31, "[%s %s]", providerName, PinMapping.get().battery.providerName); MessageOutput.printf("%s Initialize interface...", _providerName); + if (PinMapping.isValidBatteryConfig()) { + MessageOutput.println(" Invalid pin config"); + return false; + } + const auto &pin = PinMapping.get().battery; if (pin.provider == Battery_Provider_t::MCP2515) { - MessageOutput.printf(" clk = %d, miso = %d, mosi = %d, cs = %d, irq = %d", + MessageOutput.printf(" clk = %d, miso = %d, mosi = %d, cs = %d, irq = %d\r\n", pin.mcp2515.clk, pin.mcp2515.miso, pin.mcp2515.mosi, pin.mcp2515.cs, pin.mcp2515.irq); - if (pin.mcp2515.clk < 0 || pin.mcp2515.miso < 0 || pin.mcp2515.mosi < 0 || pin.mcp2515.cs < 0 || pin.mcp2515.irq < 0) { - MessageOutput.println(", Invalid pin config"); - return false; - } - - _mcp2515_irq = pin.mcp2515.irq; - auto oSPInum = SPIPortManager.allocatePort("MCP2515"); if (!oSPInum) { return false; } - MessageOutput.printf(", init SPI... "); + MessageOutput.printf("Init SPI... "); SPI = new SPIClass(*oSPInum); SPI->begin(pin.mcp2515.clk, pin.mcp2515.miso, pin.mcp2515.mosi, pin.mcp2515.cs); - MessageOutput.printf("done"); + MessageOutput.println("done."); pinMode(pin.mcp2515.cs, OUTPUT); digitalWrite(pin.mcp2515.cs, HIGH); + _mcp2515_irq = pin.mcp2515.irq; pinMode(_mcp2515_irq, INPUT_PULLUP); auto frequency = Configuration.get().MCP2515.Controller_Frequency; @@ -47,19 +43,17 @@ bool BatteryCanReceiver::init(char const* providerName) if (16000000UL == frequency) { mcp_frequency = MCP_16MHZ; } else if (20000000UL == frequency) { mcp_frequency = MCP_20MHZ; } else if (8000000UL != frequency) { - MessageOutput.printf("%s unknown frequency %u Hz, using 8 MHz\r\n", _providerName, frequency); - //SPI->end(); - return false; + MessageOutput.printf("Unknown frequency %u Hz, using 8 MHz\r\n", frequency); } - MessageOutput.printf(", MCP2515 Quarz = %u Mhz", (unsigned int)(frequency/1000000UL)); + MessageOutput.printf("MCP2515 Quarz = %u Mhz\r\n", (unsigned int)(frequency/1000000UL)); if (!_CAN) { MessageOutput.printf(", init MCP_CAN... "); _CAN = new MCP_CAN(SPI, pin.mcp2515.cs); - MessageOutput.printf("done"); + MessageOutput.println("done."); } if (!_CAN->begin(MCP_STDEXT, CAN_500KBPS, mcp_frequency) == CAN_OK) { - MessageOutput.println(", MCP2515 chip not initialized"); + MessageOutput.println("MCP2515 chip not initialized"); //SPI->end(); return false; } @@ -76,51 +70,80 @@ bool BatteryCanReceiver::init(char const* providerName) } else if (pin.provider == Battery_Provider_t::CAN0) { - MessageOutput.printf(" rx = %d, tx = %d", pin.can0.rx, pin.can0.tx); - - if (PinMapping.isValidBatteryConfig()) { - MessageOutput.println(" Invalid pin config"); - return false; - } + MessageOutput.printf(" rx = %d, tx = %d\r\n", pin.can0.rx, pin.can0.tx); auto tx = static_cast(pin.can0.tx); auto rx = static_cast(pin.can0.rx); twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(tx, rx, TWAI_MODE_NORMAL); + // g_config.bus_off_io = (gpio_num_t)can0_stb; + g_config.intr_flags = ESP_INTR_FLAG_LEVEL2; // Initialize configuration structures using macro initializers twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS(); twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); // Install TWAI driver + MessageOutput.print("Twai driver install"); esp_err_t twaiLastResult = twai_driver_install(&g_config, &t_config, &f_config); switch (twaiLastResult) { case ESP_OK: - MessageOutput.print(" Twai driver installed"); + MessageOutput.print("ed"); break; case ESP_ERR_INVALID_ARG: - MessageOutput.println(" Twai driver install - invalid arg"); + MessageOutput.println(" - invalid arg"); return false; case ESP_ERR_NO_MEM: - MessageOutput.println(" Twai driver install - no memory"); + MessageOutput.println(" - no memory"); return false; case ESP_ERR_INVALID_STATE: - MessageOutput.println(" Twai driver install - invalid state"); + MessageOutput.println(" - invalid state"); + return false; + default:; + MessageOutput.println(" failed."); return false; } // Start TWAI driver + MessageOutput.print(", start"); twaiLastResult = twai_start(); switch (twaiLastResult) { case ESP_OK: - MessageOutput.print(" and started"); + MessageOutput.print("ed"); break; case ESP_ERR_INVALID_STATE: - MessageOutput.println(" Twai driver start - invalid state"); + MessageOutput.println(" - invalid state."); + return false; + default:; + MessageOutput.println(" - failed."); return false; } + } else if (pin.provider == Battery_Provider_t::I2C0 || pin.provider == Battery_Provider_t::I2C1) { - MessageOutput.println(" I2C CAN Bus Provider not yet implemented"); - return false; + + auto scl = pin.provider == Battery_Provider_t::I2C0?pin.i2c0.scl:pin.i2c1.scl; + auto sda = pin.provider == Battery_Provider_t::I2C1?pin.i2c0.sda:pin.i2c1.sda; + + MessageOutput.printf("I2C CAN Bus @ I2C%d scl = %d, sda = %d\r\n", pin.i2c0.scl>=0?0:1, scl, sda); + + i2c_can = new I2C_CAN(pin.provider == Battery_Provider_t::I2C0?&Wire:&Wire1, 0x25, scl, sda, 400000UL); // Set I2C Address + + int i = 10; + while (CAN_OK != i2c_can->begin(I2C_CAN_500KBPS)) // init can bus : baudrate = 500k + { + delay(200); + if (--i) continue; + + MessageOutput.println("CAN Bus FAIL!"); + return false; + } +/* + const uint32_t myMask = 0xFFFFFFFF; // Look at all incoming bits and... + const uint32_t myFilter = 0x1081407F; // filter for this message only + i2c_can->init_Mask(0, 1, myMask); + i2c_can->init_Filt(0, 1, myFilter); + i2c_can->init_Mask(1, 1, myMask); +*/ + MessageOutput.println("I2C CAN Bus OK!"); } MessageOutput.println(" Done"); @@ -154,7 +177,7 @@ void BatteryCanReceiver::deinit() MessageOutput.printf("stopped"); break; case ESP_ERR_INVALID_STATE: - MessageOutput.printf(" - invalid state"); + MessageOutput.print(" - invalid state"); break; } @@ -162,14 +185,13 @@ void BatteryCanReceiver::deinit() twaiLastResult = twai_driver_uninstall(); switch (twaiLastResult) { case ESP_OK: - MessageOutput.print(" uninstalled"); + MessageOutput.print(" and uninstalled"); break; case ESP_ERR_INVALID_STATE: - MessageOutput.print(" uninstall - invalid state"); - break; + MessageOutput.print(", uninstall - invalid state"); } + } else if (pin.provider == Battery_Provider_t::I2C0 || pin.provider == Battery_Provider_t::I2C1){ } - MessageOutput.println(); _initialized = false; @@ -177,66 +199,84 @@ void BatteryCanReceiver::deinit() void BatteryCanReceiver::loop() { - const auto pin = PinMapping.get().battery; + const auto _provider = PinMapping.get().battery.provider; twai_message_t rx_message; - if (pin.provider == Battery_Provider_t::MCP2515) + if (_provider == Battery_Provider_t::MCP2515) { - if(!digitalRead(_mcp2515_irq)) // If CAN0_INT pin is low, read receive buffer + if(digitalRead(_mcp2515_irq)) // If CAN0_INT pin is low, read receive buffer { - _CAN->readMsgBuf(&rx_message.identifier, &rx_message.data_length_code, rx_message.data); // Read data: len = data length, buf = data byte(s) + return; + } + + _CAN->readMsgBuf(&rx_message.identifier, &rx_message.data_length_code, rx_message.data); // Read data: len = data length, buf = data byte(s) + if (_verboseLogging) { if((rx_message.identifier & 0x80000000) == 0x80000000) // Determine if ID is standard (11 bits) or extended (29 bits) MessageOutput.printf("Extended ID: 0x%.8" PRIx32 " DLC: %1d Data:", rx_message.identifier & 0x1FFFFFFF, rx_message.data_length_code); else MessageOutput.printf("Standard ID: 0x%.3" PRIx32 " DLC: %1d Data:", rx_message.identifier, rx_message.data_length_code); + } - if((rx_message.identifier & 0x40000000) == 0x40000000){ // Determine if message is a remote request frame. - MessageOutput.printf(" REMOTE REQUEST FRAME %s\r\n", rx_message.data); - } else { - - if (_verboseLogging) { - MessageOutput.printf("%s Received CAN message: 0x%04X -", _providerName, rx_message.identifier); - - for (int i = 0; i < rx_message.data_length_code; i++) { - MessageOutput.printf(" %02X", rx_message.data[i]); - } - - MessageOutput.printf("\r\n"); - } - - onMessage(rx_message); - } + if ((rx_message.identifier & 0x40000000) == 0x40000000){ // Determine if message is a remote request frame. + MessageOutput.printf(" REMOTE REQUEST FRAME %s\r\n", rx_message.data); return; } - } else if (pin.provider == Battery_Provider_t::CAN0) { + } else if (_provider == Battery_Provider_t::CAN0) { // Check for messages. twai_receive is blocking when there is no data so we return if there are no frames in the buffer twai_status_info_t status_info; esp_err_t twaiLastResult = twai_get_status_info(&status_info); if (twaiLastResult != ESP_OK) { + MessageOutput.printf("%s Twai driver get status ", _providerName); switch (twaiLastResult) { case ESP_ERR_INVALID_ARG: - MessageOutput.printf("%s Twai driver get status - invalid arg\r\n", _providerName); + MessageOutput.println("- invalid arg"); break; case ESP_ERR_INVALID_STATE: - MessageOutput.printf("%s Twai driver get status - invalid state\r\n", _providerName); + MessageOutput.println("- invalid state"); break; + default:; + MessageOutput.println("- failure"); } return; } + if (status_info.msgs_to_rx == 0) { return; } // Wait for message to be received, function is blocking - twai_message_t rx_message; if (twai_receive(&rx_message, pdMS_TO_TICKS(100)) != ESP_OK) { MessageOutput.printf("%s Failed to receive message\r\n", _providerName); return; } + + } else if (_provider == Battery_Provider_t::I2C0 || _provider == Battery_Provider_t::I2C1) { + + if (CAN_MSGAVAIL != i2c_can->checkReceive()) { + return; + } + + if (CAN_OK != i2c_can->readMsgBuf(&rx_message.data_length_code, rx_message.data)) { // read data, len: data length, buf: data buf + MessageOutput.println("I2C CAN nothing received"); + return; + } + + if (rx_message.data_length_code > 8) { + MessageOutput.printf("I2C CAN received %d bytes\r\n", rx_message.data_length_code); + return; + } + + if (rx_message.data_length_code == 0) { + return; + } + + rx_message.identifier = i2c_can->getCanId(); + rx_message.extd = i2c_can->isExtendedFrame(); + rx_message.rtr = i2c_can->isRemoteRequest(); } if (_verboseLogging) { diff --git a/src/BatteryStats.cpp b/src/BatteryStats.cpp index e470ec491..9c7490782 100644 --- a/src/BatteryStats.cpp +++ b/src/BatteryStats.cpp @@ -346,11 +346,11 @@ void PytesBatteryStats::getLiveViewData(JsonVariant& root) const addLiveViewValue(root, "remainingCapacity", _availableCapacity, "Ah", 0); if (_chargedEnergy != -1) { - addLiveViewValue(root, "chargedEnergy", _chargedEnergy, "kWh", 2); + addLiveViewValue(root, "chargedEnergy", _chargedEnergy, "kWh", 1); } if (_dischargedEnergy != -1) { - addLiveViewValue(root, "dischargedEnergy", _dischargedEnergy, "kWh", 2); + addLiveViewValue(root, "dischargedEnergy", _dischargedEnergy, "kWh", 1); } addLiveViewValue(root, "online", _moduleCountOnline, "Module", 0); diff --git a/src/Configuration.cpp b/src/Configuration.cpp index aaaeeeb5f..cdae818dc 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -280,6 +280,9 @@ bool ConfigurationClass::write() powerlimiter["full_solar_passthrough_soc"] = config.PowerLimiter.FullSolarPassThroughSoc; powerlimiter["full_solar_passthrough_start_voltage"] = config.PowerLimiter.FullSolarPassThroughStartVoltage; powerlimiter["full_solar_passthrough_stop_voltage"] = config.PowerLimiter.FullSolarPassThroughStopVoltage; +#ifdef USE_SURPLUSPOWER + powerlimiter["surplus_power_enabled"] = config.PowerLimiter.SurplusPowerEnabled; +#endif JsonObject battery = doc["battery"].to(); battery["enabled"] = config.Battery.Enabled; @@ -719,6 +722,9 @@ bool ConfigurationClass::read() config.PowerLimiter.FullSolarPassThroughSoc = powerlimiter["full_solar_passthrough_soc"] | POWERLIMITER_FULL_SOLAR_PASSTHROUGH_SOC; config.PowerLimiter.FullSolarPassThroughStartVoltage = powerlimiter["full_solar_passthrough_start_voltage"] | POWERLIMITER_FULL_SOLAR_PASSTHROUGH_START_VOLTAGE; config.PowerLimiter.FullSolarPassThroughStopVoltage = powerlimiter["full_solar_passthrough_stop_voltage"] | POWERLIMITER_FULL_SOLAR_PASSTHROUGH_STOP_VOLTAGE; +#ifdef USE_SURPLUSPOWER + config.PowerLimiter.SurplusPowerEnabled = powerlimiter["surplus_power_enabled"] | false; +#endif JsonObject battery = doc["battery"]; config.Battery.Enabled = battery["enabled"] | BATTERY_ENABLED; diff --git a/src/Huawei_can.cpp b/src/Huawei_can.cpp index 180853e70..89e1637e3 100644 --- a/src/Huawei_can.cpp +++ b/src/Huawei_can.cpp @@ -35,29 +35,6 @@ void HuaweiCanCommunicationTask(void* parameter) { } } -bool HuaweiCanCommClass::enable(void) -{ - twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS(); - twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); - - // Install TWAI driver - if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK) { - MessageOutput.print("Twai driver installed"); - } else { - MessageOutput.println("ERR:Failed to install Twai driver."); - return false; - } - - // Start TWAI driver - if (twai_start() == ESP_OK) { - MessageOutput.print(" and started. "); - } else { - MessageOutput.println(". ERR:Failed to start Twai driver."); - return false; - } - return true; -} - bool HuaweiCanCommClass::init() { if (!PinMapping.isValidChargerConfig()) { @@ -66,27 +43,42 @@ bool HuaweiCanCommClass::init() { } const CHARGER_t& pin = PinMapping.get().charger; - if (pin.provider == Charger_Provider::CAN0) { - _provider = CAN_Provider_t::CAN0; + if (pin.provider == Charger_Provider_t::CAN0) { auto tx = static_cast(pin.can0.tx); auto rx = static_cast(pin.can0.rx); - MessageOutput.printf("CAN0 port rx = %d, tx = %d. ", rx, tx); + MessageOutput.printf("CAN0 port rx = %d, tx = %d\r\n", rx, tx); g_config = TWAI_GENERAL_CONFIG_DEFAULT(tx, rx, TWAI_MODE_NORMAL); - // g_config.bus_off_io = (gpio_num_t)can0_stb; - g_config.bus_off_io = (gpio_num_t)-1; - if (!enable()) + // g_config.bus_off_io = (gpio_num_t)can0_stb; + g_config.intr_flags = ESP_INTR_FLAG_LEVEL2; + + twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS(); + twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + + // Install TWAI driver + if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK) { + MessageOutput.print("Twai driver installed"); + } else { + MessageOutput.println("ERR:Failed to install Twai driver."); return false; + } + + // Start TWAI driver + if (twai_start() == ESP_OK) { + MessageOutput.print(" and started. "); + } else { + MessageOutput.println(". ERR:Failed to start Twai driver."); + return false; + } - } else if (pin.provider == Charger_Provider::I2C0 || pin.provider == Charger_Provider::I2C1) { - _provider = CAN_Provider_t::I2C; + } else if (pin.provider == Charger_Provider_t::I2C0 || pin.provider == Charger_Provider_t::I2C1) { - auto scl = pin.provider == Charger_Provider::I2C0?pin.i2c0.scl:pin.i2c1.scl; - auto sda = pin.provider == Charger_Provider::I2C1?pin.i2c0.sda:pin.i2c1.sda; + auto scl = pin.provider == Charger_Provider_t::I2C0?pin.i2c0.scl:pin.i2c1.scl; + auto sda = pin.provider == Charger_Provider_t::I2C1?pin.i2c0.sda:pin.i2c1.sda; - MessageOutput.printf("I2C CAN Bus @ I2C%d scl = %d, sda = %d. ", pin.i2c0.scl>=0?0:1, scl, sda); + MessageOutput.printf("I2C CAN Bus @ I2C%d scl = %d, sda = %d\r\n", pin.i2c0.scl>=0?0:1, scl, sda); i2c_can = new I2C_CAN(pin.provider == Charger_Provider_t::I2C0?&Wire:&Wire1, 0x25, scl, sda, 400000UL); // Set I2C Address @@ -108,8 +100,7 @@ bool HuaweiCanCommClass::init() { MessageOutput.println("I2C CAN Bus OK!"); - } else if (pin.provider == Charger_Provider::MCP2515) { - _provider = CAN_Provider_t::MCP2515; + } else if (pin.provider == Charger_Provider_t::MCP2515) { auto oSPInum = SPIPortManager.allocatePort("MCP2515"); if (!oSPInum) { return false; } @@ -156,15 +147,16 @@ void HuaweiCanCommClass::loop() twai_message_t rx_message; uint8_t i; bool gotMessage = false; + auto _provider = PinMapping.get().charger.provider; - if (_provider == CAN_Provider_t::MCP2515) { + if (_provider == Charger_Provider_t::MCP2515) { if (!digitalRead(_mcp2515Irq)) { // If CAN_INT pin is low, read receive buffer _CAN->readMsgBuf(&rx_message.identifier, &rx_message.data_length_code, rx_message.data); // Read data: len = data length, buf = data byte(s) gotMessage = true; } - } else if (_provider == CAN_Provider_t::CAN0) { + } else if (_provider == Charger_Provider_t::CAN0) { // Check for messages. twai_recive is blocking when there is no data so we return if there are no frames in the buffer twai_status_info_t status_info; if (twai_get_status_info(&status_info) == ESP_OK) { @@ -182,7 +174,7 @@ void HuaweiCanCommClass::loop() MessageOutput.printf("%s Failed to get Twai status info\r\n", TAG); } - } else if (_provider == CAN_Provider_t::I2C) { + } else if (_provider == Charger_Provider_t::I2C0 || _provider == Charger_Provider_t::I2C1) { if (CAN_MSGAVAIL == i2c_can->checkReceive()) { if (CAN_OK == i2c_can->readMsgBuf(&rx_message.data_length_code, rx_message.data)) { // read data, len: data length, buf: data buf if (rx_message.data_length_code > 0 && rx_message.data_length_code <= 8) { @@ -260,7 +252,8 @@ void HuaweiCanCommClass::loop() } byte HuaweiCanCommClass::sendMsgBuf(uint32_t identifier, uint8_t extd, uint8_t len, uint8_t *data) { - if (_provider == CAN_Provider_t::CAN0) { + auto _provider = PinMapping.get().charger.provider; + if (_provider == Charger_Provider_t::CAN0) { twai_message_t tx_message; memcpy(tx_message.data, data, len); tx_message.extd = extd; @@ -274,9 +267,9 @@ byte HuaweiCanCommClass::sendMsgBuf(uint32_t identifier, uint8_t extd, uint8_t l yield(); MessageOutput.printf("%s Failed to queue message for transmission\r\n", TAG); - } else if (_provider == CAN_Provider_t::MCP2515) { + } else if (_provider == Charger_Provider_t::MCP2515) { return _CAN->sendMsgBuf(identifier, extd, len, data); - } else if (_provider == CAN_Provider_t::I2C) { + } else if (_provider == Charger_Provider_t::I2C0 || _provider == Charger_Provider_t::I2C1) { i2c_can->sendMsgBuf(identifier, extd, len, data); return CAN_OK; } diff --git a/src/Led_Strip.cpp b/src/Led_Strip.cpp index db40b155e..bea975339 100644 --- a/src/Led_Strip.cpp +++ b/src/Led_Strip.cpp @@ -29,10 +29,9 @@ #include #include -#define LED_STRIP_TASK_SIZE (4096) -#define LED_STRIP_TASK_PRIORITY (configMAX_PRIORITIES - 1) +#define LED_STRIP_TASK_SIZE (2048) -#define LED_STRIP_REFRESH_PERIOD_MS (30U) // TODO(skippermeister): add as parameter to led_strip_task +#define LED_STRIP_REFRESH_PERIOD_MS (1000U) // TODO(skippermeister): add as parameter to led_strip_task #define LED_STRIP_NUM_RMT_ITEMS_PER_LED (24U) // Assumes 24 bit color for each led @@ -59,7 +58,7 @@ void LedStripClass::init(Scheduler& scheduler) { MessageOutput.print("Initialize LED WS2812... "); - bool ledRGBActive = false; + cLed = Configuration.get().Led; turnAllOn(); @@ -72,12 +71,10 @@ void LedStripClass::init(Scheduler& scheduler) if (pin.led_rgb >= 0) { MessageOutput.printf("at Pin %d ...", pin.led_rgb); - ledRGBActive = true; + _ledRGBState[0] = LedRGBState_t::OffN; _ledRGBState[1] = LedRGBState_t::OffI; - } - if (ledRGBActive) { // uint32_t b1[_numPixels]; // buf1 = b1; buf1 = reinterpret_cast(malloc(sizeof(uint32_t) * _numPixels)); @@ -101,13 +98,12 @@ void LedStripClass::init(Scheduler& scheduler) xSemaphoreGive(access_semaphore); - BaseType_t task_created = xTaskCreatePinnedToCore(led_strip_task, + BaseType_t task_created = xTaskCreate(led_strip_task, "led_strip_task", LED_STRIP_TASK_SIZE, this, - +6, // LED_STRIP_TASK_PRIORITY, - &led_strip_task_handle, - 1); + tskIDLE_PRIORITY, + &led_strip_task_handle); if (!task_created) { MessageOutput.println("error: creating LED Strip Task"); @@ -188,7 +184,6 @@ void LedStripClass::led_strip_task(void* arg) led_fill_rmt_items_fn led_make_waveform = NULL; - bool make_new_rmt_items = true; bool prev_showing_buf_1 = !strip->showingBuf1; size_t num_items_malloc = (LED_STRIP_NUM_RMT_ITEMS_PER_LED * strip->_numPixels); @@ -220,22 +215,11 @@ void LedStripClass::led_strip_task(void* arg) * and now buf 1 is being shown, it should update the new rmt items array. * Otherwise, no need to update the array */ - if ((prev_showing_buf_1 == true) && (strip->showingBuf1 == false)) { - make_new_rmt_items = true; - } else if ((prev_showing_buf_1 == false) && (strip->showingBuf1 == true)) { - make_new_rmt_items = true; - } else { - make_new_rmt_items = false; - } - - if (make_new_rmt_items) { - if (strip->showingBuf1) { - led_make_waveform(strip->buf1, rmt_items, strip->_numPixels, - strip->wOffset, strip->rOffset, strip->gOffset, strip->bOffset); - } else { - led_make_waveform(strip->buf2, rmt_items, strip->_numPixels, - strip->wOffset, strip->rOffset, strip->gOffset, strip->bOffset); - } + if ((prev_showing_buf_1 == true && strip->showingBuf1 == false) || + (prev_showing_buf_1 == false && strip->showingBuf1 == true)) + { + led_make_waveform(strip->showingBuf1?strip->buf1:strip->buf2, rmt_items, strip->_numPixels, + strip->wOffset, strip->rOffset, strip->gOffset, strip->bOffset); } rmt_write_items((rmt_channel_t)strip->rmtChannel, rmt_items, num_items_malloc, false); @@ -259,8 +243,7 @@ void LedStripClass::led_strip_fill_rmt_items_ws2812(uint32_t* led_strip_buf, rmt uint32_t led_color = led_strip_buf[led_index]; uint8_t bytes[4] = { (uint8_t)((led_color & 0xff0000) >> 16), (uint8_t)((led_color & 0xff00) >> 8), (uint8_t)(led_color & 0xff), (uint8_t)((led_color & 0xff) >> 24) }; for (uint8_t bit = 8; bit != 0; bit--) { - uint8_t bit_set = (bytes[wOffset] >> (bit - 1)) & 1; - if (bit_set) { + if ( (bytes[wOffset] >> (bit - 1)) & 1 ) { LedStripClass::led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index])); } else { LedStripClass::led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index])); @@ -268,8 +251,7 @@ void LedStripClass::led_strip_fill_rmt_items_ws2812(uint32_t* led_strip_buf, rmt rmt_items_index++; } for (uint8_t bit = 8; bit != 0; bit--) { - uint8_t bit_set = (bytes[gOffset] >> (bit - 1)) & 1; - if (bit_set) { + if ( (bytes[gOffset] >> (bit - 1)) & 1 ) { LedStripClass::led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index])); } else { LedStripClass::led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index])); @@ -277,8 +259,7 @@ void LedStripClass::led_strip_fill_rmt_items_ws2812(uint32_t* led_strip_buf, rmt rmt_items_index++; } for (uint8_t bit = 8; bit != 0; bit--) { - uint8_t bit_set = (bytes[bOffset] >> (bit - 1)) & 1; - if (bit_set) { + if ( (bytes[bOffset] >> (bit - 1)) & 1 ) { led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index])); } else { led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index])); @@ -287,8 +268,7 @@ void LedStripClass::led_strip_fill_rmt_items_ws2812(uint32_t* led_strip_buf, rmt } if (wOffset != rOffset) { for (uint8_t bit = 8; bit != 0; bit--) { - uint8_t bit_set = (bytes[wOffset] >> (bit - 1)) & 1; - if (bit_set) { + if ( (bytes[wOffset] >> (bit - 1)) & 1 ) { led_strip_rmt_bit_1_ws2812(&(rmt_items[rmt_items_index])); } else { led_strip_rmt_bit_0_ws2812(&(rmt_items[rmt_items_index])); @@ -317,21 +297,13 @@ bool LedStripClass::setPixelColor(uint32_t pixel_num, uint32_t color) */ bool LedStripClass::clear() { - bool success = true; - - if (showingBuf1) { - memset(buf2, 0, sizeof(uint32_t) * _numPixels); - } else { - memset(buf1, 0, sizeof(uint32_t) * _numPixels); - } + memset(showingBuf1?buf2:buf1, 0, sizeof(uint32_t) * _numPixels); - return success; + return true; } bool LedStripClass::getPixelColor(uint32_t pixel_num, uint32_t color) { - bool get_success = true; - if ((pixel_num > _numPixels) || (!color)) { color = 0; return false; @@ -343,7 +315,7 @@ bool LedStripClass::getPixelColor(uint32_t pixel_num, uint32_t color) color = buf2[pixel_num]; } - return get_success; + return true; } /*! @@ -519,11 +491,11 @@ void LedStripClass::whiteOverRainbow(int whiteSpeed, int whiteLength) clock_t lastTime = millis(); uint32_t firstPixelHue = 0; - const Led_Config_T *cLed = Configuration.get().Led; for (;;) { // Repeat forever (or until a 'break' or 'return') for (int i = 0; i < numPixels(); i++) { // For each pixel in .. if (((i >= tail) && (i <= head)) || // If between head & tail... - ((tail > head) && ((i >= tail) || (i <= head)))) { + ((tail > head) && ((i >= tail) || (i <= head)))) + { setPixelColor(i, Color(0, 0, 0, 255, cLed[i].Brightness).value()); // Set white } else { // else set rainbow int pixelHue = firstPixelHue + (i * 65536L / numPixels()); @@ -553,8 +525,6 @@ void LedStripClass::whiteOverRainbow(int whiteSpeed, int whiteLength) void LedStripClass::pulseWhite(uint8_t wait) { - const Led_Config_T *cLed = Configuration.get().Led; - for (int j = 0; j < 256; j++) { // Ramp up from 0 to 255 // Fill entire strip with white at gamma-corrected brightness level 'j': fill(Color(gamma8(j), gamma8(j), gamma8(j), 0, cLed[j].Brightness).value(), 0, 0); @@ -608,7 +578,6 @@ void LedStripClass::rainbowFade2White(int wait, int rainbowLoops, int whiteLoops } } - const Led_Config_T *cLed = Configuration.get().Led; for (int k = 0; k < whiteLoops; k++) { for (int j = 0; j < 256; j++) { // Ramp up 0 to 255 // Fill entire strip with white at gamma-corrected brightness level 'j': @@ -697,8 +666,6 @@ void LedStripClass::loop() _ledRGBState[0] = LedRGBState_t::OffN; _ledRGBState[1] = LedRGBState_t::OffI; - const Led_Config_T *cLed = Configuration.get().Led; - if (_allMode == LedRGBState_t::On) { const CONFIG_T& config = Configuration.get(); @@ -732,29 +699,25 @@ void LedStripClass::loop() break; case LedRGBState_t::OnN: colorWipe(Color(0, 0, 255, 0, cLed[i].Brightness).value(), 1000, i); // BLUE: Network is On -// colorWipe(Color(0, 255, 0, 0, cLed[i].Brightness).value(), 1000, i); // GREEN: Network is On break; case LedRGBState_t::BlinkN: -// colorWipe(Color(255, 255, 0, 0, cLed[i].Brightness).value(), 1000, i); // YELLOW: Network is Connected -// colorWipe(Color(255, 51, 204, 0, cLed[i].Brightness).value(), 1000, i); // VIOLET: Network is Connected colorWipe(Color(0, 255, 0, 0, cLed[i].Brightness).value(), 1000, i); // GREEN: Network is Connected -// colorWipe(Color(0, 0, 255, 0, cLed[i].Brightness).value(), 1000, i); // BLUE: Network is Connected break; case LedRGBState_t::OffI: colorWipe(Color(255, 0, 0, 0, cLed[i].Brightness).value(), 1000, i); // RED: Inverter is Off -// colorWipe(Color(0, 0, 255, 0, cLed[i].Brightnesss).value(), 1000, i); // BLUE: Inverter is Off break; case LedRGBState_t::OnI: -// colorWipe(Color(255, 255, 0, 0, cLed[i].Brightness).value(), 1000, i); // YELLOW: Inverter is On -// colorWipe(Color(0, 255, 0, 0, cLed[i].Brightness).value(), 1000, i); // GREEN: Inverter is On colorWipe(Color(0, 0, 255, 0, cLed[i].Brightness).value(), 1000, i); // BLUE: Inverter is On break; case LedRGBState_t::BlinkI: colorWipe(Color(255, 102, 0, 0, cLed[i].Brightness).value(), 1000, i); // ORANGE: Inverter is Blink break; case LedRGBState_t::On: + colorWipe(Color(255, 255, 0, 0, cLed[i].Brightness).value(), 1000, i); // YELLOW +// colorWipe(Color(255, 51, 204, 0, cLed[i].Brightness).value(), 1000, i); // VIOLET break; case LedRGBState_t::Off: + colorWipe(Color(0, 0, 0, 0, cLed[i].Brightness).value(), 1000, i); // all off break; } } diff --git a/src/MeanWell_can.cpp b/src/MeanWell_can.cpp index 61c2e570d..7a9f18168 100644 --- a/src/MeanWell_can.cpp +++ b/src/MeanWell_can.cpp @@ -13,7 +13,6 @@ #include "Battery.h" #include "SunPosition.h" #include "PowerMeter.h" -#include "PinMapping.h" #include "Configuration.h" #include #include @@ -23,8 +22,6 @@ #include Preferences preferences; -static constexpr char TAG[] = "[MEANWELL]"; - MeanWellCanClass MeanWellCan; SemaphoreHandle_t xSemaphore = NULL; @@ -34,35 +31,14 @@ MeanWellCanClass::MeanWellCanClass() { } -bool MeanWellCanClass::enable(void) -{ - twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS(); - twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); - - // Install TWAI driver - if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK) { - MessageOutput.print("Twai driver installed"); - } else { - MessageOutput.println("ERR:Failed to install Twai driver."); - return false; - } - - // Start TWAI driver - if (twai_start() == ESP_OK) { - MessageOutput.print(" and started. "); - } else { - MessageOutput.println(". ERR:Failed to start Twai driver."); - return false; - } - return true; -} - static constexpr char sEEPROMwrites[] = "EEPROMwrites"; void MeanWellCanClass::init(Scheduler& scheduler) { MessageOutput.print("Initialize MeanWell AC charger interface... "); + snprintf(_providerName, 31, "[%s %s]", "Meanwell", PinMapping.get().charger.providerName); + _previousMillis = millis(); preferences.begin("OpenDTU", false); @@ -100,40 +76,71 @@ void MeanWellCanClass::updateSettings() return; } - _loopTask.enable(); - if (_initialized) { _setupParameter = true; return; } if (!PinMapping.isValidChargerConfig()) { - MessageOutput.println("Invalid pin config"); + MessageOutput.println("Invalid pin config."); return; } const CHARGER_t& pin = PinMapping.get().charger; if (pin.provider == Charger_Provider_t::CAN0) { - _provider = CAN_Provider_t::CAN0; auto tx = static_cast(pin.can0.tx); auto rx = static_cast(pin.can0.rx); - MessageOutput.printf("CAN0 port rx = %d, tx = %d. ", rx, tx); + MessageOutput.printf("CAN0 port rx = %d, tx = %d.\r\n", rx, tx); - g_config = TWAI_GENERAL_CONFIG_DEFAULT(tx, rx, TWAI_MODE_NORMAL); - // g_config.bus_off_io = (gpio_num_t)can0_stb; - g_config.bus_off_io = (gpio_num_t)-1; - if (!enable()) - return; + twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(tx, rx, TWAI_MODE_NORMAL); + // g_config.bus_off_io = (gpio_num_t)can0_stb; + g_config.intr_flags = ESP_INTR_FLAG_LEVEL2; + + twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS(); + twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + + // Install TWAI driver + MessageOutput.print("Twai driver install"); + switch (twai_driver_install(&g_config, &t_config, &f_config)) { + case ESP_OK: + MessageOutput.print("ed"); + break; + case ESP_ERR_INVALID_ARG: + MessageOutput.println(" - invalid arg."); + return; + case ESP_ERR_NO_MEM: + MessageOutput.println(" - no memory."); + return; + case ESP_ERR_INVALID_STATE: + MessageOutput.println(" - invalid state."); + return; + default:; + MessageOutput.println(" failed."); + return; + } + + // Start TWAI driver + MessageOutput.print(", start"); + switch (twai_start()) { + case ESP_OK: + MessageOutput.println("ed."); + break; + case ESP_ERR_INVALID_STATE: + MessageOutput.println(" - invalid state."); + return; + default:; + MessageOutput.println(" failed."); + return; + } } else if (pin.provider == Charger_Provider_t::I2C0 || pin.provider == Charger_Provider_t::I2C1) { - _provider = CAN_Provider_t::I2C; auto scl = pin.provider == Charger_Provider_t::I2C0?pin.i2c0.scl:pin.i2c1.scl; auto sda = pin.provider == Charger_Provider_t::I2C1?pin.i2c0.sda:pin.i2c1.sda; - MessageOutput.printf("I2C CAN Bus @ I2C%d scl = %d, sda = %d. ", pin.i2c0.scl>=0?0:1, scl, sda); + MessageOutput.printf("I2C CAN Bus @ I2C%d scl = %d, sda = %d.\r\n", pin.i2c0.scl>=0?0:1, scl, sda); i2c_can = new I2C_CAN(pin.provider == Charger_Provider_t::CAN0?&Wire:&Wire1, 0x25, scl, sda, 400000UL); // Set I2C Address @@ -150,9 +157,8 @@ void MeanWellCanClass::updateSettings() MessageOutput.println("I2C CAN Bus OK!"); } else if (pin.provider == Charger_Provider_t::MCP2515) { - _provider = CAN_Provider_t::MCP2515; - MessageOutput.printf("MCP2515 CAN: miso = %d, mosi = %d, clk = %d, irq = %d, cs = %d. ", + MessageOutput.printf("MCP2515 CAN: miso = %d, mosi = %d, clk = %d, irq = %d, cs = %d.\r\n", pin.mcp2515.miso, pin.mcp2515.mosi, pin.mcp2515.clk, pin.mcp2515.irq, pin.mcp2515.cs); auto oSPInum = SPIPortManager.allocatePort("MCP2515"); if (!oSPInum) { return; } @@ -162,16 +168,17 @@ void MeanWellCanClass::updateSettings() pinMode(pin.mcp2515.cs, OUTPUT); digitalWrite(pin.mcp2515.cs, HIGH); - pinMode(pin.mcp2515.irq, INPUT_PULLUP); _mcp2515_irq = pin.mcp2515.irq; + pinMode(_mcp2515_irq, INPUT_PULLUP); auto frequency = Configuration.get().MCP2515.Controller_Frequency; auto mcp_frequency = MCP_8MHZ; - if (16000000UL == frequency) { - mcp_frequency = MCP_16MHZ; } + if (20000000UL == frequency) { mcp_frequency = MCP_20MHZ; } + else if (16000000UL == frequency) { mcp_frequency = MCP_16MHZ; } else if (8000000UL != frequency) { - MessageOutput.printf("%s CAN: unknown frequency %d Hz, using 8 MHz\r\n", TAG, mcp_frequency); + MessageOutput.printf("Unknown frequency %d Hz, using 8 MHz\r\n", mcp_frequency); } + MessageOutput.printf("MCP2515 Quarz = %u Mhz\r\n", (unsigned int)(frequency/1000000UL)); CAN = new MCP_CAN(spi, pin.mcp2515.cs); if (!CAN->begin(MCP_ANY, CAN_125KBPS, mcp_frequency) == CAN_OK) { MessageOutput.println("Error Initializing MCP2515..."); @@ -182,9 +189,11 @@ void MeanWellCanClass::updateSettings() CAN->setMode(MCP_NORMAL); } + _loopTask.enable(); + _initialized = true; - MessageOutput.print("Initialized Successfully! "); + MessageOutput.println("Initialized Successfully!"); } bool MeanWellCanClass::getCanCharger(void) @@ -204,37 +213,39 @@ bool MeanWellCanClass::getCanCharger(void) bool MeanWellCanClass::parseCanPackets(void) { if (xSemaphore == NULL) { - MessageOutput.printf("%s xSemapore not initialized\r\n", TAG); + MessageOutput.printf("%s xSemapore not initialized\r\n", _providerName); return false; } + auto _provider = PinMapping.get().charger.provider; + /* See if we can obtain the semaphore. If the semaphore is not available wait 1000 ticks to see if it becomes free. */ if (xSemaphoreTake(xSemaphore, (TickType_t)1000) == pdTRUE) { twai_message_t rx_message; - if (_provider == CAN_Provider_t::CAN0) { + if (_provider == Charger_Provider_t::CAN0) { // Check for messages. twai_recive is blocking when there is no data so we return if there are no frames in the buffer twai_status_info_t status_info; if (twai_get_status_info(&status_info) != ESP_OK) { - MessageOutput.printf("%s Failed to get Twai status info\r\n", TAG); + MessageOutput.printf("%s Failed to get Twai status info\r\n", _providerName); xSemaphoreGive(xSemaphore); return false; } if (status_info.msgs_to_rx == 0) { - // MessageOutput.printf("%s no message received\r\n", TAG); + // MessageOutput.printf("%s no message received\r\n", _providerName); xSemaphoreGive(xSemaphore); return false; } // Wait for message to be received, function is blocking if (twai_receive(&rx_message, pdMS_TO_TICKS(100)) != ESP_OK) { - MessageOutput.printf("%s Failed to receive message\r\n", TAG); + MessageOutput.printf("%s Failed to receive message\r\n", _providerName); xSemaphoreGive(xSemaphore); return false; } - } else if (_provider == CAN_Provider_t::I2C) { + } else if (_provider == Charger_Provider_t::I2C0 || _provider == Charger_Provider_t::I2C1) { if (CAN_MSGAVAIL != i2c_can->checkReceive()) { xSemaphoreGive(xSemaphore); @@ -262,28 +273,39 @@ bool MeanWellCanClass::parseCanPackets(void) rx_message.extd = i2c_can->isExtendedFrame(); rx_message.rtr = i2c_can->isRemoteRequest(); - } else if (_provider == CAN_Provider_t::MCP2515) { + } else if (_provider == Charger_Provider_t::MCP2515) { if (digitalRead(_mcp2515_irq)) { - // MessageOutput.printf("%s no message received\r\n", TAG); + // MessageOutput.printf("%s no message received\r\n", _providerName); xSemaphoreGive(xSemaphore); return false; } // If CAN_INT pin is low, read receive buffer CAN->readMsgBuf(&rx_message.identifier, &rx_message.data_length_code, rx_message.data); // Read data: len = data length, buf = data byte(s) + + if (_verboseLogging) { + if((rx_message.identifier & 0x80000000) == 0x80000000) // Determine if ID is standard (11 bits) or extended (29 bits) + MessageOutput.printf("Extended ID: 0x%.8" PRIx32 " DLC: %1d Data:", rx_message.identifier & 0x1FFFFFFF, rx_message.data_length_code); + else + MessageOutput.printf("Standard ID: 0x%.3" PRIx32 " DLC: %1d Data:", rx_message.identifier, rx_message.data_length_code); + } + if ((rx_message.identifier & 0x40000000) == 0x40000000){ // Determine if message is a remote request frame. + MessageOutput.printf(" REMOTE REQUEST FRAME %s\r\n", rx_message.data); + return false; + } } _meanwellLastResponseTime = millis(); // save last response time yield(); if (_verboseLogging) - MessageOutput.printf("%s id: 0x%08X, extd: %d, data len: %d bytes\r\n", TAG, + MessageOutput.printf("%s id: 0x%08X, extd: %d, data len: %d bytes\r\n", _providerName, rx_message.identifier, rx_message.extd, rx_message.data_length_code); switch (rx_message.identifier & 0xFFFFFF00) { case 0x000C0100: - // MessageOutput.printf("%s 0x%08X len: %d\r\n", TAG, rx_message.identifier, rx_message.data_length_code); + // MessageOutput.printf("%s 0x%08X len: %d\r\n", _providerName, rx_message.identifier, rx_message.data_length_code); xSemaphoreGive(xSemaphore); return false; case 0x000C0000: @@ -294,7 +316,7 @@ bool MeanWellCanClass::parseCanPackets(void) return true; } - MessageOutput.printf("%s xSemapore not free\r\n", TAG); + MessageOutput.printf("%s xSemapore not free\r\n", _providerName); return false; } @@ -371,7 +393,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.operation = *(frame + 2); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s Operation: %02X %s\r\n", TAG, _rp.operation, _rp.operation ? "On" : "Off"); + if (_verboseLogging) MessageOutput.printf("%s Operation: %02X %s\r\n", _providerName, _rp.operation, _rp.operation ? "On" : "Off"); #endif break; @@ -379,7 +401,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.outputVoltageSet = scaleValue(readUnsignedInt16(frame + 2), 0.01f); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s OutputVoltage(VOUT_SET): %.2fV\r\n", TAG, _rp.outputVoltageSet); + if (_verboseLogging) MessageOutput.printf("%s OutputVoltage(VOUT_SET): %.2fV\r\n", _providerName, _rp.outputVoltageSet); #endif break; @@ -387,7 +409,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.outputCurrentSet = scaleValue(readUnsignedInt16(frame + 2), 0.01f); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s OutputCurrent(IOUT_SET): %.2fA\r\n", TAG, _rp.outputCurrentSet); + if (_verboseLogging) MessageOutput.printf("%s OutputCurrent(IOUT_SET): %.2fA\r\n", _providerName, _rp.outputCurrentSet); #endif break; @@ -396,7 +418,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED if (_verboseLogging) - MessageOutput.printf("%s FAULT_STATUS : %s : HI_TEMP: %d, OP_OFF: %d, AC_FAIL: %d, SHORT: %d, OLP: %d, OVP: %d, OTP: %d\r\n", TAG, + MessageOutput.printf("%s FAULT_STATUS : %s : HI_TEMP: %d, OP_OFF: %d, AC_FAIL: %d, SHORT: %d, OLP: %d, OVP: %d, OTP: %d\r\n", _providerName, Word2BinaryString(_rp.FaultStatus), _rp.FAULT_STATUS.HI_TEMP, _rp.FAULT_STATUS.OP_OFF, @@ -413,7 +435,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) if (_model < NPB_Model_t::NPB_1200_24) _rp.inputVoltage = 230.0; _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s InputVoltage: %.1fV\r\n", TAG, _rp.inputVoltage); + if (_verboseLogging) MessageOutput.printf("%s InputVoltage: %.1fV\r\n", _providerName, _rp.inputVoltage); #endif break; @@ -422,7 +444,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) calcPower(); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s OutputVoltage: %.2fV\r\n", TAG, _rp.outputVoltage); + if (_verboseLogging) MessageOutput.printf("%s OutputVoltage: %.2fV\r\n", _providerName, _rp.outputVoltage); #endif break; @@ -431,7 +453,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) calcPower(); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s OutputCurrent: %.2fA\r\n", TAG, _rp.outputCurrent); + if (_verboseLogging) MessageOutput.printf("%s OutputCurrent: %.2fA\r\n", _providerName, _rp.outputCurrent); #endif break; @@ -439,7 +461,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.internalTemperature = scaleValue(readSignedInt16(frame + 2), 0.1f); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s Temperature: %.1f°C\r\n", TAG, _rp.internalTemperature); + if (_verboseLogging) MessageOutput.printf("%s Temperature: %.1f°C\r\n", _providerName, _rp.internalTemperature); #endif break; @@ -457,7 +479,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) } _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s Manufacturer Name: '%s'\r\n", TAG, _rp.ManufacturerName); + if (_verboseLogging) MessageOutput.printf("%s Manufacturer Name: '%s'\r\n", _providerName, _rp.ManufacturerName); #endif break; @@ -542,7 +564,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) //#ifdef MEANWELL_DEBUG_ENABLED //if (_verboseLogging) - MessageOutput.printf("%s Manufacturer Model Name: '%s' %d\r\n", TAG, _rp.ManufacturerModelName, static_cast(_model)); + MessageOutput.printf("%s Manufacturer Model Name: '%s' %d\r\n", _providerName, _rp.ManufacturerModelName, static_cast(_model)); //#endif } break; @@ -561,7 +583,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) hex[i * 2] = hn > 9 ? 'A' + hn - 10 : '0' + hn; hex[i * 2 + 1] = ln > 9 ? 'A' + ln - 10 : '0' + ln; } - MessageOutput.printf("%s Firmware Revision: '%s'\r\n", TAG, hex); + MessageOutput.printf("%s Firmware Revision: '%s'\r\n", _providerName, hex); } #endif break; @@ -570,7 +592,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) strncpy(reinterpret_cast(_rp.ManufacturerFactoryLocation), reinterpret_cast(frame + 2), 3); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s Manufacturer Factory Location: '%s'\r\n", TAG, _rp.ManufacturerFactoryLocation); + if (_verboseLogging) MessageOutput.printf("%s Manufacturer Factory Location: '%s'\r\n", _providerName, _rp.ManufacturerFactoryLocation); #endif break; @@ -578,7 +600,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) strncpy(reinterpret_cast(_rp.ManufacturerDate), reinterpret_cast(frame + 2), 6); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s Manufacturer Date: '%s'\r\n", TAG, _rp.ManufacturerDate); + if (_verboseLogging) MessageOutput.printf("%s Manufacturer Date: '%s'\r\n", _providerName, _rp.ManufacturerDate); #endif break; @@ -590,7 +612,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) strncpy(reinterpret_cast(&(_rp.ProductSerialNo[6])), reinterpret_cast(frame + 2), 6); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s Product Serial No '%s'\r\n", TAG, _rp.ProductSerialNo); + if (_verboseLogging) MessageOutput.printf("%s Product Serial No '%s'\r\n", _providerName, _rp.ProductSerialNo); #endif break; @@ -598,7 +620,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.curveCC = scaleValue(readUnsignedInt16(frame + 2), 0.01f); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s CurveCC: %.2fA\r\n", TAG, _rp.curveCC); + if (_verboseLogging) MessageOutput.printf("%s CurveCC: %.2fA\r\n", _providerName, _rp.curveCC); #endif break; @@ -606,7 +628,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.curveCV = scaleValue(readUnsignedInt16(frame + 2), 0.01f); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s CurveCV: %.2fV\r\n", TAG, _rp.curveCV); + if (_verboseLogging) MessageOutput.printf("%s CurveCV: %.2fV\r\n", _providerName, _rp.curveCV); #endif break; @@ -614,7 +636,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.curveFV = scaleValue(readUnsignedInt16(frame + 2), 0.01f); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s CurveFV: %.2fV\r\n", TAG, _rp.curveFV); + if (_verboseLogging) MessageOutput.printf("%s CurveFV: %.2fV\r\n", _providerName, _rp.curveFV); #endif break; @@ -622,7 +644,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.curveTC = scaleValue(readUnsignedInt16(frame + 2), 0.01f); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s CurveTC: %.2fA\r\n", TAG, _rp.curveTC); + if (_verboseLogging) MessageOutput.printf("%s CurveTC: %.2fA\r\n", _providerName, _rp.curveTC); #endif break; @@ -631,7 +653,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED if (_verboseLogging) - MessageOutput.printf("%s CURVE_CONFIG : %s : CUVE: %d, STGS: %d, TCS: %d, CUVS: %X\r\n", TAG, + MessageOutput.printf("%s CURVE_CONFIG : %s : CUVE: %d, STGS: %d, TCS: %d, CUVS: %X\r\n", _providerName, Word2BinaryString(_rp.CurveConfig), _rp.CURVE_CONFIG.CUVE, _rp.CURVE_CONFIG.STGS, @@ -644,7 +666,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.curveCC_Timeout = readUnsignedInt16(frame + 2); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s CurveCC_Timeout: %d minutes\r\n", TAG, _rp.curveCC_Timeout); + if (_verboseLogging) MessageOutput.printf("%s CurveCC_Timeout: %d minutes\r\n", _providerName, _rp.curveCC_Timeout); #endif break; @@ -652,7 +674,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.curveCV_Timeout = readUnsignedInt16(frame + 2); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s CurveCV_Timeout: %d minutes\r\n", TAG, _rp.curveCV_Timeout); + if (_verboseLogging) MessageOutput.printf("%s CurveCV_Timeout: %d minutes\r\n", _providerName, _rp.curveCV_Timeout); #endif break; @@ -660,7 +682,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.curveFV_Timeout = readUnsignedInt16(frame + 2); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s CurveFV_Timeout: %d minutes\r\n", TAG, _rp.curveFV_Timeout); + if (_verboseLogging) MessageOutput.printf("%s CurveFV_Timeout: %d minutes\r\n", _providerName, _rp.curveFV_Timeout); #endif break; @@ -669,7 +691,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED if (_verboseLogging) - MessageOutput.printf("%s CHG_STATUS : %s : BTNC: %d, WAKUP_STOP: %d, FVM: %d, CVM: %d, CCM: %d, FULLM: %d\r\n", TAG, + MessageOutput.printf("%s CHG_STATUS : %s : BTNC: %d, WAKUP_STOP: %d, FVM: %d, CVM: %d, CCM: %d, FULLM: %d\r\n", _providerName, Word2BinaryString(_rp.ChargeStatus), _rp.CHG_STATUS.BTNC, _rp.CHG_STATUS.WAKEUP_STOP, @@ -683,7 +705,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) case 0x00B9: // CHG_RST_VBAT 2 bytes The voltage Rest to art the charging after the battery is fully { uint16_t ChgRstVbat = readUnsignedInt16(frame + 2); - if (_verboseLogging) MessageOutput.printf("%s CHG_RST_VBAT: %d\r\n", TAG, ChgRstVbat); + if (_verboseLogging) MessageOutput.printf("%s CHG_RST_VBAT: %d\r\n", _providerName, ChgRstVbat); } break; @@ -691,7 +713,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _rp.scalingFactor = readUnsignedInt16(frame + 2); _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED - if (_verboseLogging) MessageOutput.printf("%s ScalingFactor: %d, %04X\r\n", TAG, _rp.scalingFactor, _rp.scalingFactor); + if (_verboseLogging) MessageOutput.printf("%s ScalingFactor: %d, %04X\r\n", _providerName, _rp.scalingFactor, _rp.scalingFactor); #endif break; @@ -700,7 +722,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) _lastUpdate = millis(); #ifdef MEANWELL_DEBUG_ENABLED if (_verboseLogging) - MessageOutput.printf("%s SYSTEM_STATUS : %s : EEPER: %d, INITIAL_STATE: %d, DC_OK: %d\r\n", TAG, + MessageOutput.printf("%s SYSTEM_STATUS : %s : EEPER: %d, INITIAL_STATE: %d, DC_OK: %d\r\n", _providerName, Word2BinaryString(_rp.SystemStatus), _rp.SYSTEM_STATUS.EEPER, _rp.SYSTEM_STATUS.INITIAL_STATE, @@ -719,7 +741,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) "Power on with the last setting", "No used" }; - MessageOutput.printf("%s SYSTEM_CONFIG : %s : Inital operational behavior: %s, EEPROM write disable: %d\r\n", TAG, + MessageOutput.printf("%s SYSTEM_CONFIG : %s : Inital operational behavior: %s, EEPROM write disable: %d\r\n", _providerName, Word2BinaryString(_rp.SystemConfig), OperationInit[_rp.SYSTEM_CONFIG.OPERATION_INIT], _rp.SYSTEM_CONFIG.EEP_OFF); @@ -728,7 +750,7 @@ void MeanWellCanClass::onReceive(uint8_t* frame, uint8_t len) break; default:; - MessageOutput.printf("%s CAN: Unknown Command %04X, len %d\r\n", TAG, readUnsignedInt16(frame), len); + MessageOutput.printf("%s CAN: Unknown Command %04X, len %d\r\n", _providerName, readUnsignedInt16(frame), len); } } @@ -745,7 +767,7 @@ void MeanWellCanClass::setupParameter() bool temp = _verboseLogging; _verboseLogging = true; - if (_verboseLogging) MessageOutput.printf("%s read parameter\r\n", TAG); + if (_verboseLogging) MessageOutput.printf("%s read parameter\r\n", _providerName); // Switch Charger off _rp.operation = 0; // Operation OFF @@ -821,7 +843,7 @@ void MeanWellCanClass::setupParameter() yield(); _rp.SystemConfig = 0b0000000000000001; // Initial operation with power on 00: Power Supply is OFF _rp.SYSTEM_CONFIG.EEP_OFF = 1; // disable realtime writing to EEPROM - MessageOutput.printf("%s SystemConfig: %s\r\n", TAG, Word2BinaryString(_rp.SystemConfig)); + MessageOutput.printf("%s SystemConfig: %s\r\n", _providerName, Word2BinaryString(_rp.SystemConfig)); sendCmd(ChargerID, 0x00C2, reinterpret_cast(&_rp.SystemConfig), 2); // read SYSTEM_CONFIG vTaskDelay(100); // delay 200 tick yield(); @@ -844,7 +866,7 @@ void MeanWellCanClass::setupParameter() readCmd(ChargerID, 0x0040); // read Fault Status yield(); - if (_verboseLogging) MessageOutput.printf("%s done\r\n", TAG); + if (_verboseLogging) MessageOutput.printf("%s done\r\n", _providerName); _verboseLogging = temp; @@ -858,7 +880,7 @@ void MeanWellCanClass::updateEEPROMwrites2NVS() { // test and update every 60 Minutes if (millis() - lastupdated > 60*1000*60) { if (EEPROMwrites != preferences.getULong(sEEPROMwrites)) { - MessageOutput.printf("%s update EEPROMwrites=%u in NVS storage\r\n", TAG, EEPROMwrites); + MessageOutput.printf("%s update EEPROMwrites=%u in NVS storage\r\n", _providerName, EEPROMwrites); preferences.putULong(sEEPROMwrites, EEPROMwrites); } lastupdated = millis(); @@ -893,7 +915,7 @@ void MeanWellCanClass::loop() 0x0000, // read ON/OFF Status 0x0040 // read FAULT_STATUS }; - if (_verboseLogging) MessageOutput.printf("%s State: %d\r\n", TAG, state); + if (_verboseLogging) MessageOutput.printf("%s State: %d\r\n", _providerName, state); readCmd(ChargerID, Cmnds[state++]); if (state >= sizeof(Cmnds)/sizeof(uint16_t)) state = 0; @@ -933,7 +955,7 @@ void MeanWellCanClass::loop() // static_cast(inv0->Statistics()->getChannelFieldDigits(TYPE_AC, CH0, FLD_PAC))); float GridPower = PowerMeter.getPowerTotal(); if (_verboseLogging) - MessageOutput.printf("%s %lu ms, House Power: %.1fW, Grid Power: %.1fW, Inverter (%s) Day Power: %.1fW, Batt con. Inverter (%s), Charger Power: %.1fW\r\n", TAG, + MessageOutput.printf("%s %lu ms, House Power: %.1fW, Grid Power: %.1fW, Inverter (%s) Day Power: %.1fW, Batt con. Inverter (%s), Charger Power: %.1fW\r\n", _providerName, millis() - t_start, PowerMeter.getHousePower(), GridPower, invName.c_str(), InverterPower, BattInvName.c_str(), _rp.outputPower); if (!Battery.initialized()) @@ -941,7 +963,7 @@ void MeanWellCanClass::loop() if (_automaticCharge) { if (_verboseLogging) - MessageOutput.printf("%s automatic mode, it's %s, SOC: %.1f%%, %s%s%scharge%sabled, ChargeTemperatur is %svalid, %s is %sproducing, %s is %sproducing, Charger is %s", TAG, + MessageOutput.printf("%s automatic mode, it's %s, SOC: %.1f%%, %s%s%scharge%sabled, ChargeTemperatur is %svalid, %s is %sproducing, %s is %sproducing, Charger is %s", _providerName, SunPosition.isDayPeriod() ? "day" : "night", Battery.getStats()->getSoC(), Battery.getStats()->getAlarm().overVoltage ? "alarmOverVoltage, " : "", @@ -1028,13 +1050,13 @@ void MeanWellCanClass::loop() ) ) { - if (_verboseLogging) MessageOutput.printf("%s Immediate Charge requested", TAG); + if (_verboseLogging) MessageOutput.printf("%s Immediate Charge requested", _providerName); setValue(config.MeanWell.MaxCurrent, MEANWELL_SET_CURRENT); setValue(config.MeanWell.MaxCurrent, MEANWELL_SET_CURVE_CC); _chargeImmediateRequested = true; } else { // Zero Grid Export Charging Algorithm (Charger consums at operation minimum 180 Watt = 3.6A*50V) - if (_verboseLogging) MessageOutput.printf("%s Zero Grid Charger controller", TAG); + if (_verboseLogging) MessageOutput.printf("%s Zero Grid Charger controller", _providerName); float pCharger = _rp.outputCurrentSet * Battery.getStats()->getVoltage(); if (_rp.outputPower > 0.0f) pCharger = _rp.outputPower; if (GridPower - _rp.outputPower < -(pCharger + config.MeanWell.Hysteresis)) { // 25 Watt Hysteresic @@ -1140,7 +1162,7 @@ void MeanWellCanClass::loop() exit:; - MessageOutput.printf("%s Round trip %lu ms\r\n", TAG, millis() - t_start); + MessageOutput.printf("%s Round trip %lu ms\r\n", _providerName, millis() - t_start); } void MeanWellCanClass::switchChargerOff(const char* reason) @@ -1170,7 +1192,7 @@ void MeanWellCanClass::setValue(float in, uint8_t parameterType) const char* type[] = { "Voltage", "Current", "Curve_CV", "Curve_CC", "Curve_FV", "Curve_TC" }; const char unit[] = { 'V', 'A', 'V', 'A', 'V', 'A' }; if (_verboseLogging) - MessageOutput.printf("%s setValue %s: %.2f%c ... ", TAG, type[parameterType], in, unit[parameterType]); + MessageOutput.printf("%s setValue %s: %.2f%c ... ", _providerName, type[parameterType], in, unit[parameterType]); const MeanWell_CONFIG_T& cMeanWell = Configuration.get().MeanWell; @@ -1262,7 +1284,7 @@ void MeanWellCanClass::setValue(float in, uint8_t parameterType) void MeanWellCanClass::setPower(bool power) { if (_verboseLogging) - MessageOutput.printf("%s setPower %s\r\n", TAG, power ? "on" : "off"); + MessageOutput.printf("%s setPower %s\r\n", _providerName, power ? "on" : "off"); /* if (_meanwell_power > 0) { @@ -1360,6 +1382,8 @@ bool MeanWellCanClass::_sendCmd(uint8_t id, uint16_t cmd, uint8_t* data, int len tx_message.data_length_code = len + 2; tx_message.identifier = 0x000C0100 | id; + auto _provider = PinMapping.get().charger.provider; + if (_verboseLogging) { char data[32]; int i, j=0; @@ -1373,13 +1397,11 @@ bool MeanWellCanClass::_sendCmd(uint8_t id, uint16_t cmd, uint8_t* data, int len data[j++] = c; } data[j]=0; - int i2cChannel = PinMapping.get().charger.i2c0.scl >= 0 ? 0 : - PinMapping.get().charger.i2c1.scl >= 0 ? 1 : -1; MessageOutput.printf("%s: id: %08X extd: %d len: %d data:[%s]\r\n", - _provider == CAN_Provider_t::CAN0 ? "CAN0": - _provider == CAN_Provider_t::I2C && i2cChannel == 0 ? "I2C0 CAN": - _provider == CAN_Provider_t::I2C && i2cChannel == 1 ? "I2C1 CAN": - _provider == CAN_Provider_t::MCP2515 ? "MCP2515 CAN":"MeanWell unknown CAN Bus provider", + _provider == Charger_Provider_t::CAN0 ? "CAN0": + _provider == Charger_Provider_t::I2C0 ? "I2C0 CAN": + _provider == Charger_Provider_t::I2C1 ? "I2C1 CAN": + _provider == Charger_Provider_t::MCP2515 ? "MCP2515 CAN":"MeanWell unknown CAN Bus provider", tx_message.identifier, tx_message.extd, tx_message.data_length_code, data); } @@ -1388,33 +1410,33 @@ bool MeanWellCanClass::_sendCmd(uint8_t id, uint16_t cmd, uint8_t* data, int len if (packetMarginTime < 5) vTaskDelay(5 - packetMarginTime); // ensure minimum packet time of 5ms between last response and new request - if (_provider == CAN_Provider_t::CAN0) { + if (_provider == Charger_Provider_t::CAN0) { // Queue message for transmission if (twai_transmit(&tx_message, pdMS_TO_TICKS(1000)) == ESP_OK) { yield(); #ifdef MEANWELL_DEBUG_ENABLED__ if (_verboseLogging) - MessageOutput.printf("%s Message queued for transmission cmnd %04X with %d data bytes\r\n", TAG, cmd, len + 2); + MessageOutput.printf("%s Message queued for transmission cmnd %04X with %d data bytes\r\n", _providerName, cmd, len + 2); #endif if (len>0) EEPROMwrites++; } else { yield(); - MessageOutput.printf("%s Failed to queue message for transmission\r\n", TAG); + MessageOutput.printf("%s Failed to queue message for transmission\r\n", _providerName); return false; } - } else if (_provider == CAN_Provider_t::I2C) { + } else if (_provider == Charger_Provider_t::I2C0 || _provider == Charger_Provider_t::I2C1 ) { i2c_can->sendMsgBuf(tx_message.identifier, (uint8_t)tx_message.extd, tx_message.data_length_code, tx_message.data); - } else if (_provider == CAN_Provider_t::MCP2515) { + } else if (_provider == Charger_Provider_t::MCP2515) { uint8_t sndStat = CAN->sendMsgBuf(tx_message.identifier, (uint8_t)tx_message.extd, tx_message.data_length_code, tx_message.data); if (sndStat == CAN_OK) { - // MessageOutput.printf("%s Message Sent Successfully!\r\n", TAG); + // MessageOutput.printf("%s Message Sent Successfully!\r\n", _providerName); } else { - MessageOutput.printf("%s Error Sending Message... Status: %d\r\n", TAG, sndStat); + MessageOutput.printf("%s Error Sending Message... Status: %d\r\n", _providerName, sndStat); } } @@ -1432,7 +1454,7 @@ bool MeanWellCanClass::readCmd(uint8_t id, uint16_t cmd) yield(); semaphore++; if (semaphore > 1) - MessageOutput.printf("%s Semaphore = %d\r\n", TAG, semaphore); + MessageOutput.printf("%s Semaphore = %d\r\n", _providerName, semaphore); if (_sendCmd(id, cmd) == false) { // read parameter semaphore--; diff --git a/src/MqttHandleBatteryHass.cpp b/src/MqttHandleBatteryHass.cpp index 9440b738a..02a1f5eb4 100644 --- a/src/MqttHandleBatteryHass.cpp +++ b/src/MqttHandleBatteryHass.cpp @@ -65,12 +65,11 @@ void MqttHandleBatteryHassClass::publishConfig() // battery info switch (config.Battery.Provider) { + case 0: // Pylontech Battery via CAN0, MCP2515, I2C0, I2C1, RS485 #ifdef USE_PYLONTECH_CAN_RECEIVER - case 1: // Pylontech Battery CAN0 - case 2: // Pylontech Battery CAN MCP2515 - publishSensor("State of Health (SOH)", "mdi:heart-plus", "stateOfHealth", NULL, "measurement", "%"); + if (PinMapping.get().battery.provider != Battery_Provider_t::RS485) + publishSensor("State of Health (SOH)", "mdi:heart-plus", "stateOfHealth", NULL, "measurement", "%"); #endif - case 0: // Pylontech Battery RS485 publishSensor("Battery voltage", NULL, "voltage", "voltage", "measurement", "V"); publishSensor("Battery current", NULL, "current", "current", "measurement", "A"); publishSensor("Total Capacity", NULL, "capacity", "capacity", "measurement", "Ah"); @@ -111,61 +110,12 @@ void MqttHandleBatteryHassClass::publishConfig() #undef PBSW #undef PBSA break; - -#ifdef USE_JKBMS_CONTROLLER - case 3: // JK BMS - // caption icon topic dev. class state class unit - publishSensor("Voltage", "mdi:battery-charging", "BatteryVoltageMilliVolt", "voltage", "measurement", "mV"); - publishSensor("Current", "mdi:current-dc", "BatteryCurrentMilliAmps", "current", "measurement", "mA"); - publishSensor("BMS Temperature", "mdi:thermometer", "BmsTempCelsius", "temperature", "measurement", "°C"); - publishSensor("Cell Voltage Diff", "mdi:battery-alert", "CellDiffMilliVolt", "voltage", "measurement", "mV"); - publishSensor("Charge Cycles", "mdi:counter", "BatteryCycles"); - publishSensor("Cycle Capacity", "mdi:battery-sync", "BatteryCycleCapacity"); - - publishBinarySensor("Charging Possible", "mdi:battery-arrow-up", "status/ChargingActive", "1", "0"); - publishBinarySensor("Discharging Possible", "mdi:battery-arrow-down", "status/DischargingActive", "1", "0"); - publishBinarySensor("Balancing Active", "mdi:scale-balance", "status/BalancingActive", "1", "0"); -#define PBS(a, b, c) publishBinarySensor("Alarm: " a, "mdi:" b, "alarms/" c, "1", "0") - PBS("Low Capacity", "battery-alert-variant-outline", "LowCapacity"); - PBS("BMS Overtemperature", "thermometer-alert", "BmsOvertemperature"); - PBS("Charging Overvoltage", "fuse-alert", "ChargingOvervoltage"); - PBS("Discharge Undervoltage", "fuse-alert", "DischargeUndervoltage"); - PBS("Battery Overtemperature", "thermometer-alert", "BatteryOvertemperature"); - PBS("Charging Overcurrent", "fuse-alert", "ChargingOvercurrent"); - PBS("Discharging Overcurrent", "fuse-alert", "DischargeOvercurrent"); - PBS("Cell Voltage Difference", "battery-alert", "CellVoltageDifference"); - PBS("Battery Box Overtemperature", "thermometer-alert", "BatteryBoxOvertemperature"); - PBS("Battery Undertemperature", "thermometer-alert", "BatteryUndertemperature"); - PBS("Cell Overvoltage", "battery-alert", "CellOvervoltage"); - PBS("Cell Undervoltage", "battery-alert", "CellUndervoltage"); -#undef PBS - break; -#endif -#ifdef USE_VICTRON_SMART_SHUNT - case 4: // Victron SmartShunt - publishSensor("Voltage", "mdi:battery-charging", "voltage", "voltage", "measurement", "V"); - publishSensor("Current", "mdi:current-dc", "current", "current", "measurement", "A"); - publishSensor("Instantaneous Power", NULL, "instantaneousPower", "power", "measurement", "W"); - publishSensor("Charged Energy", NULL, "chargedEnergy", "energy", "total_increasing", "kWh"); - publishSensor("Discharged Energy", NULL, "dischargedEnergy", "energy", "total_increasing", "kWh"); - publishSensor("Charge Cycles", "mdi:counter", "chargeCycles"); - publishSensor("Consumed Amp Hours", NULL, "consumedAmpHours", NULL, "measurement", "Ah"); - publishSensor("Last Full Charge", "mdi:timelapse", "lastFullCharge", NULL, NULL, "min"); - publishSensor("Midpoint Voltage", NULL, "midpointVoltage", "voltage", "measurement", "V"); - publishSensor("Midpoint Deviation", NULL, "midpointDeviation", "battery", "measurement", "%"); - break; -#endif -#ifdef USE_DALYBMS_CONTROLLER - case 5: // DALY BMS - break; -#endif #ifdef USE_MQTT_BATTERY case 6: // SoC from MQTT break; #endif #ifdef USE_PYTES_CAN_RECEIVER - case 7: // Pytes Battery CAN0 - case 8: // Pytes Battery MCP2515 + case 1: // Pytes Battery via CAN0, MCP2515, I2C0, I2C1 publishSensor("Charge voltage (BMS)", NULL, "settings/chargeVoltage", "voltage", "measurement", "V"); publishSensor("Charge current limit", NULL, "settings/chargeCurrentLimitation", "current", "measurement", "A"); publishSensor("Discharge current limit", NULL, "settings/dischargeCurrentLimitation", "current", "measurement", "A"); @@ -221,6 +171,54 @@ void MqttHandleBatteryHassClass::publishConfig() publishBinarySensor("Warning Cell Imbalance", "mdi:alert-outline", "warning/cellImbalance", "1", "0"); break; #endif + +#ifdef USE_JKBMS_CONTROLLER + case 3: // JK BMS + // caption icon topic dev. class state class unit + publishSensor("Voltage", "mdi:battery-charging", "BatteryVoltageMilliVolt", "voltage", "measurement", "mV"); + publishSensor("Current", "mdi:current-dc", "BatteryCurrentMilliAmps", "current", "measurement", "mA"); + publishSensor("BMS Temperature", "mdi:thermometer", "BmsTempCelsius", "temperature", "measurement", "°C"); + publishSensor("Cell Voltage Diff", "mdi:battery-alert", "CellDiffMilliVolt", "voltage", "measurement", "mV"); + publishSensor("Charge Cycles", "mdi:counter", "BatteryCycles"); + publishSensor("Cycle Capacity", "mdi:battery-sync", "BatteryCycleCapacity"); + + publishBinarySensor("Charging Possible", "mdi:battery-arrow-up", "status/ChargingActive", "1", "0"); + publishBinarySensor("Discharging Possible", "mdi:battery-arrow-down", "status/DischargingActive", "1", "0"); + publishBinarySensor("Balancing Active", "mdi:scale-balance", "status/BalancingActive", "1", "0"); +#define PBS(a, b, c) publishBinarySensor("Alarm: " a, "mdi:" b, "alarms/" c, "1", "0") + PBS("Low Capacity", "battery-alert-variant-outline", "LowCapacity"); + PBS("BMS Overtemperature", "thermometer-alert", "BmsOvertemperature"); + PBS("Charging Overvoltage", "fuse-alert", "ChargingOvervoltage"); + PBS("Discharge Undervoltage", "fuse-alert", "DischargeUndervoltage"); + PBS("Battery Overtemperature", "thermometer-alert", "BatteryOvertemperature"); + PBS("Charging Overcurrent", "fuse-alert", "ChargingOvercurrent"); + PBS("Discharging Overcurrent", "fuse-alert", "DischargeOvercurrent"); + PBS("Cell Voltage Difference", "battery-alert", "CellVoltageDifference"); + PBS("Battery Box Overtemperature", "thermometer-alert", "BatteryBoxOvertemperature"); + PBS("Battery Undertemperature", "thermometer-alert", "BatteryUndertemperature"); + PBS("Cell Overvoltage", "battery-alert", "CellOvervoltage"); + PBS("Cell Undervoltage", "battery-alert", "CellUndervoltage"); +#undef PBS + break; +#endif +#ifdef USE_DALYBMS_CONTROLLER + case 4: // DALY BMS + break; +#endif +#ifdef USE_VICTRON_SMART_SHUNT + case 5: // Victron SmartShunt + publishSensor("Voltage", "mdi:battery-charging", "voltage", "voltage", "measurement", "V"); + publishSensor("Current", "mdi:current-dc", "current", "current", "measurement", "A"); + publishSensor("Instantaneous Power", NULL, "instantaneousPower", "power", "measurement", "W"); + publishSensor("Charged Energy", NULL, "chargedEnergy", "energy", "total_increasing", "kWh"); + publishSensor("Discharged Energy", NULL, "dischargedEnergy", "energy", "total_increasing", "kWh"); + publishSensor("Charge Cycles", "mdi:counter", "chargeCycles"); + publishSensor("Consumed Amp Hours", NULL, "consumedAmpHours", NULL, "measurement", "Ah"); + publishSensor("Last Full Charge", "mdi:timelapse", "lastFullCharge", NULL, NULL, "min"); + publishSensor("Midpoint Voltage", NULL, "midpointVoltage", "voltage", "measurement", "V"); + publishSensor("Midpoint Deviation", NULL, "midpointDeviation", "battery", "measurement", "%"); + break; +#endif } } @@ -321,8 +319,7 @@ void MqttHandleBatteryHassClass::createDeviceInfo(JsonObject& object) switch (config.Battery.Provider) { case 0: case 1: - case 2: - case 4: + case 5: case 6: object["name"] = "Battery(" + serial + ")"; break; @@ -332,7 +329,7 @@ void MqttHandleBatteryHassClass::createDeviceInfo(JsonObject& object) break; #endif #ifdef USE_DALYBMS_CONTROLLER - case 5: + case 4: object["name"] = "DALY BMS (" + Battery.getStats()->getManufacturer() + ")"; break; #endif diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index ea919c501..69a32c417 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -299,6 +299,7 @@ PinMappingClass::PinMappingClass() _pinMapping.battery.can0.rx = CAN0_PIN_RX; _pinMapping.battery.can0.tx = CAN0_PIN_TX; #endif + _pinMapping.battery.providerName = help[(int)_pinMapping.battery.provider]; #if defined(USE_CHARGER_HUAWEI) _pinMapping.charger.power = CHARGER_PIN_POWER; @@ -306,6 +307,7 @@ PinMappingClass::PinMappingClass() _pinMapping.charger.provider = Charger_Provider_t::CAN0; _pinMapping.charger.can0.rx = CAN0_PIN_RX; _pinMapping.charger.can0.tx = CAN0_PIN_TX; + _pinMapping.charger.providerName = help[(int)_pinMapping.charger.provider]; _pinMapping.pre_charge = PRE_CHARGE_PIN; _pinMapping.full_power = FULL_POWER_PIN; @@ -471,11 +473,13 @@ void PinMappingClass::init(const String& deviceMapping) _pinMapping.battery.wakeup = doc[i]["battery"]["wakeup"] | BATTERY_PIN_WAKEUP; #endif } + _pinMapping.battery.providerName = help[(int)_pinMapping.battery.provider]; #if defined(USE_CHARGER_MEANWELL) || defined(USE_CHARGER_HUAWEI) #if defined(USE_CHARGER_HUAWEI) _pinMapping.charger.power = doc[i]["charger"]["power"] | CHARGER_PIN_POWER; #endif + if (doc[i]["charger"].containsKey("can0_rx")) { _pinMapping.charger.provider = Charger_Provider_t::CAN0; _pinMapping.charger.can0.rx = doc[i]["charger"]["can0_rx"] | -1; @@ -502,6 +506,7 @@ void PinMappingClass::init(const String& deviceMapping) _pinMapping.charger.can0.tx = doc[i]["charger"]["can0_tx"] | CAN0_PIN_TX; } #endif + _pinMapping.charger.providerName = help[(int)_pinMapping.charger.provider]; _pinMapping.pre_charge = doc[i]["batteryConnectedInverter"]["pre_charge"] | PRE_CHARGE_PIN; _pinMapping.full_power = doc[i]["batteryConnectedInverter"]["full_power"] | FULL_POWER_PIN; diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index 5073ef6a3..e7bb0fa10 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -20,6 +20,7 @@ #include #include #include +#include "SurplusPower.h" static constexpr char TAG[] = "[PowerLimiter]"; @@ -320,7 +321,9 @@ void PowerLimiterClass::loop() auto getBatteryPower = [this,&config]() -> bool { if (config.PowerLimiter.IsInverterSolarPowered) { return false; } - if (_nighttimeDischarging && getSolarPower() > 0) { + auto isDayPeriod = SunPosition.isSunsetAvailable() ? SunPosition.isDayPeriod() : getSolarPower() > 0; + + if (_nighttimeDischarging && isDayPeriod) { _nighttimeDischarging = false; return isStartThresholdReached(); } @@ -335,12 +338,11 @@ void PowerLimiterClass::loop() return true; } - // TODO(schlimmchen): should be supported by sunrise and sunset, such - // that a thunderstorm or other events that drastically lower the solar - // power do not cause the start of a discharge cycle during the day. if (config.PowerLimiter.SolarPassThroughEnabled && config.PowerLimiter.BatteryAlwaysUseAtNight && - getSolarPower() == 0 && !_batteryDischargeEnabled) { + !isDayPeriod && + !_batteryDischargeEnabled) + { _nighttimeDischarging = true; if (_verboseLogging) MessageOutput.printf("%s%s: Solar passthroug is enabled and use battery always at night\r\n", TAG, __FUNCTION__); return true; @@ -615,6 +617,12 @@ bool PowerLimiterClass::calcPowerLimit(std::shared_ptr inverte if (_verboseLogging) MessageOutput.printf("%s%s: match household consumption with limit of %d W\r\n", TAG, __FUNCTION__, newPowerLimit); +#ifdef USE_SURPLUSPOWER + // use surplus power if active + if (SurplusPower.useSurplusPower()) + newPowerLimit = SurplusPower.calcSurplusPower(newPowerLimit); +#endif + // Case 3: return setNewPowerLimit(inverter, newPowerLimit); } diff --git a/src/PowerMeterSerialSdm.cpp b/src/PowerMeterSerialSdm.cpp index 9afc5b44a..e29528467 100644 --- a/src/PowerMeterSerialSdm.cpp +++ b/src/PowerMeterSerialSdm.cpp @@ -2,7 +2,6 @@ #include "PowerMeterSerialSdm.h" #include "PinMapping.h" #include "MessageOutput.h" -#include "SerialPortManager.h" #include "PowerMeter.h" #include "Datastore.h" diff --git a/src/SurplusPower.cpp b/src/SurplusPower.cpp new file mode 100644 index 000000000..701635f69 --- /dev/null +++ b/src/SurplusPower.cpp @@ -0,0 +1,253 @@ +/* Surplus-Power-Mode + * + * The Surplus-Power-Mode regulates the inverter output power based on the surplus solar power. + * Surplus solar power is available when the battery is (almost) full and the available + * solar power is higher than the power consumed in the household. + * + * Basic principe: + * In absorption- and float-mode the MPPT acts like a constant voltage source with current limiter. + * In that modes we do not get any reliable maximum power or maximum current information from the MPPT. + * To find the maximum solar power we regulate the inverter power, we go higher and higher, step by step, + * until we reach the current limit and the voltage begins to drop. When we go one step back and check if + * the voltage is back above the target voltage. A kind of simple approximation control. + * + * Notes: + * We need Victron VE.Direct Rx/Tx (text-mode and hex-mode) to get all necessary data for the regulation + * + * 2024.08.10 - 1.0 - first version + * + */ +#ifdef USE_SURPLUSPOWER + +#include "Configuration.h" +#include "VictronMppt.h" +#include "MessageOutput.h" +#include "SurplusPower.h" + + + +#define DELTA_VOLTAGE 0.05f // we allow some difference between absorption voltage and target voltage +#define MAX_STEPS 20 // amount of power steps for the approximation +#define TIME_OUT 60000 // 60 sec regulation step-up timeout +#define MODE_ABSORPTION 4 // MPPT in absorption mode +#define MODE_FLOAT 5 // MPPT in float mode +#define NOT_VALID -1.0f // if data is not available for any reason + + +SurplusPowerClass SurplusPower; + + +/* + * useSurplusPower() + * check if surplus-power-mode is enabled + * return: true = enabled, false = not enabled + */ +bool SurplusPowerClass::useSurplusPower(void) const { + CONFIG_T const& config = Configuration.get(); + + return (config.PowerLimiter.SurplusPowerEnabled) ? true : false; +} + + +/* + * handleQualityCounter() + * check if the regulation quality counter can be added to the quality statistic + */ +void SurplusPowerClass::handleQualityCounter(void) { + if (_qualityCounter != 0) + _qualityAVG.addNumber(_qualityCounter); + _qualityCounter = 0; +} + + +/* + * calcSurplusPower() + * calculates the surplus power if MPPT indicates absorption or float mode + * requested power: the power based on actual calculation from "Zero feed throttle" or "Solar-Passthrough" + * return: maximum power ("actual solar power" or "requested power") + */ +int32_t SurplusPowerClass::calcSurplusPower(int32_t const requestedPower) { + + // MPPT in absorption or float mode? + auto vStOfOp = VictronMppt.getStateOfOperation(); + if ((vStOfOp != MODE_ABSORPTION) && (vStOfOp != MODE_FLOAT)) { + _surplusPower = 0.0; + _surplusState = SurplusState::IDLE; + return requestedPower; + } + + // get the absorption/float voltage from MPPT + auto xVoltage = VictronMppt.getVoltage(VictronMpptClass::MPPTVoltage::ABSORPTION); + if (xVoltage != NOT_VALID) _absorptionVoltage = xVoltage; + xVoltage = VictronMppt.getVoltage(VictronMpptClass::MPPTVoltage::FLOAT); + if (xVoltage != NOT_VALID) _floatVoltage = xVoltage; + if ((_absorptionVoltage == NOT_VALID) || (_floatVoltage == NOT_VALID)) { + MessageOutput.printf("%s Not possible. Absorption/Float voltage from MPPT is not available\r\n", + getText(Text::T_HEAD).data()); + return requestedPower; + } + + // get the battery voltage from MPPT + // Note: like the MPPT we use the MPPT voltage and not the voltage from the battery for regulation + auto mpptVoltage = VictronMppt.getVoltage(VictronMpptClass::MPPTVoltage::BATTERY); + if (mpptVoltage == NOT_VALID) { + MessageOutput.printf("%s Not possible. Battery voltage from MPPT is not available\r\n", + getText(Text::T_HEAD).data()); + return requestedPower; + } + _avgMPPTVoltage.addNumber(mpptVoltage); + auto avgMPPTVoltage = _avgMPPTVoltage.getAverage(); + + // set the regulation target voltage threshold + // todo: Check if we need the double DELTA_VOLTAGE on 48V or more power full systems + auto targetVoltage = (vStOfOp == MODE_ABSORPTION) ? _absorptionVoltage - DELTA_VOLTAGE: _floatVoltage - DELTA_VOLTAGE; + + // state machine: hold, increase or decrease the surplus power + CONFIG_T& config = Configuration.get(); + int32_t addPower = 0; + switch (_surplusState) { + + case SurplusState::IDLE: + // start check if all necessary information is available + _powerStep = config.PowerLimiter.UpperPowerLimit / MAX_STEPS; + _surplusPower = requestedPower; + _surplusState = SurplusState::TRY_MORE; + _qualityCounter = 0; + _qualityAVG.reset(); + break; + + case SurplusState::TRY_MORE: + if (mpptVoltage >= targetVoltage) { + // still above the target voltage, we try to increase the power + addPower = 2*_powerStep; + } else { + // below the target voltage, we need less power + addPower = -_powerStep; // less power + _surplusState = SurplusState::REDUCE_POWER; + } + break; + + case SurplusState::REDUCE_POWER: + if (mpptVoltage >= targetVoltage) { + // we are in target and can keep the last surplus power value + _inTargetTime = millis(); + _surplusState = SurplusState::IN_TARGET; + } else { + // still below the target voltage, we need less power + addPower = -_powerStep; + } + break; + + case SurplusState::MAXIMUM_POWER: + case SurplusState::IN_TARGET: + // note: here we use both the actual and the average battery voltage + if ((avgMPPTVoltage >= targetVoltage) || (mpptVoltage >= targetVoltage)) { + // we try to increase the power after a time out of 60 sec + if ((millis() - _inTargetTime) > TIME_OUT) { + addPower = _powerStep; // try if more power is possible + _surplusState = SurplusState::TRY_MORE; + } + // we reached the target and can check, how many polarity changes we needed + handleQualityCounter(); + } else { + // out of the target voltage we must reduce the power + addPower = -_powerStep; + _surplusState = SurplusState::REDUCE_POWER; + } + break; + + default: + addPower = 0; + _surplusState = SurplusState::IDLE; + + } + _surplusPower += addPower; + + // we do not go above the maximum power limit + if (_surplusPower > config.PowerLimiter.UpperPowerLimit) { + _surplusPower = config.PowerLimiter.UpperPowerLimit; + _surplusState = SurplusState::MAXIMUM_POWER; + } + + // we do not go below the requested power + auto backPower = _surplusPower; + if (requestedPower > _surplusPower) { + backPower = _surplusPower = requestedPower; + } else { + + // quality check: we count every power step polarity change ( + to - and - to +) + // to give an indication of the regulation quality + if (((_lastAddPower < 0) && (addPower > 0)) || ((_lastAddPower > 0) && (addPower < 0))) + _qualityCounter++; + _lastAddPower = addPower; + } + + // print some basic information + auto qualityAVG = _qualityAVG.getAverage(); + Text text = Text::Q_BAD; + if ((qualityAVG >= 0.0f) && (qualityAVG <= 1.0f)) text = Text::Q_EXCELLENT; + if ((qualityAVG > 1.0f) && (qualityAVG <= 2.0f)) text = Text::Q_GOOD; + MessageOutput.printf("%s Mode: %s, Quality: %s, Surplus power: %iW, Requested power: %iW, Returned power: %iW\r\n", + getText(Text::T_HEAD).data(), getStatusText(_surplusState).data(), getText(text).data(), + _surplusPower, requestedPower, backPower); + + + // todo: maybe we can delete some additional informations after the test phase + if (config.PowerLimiter.VerboseLogging) { + MessageOutput.printf("%s Target voltage: %0.2fV, Battery voltage: %0.2f, Average battery voltage: %0.3fV\r\n", + getText(Text::T_HEAD).data(), targetVoltage, mpptVoltage, avgMPPTVoltage); + + MessageOutput.printf("%s Regulation quality: %s, Average: %0.2f (Min: %0.0f, Max: %0.0f, Amount: %i)\r\n", + getText(Text::T_HEAD).data(), getText(text).data(), + _qualityAVG.getAverage(), _qualityAVG.getMin(), _qualityAVG.getMax(), _qualityAVG.getCounts()); + } + + return backPower; +} + + +/* + * getStatusText() + * return: string according to current status + */ +frozen::string const& SurplusPowerClass::getStatusText(SurplusPowerClass::SurplusState state) +{ + static const frozen::string missing = "programmer error: missing status text"; + + static const frozen::map texts = { + { SurplusState::IDLE, "Idle" }, + { SurplusState::TRY_MORE, "Try more power" }, + { SurplusState::REDUCE_POWER, "Reduce power" }, + { SurplusState::IN_TARGET, "In target range" }, + { SurplusState::MAXIMUM_POWER, "Maximum power" } + }; + + auto iter = texts.find(state); + if (iter == texts.end()) { return missing; } + + return iter->second; +} + + +/* + * getText() + * return: string according to current status + */ +frozen::string const& SurplusPowerClass::getText(SurplusPowerClass::Text tNr) +{ + static const frozen::string missing = "programmer error: missing status text"; + + static const frozen::map texts = { + { Text::Q_NODATA, "Insufficient data" }, + { Text::Q_EXCELLENT, "Excellent" }, + { Text::Q_GOOD, "Good" }, + { Text::Q_BAD, "Bad" }, + { Text::T_HEAD, "[Surplus-Mode]"} + }; + + auto iter = texts.find(tNr); + if (iter == texts.end()) { return missing; } + + return iter->second; +} +#endif diff --git a/src/VictronMppt.cpp b/src/VictronMppt.cpp index ab327ffd6..063588bbf 100644 --- a/src/VictronMppt.cpp +++ b/src/VictronMppt.cpp @@ -11,7 +11,7 @@ static constexpr char TAG[] = "[VictronMppt]"; void VictronMpptClass::init(Scheduler& scheduler) { - MessageOutput.print("Initialize VE.Direct interface... "); + MessageOutput.println("Initialize VE.Direct interface..."); scheduler.addTask(_loopTask); _loopTask.setCallback([this] { loop(); }); @@ -267,6 +267,12 @@ float VictronMpptClass::getVoltage(MPPTVoltage kindOf) const case MPPTVoltage::FLOAT: voltX = upController->getData().BatteryFloatMilliVolt; break; + case MPPTVoltage::BATTERY: + if (upController->isDataValid()) { + voltX.first = 1; + voltX.second = upController->getData().batteryVoltage_V_mV; + } + break; } if (voltX.first > 0) { return static_cast(voltX.second / 1000.0); diff --git a/src/WebApi_MeanWell.cpp b/src/WebApi_MeanWell.cpp index 1ec8e076a..1fde77459 100644 --- a/src/WebApi_MeanWell.cpp +++ b/src/WebApi_MeanWell.cpp @@ -55,6 +55,7 @@ void WebApiMeanWellClass::onAdminGet(AsyncWebServerRequest* request) root["enabled"] = cMeanWell.Enabled; root["verbose_logging"] = cMeanWell.VerboseLogging; + root["io_providername"] = PinMapping.get().charger.providerName; if (MeanWellCan.isMCP2515Provider()) root["can_controller_frequency"] = Configuration.get().MCP2515.Controller_Frequency; root["pollinterval"] = cMeanWell.PollInterval; root["updatesonly"] = cMeanWell.UpdatesOnly; diff --git a/src/WebApi_battery.cpp b/src/WebApi_battery.cpp index 9a7db7c2d..7bc5b4331 100644 --- a/src/WebApi_battery.cpp +++ b/src/WebApi_battery.cpp @@ -38,6 +38,7 @@ void WebApiBatteryClass::onStatus(AsyncWebServerRequest* request) root["verbose_logging"] = cBattery.VerboseLogging; root["updatesonly"] = cBattery.UpdatesOnly; root["provider"] = cBattery.Provider; + root["io_providername"] = PinMapping.get().battery.providerName; root["can_controller_frequency"] = Configuration.get().MCP2515.Controller_Frequency; #ifdef USE_JKBMS_CONTROLLER root["jkbms_interface"] = cBattery.JkBms.Interface; diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index 19b98110a..25f8d8a93 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -288,10 +288,10 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) return; } - String output; +/* String output; serializeJson(root, output); Serial.println(output); - +*/ CONFIG_T& config = Configuration.get(); bool performRestart = root["curPin"]["name"].as() != config.Dev_PinMapping; diff --git a/src/WebApi_powerlimiter.cpp b/src/WebApi_powerlimiter.cpp index ceda0aeec..6e3cd86bb 100644 --- a/src/WebApi_powerlimiter.cpp +++ b/src/WebApi_powerlimiter.cpp @@ -60,6 +60,9 @@ void WebApiPowerLimiterClass::onStatus(AsyncWebServerRequest* request) root["full_solar_passthrough_soc"] = config.PowerLimiter.FullSolarPassThroughSoc; root["full_solar_passthrough_start_voltage"] = static_cast(config.PowerLimiter.FullSolarPassThroughStartVoltage * 100 + 0.5) / 100.0; root["full_solar_passthrough_stop_voltage"] = static_cast(config.PowerLimiter.FullSolarPassThroughStopVoltage * 100 + 0.5) / 100.0; +#ifdef USE_SURPLUSPOWER + root["surplus_power_enabled"] = config.PowerLimiter.SurplusPowerEnabled; +#endif WebApi.sendJsonResponse(request, response, __FUNCTION__, __LINE__); } @@ -179,6 +182,9 @@ void WebApiPowerLimiterClass::onAdminPost(AsyncWebServerRequest* request) config.PowerLimiter.LowerPowerLimit = root["lower_power_limit"].as(); config.PowerLimiter.BaseLoadLimit = root["base_load_limit"].as(); config.PowerLimiter.UpperPowerLimit = root["upper_power_limit"].as(); +#ifdef USE_SURPLUSPOWER + config.PowerLimiter.SurplusPowerEnabled = root["surplus_power_enabled"]; +#endif if (config.Battery.Enabled) { config.PowerLimiter.IgnoreSoc = root["ignore_soc"].as(); diff --git a/src/main.cpp b/src/main.cpp index 6fcc3b742..3d48577a1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -97,7 +97,7 @@ void setup() MessageOutput.println("done"); CONFIG_T& config = Configuration.get(); - PinMapping.init(String(Configuration.get().Dev_PinMapping)); // Load PinMapping + PinMapping.init(String(config.Dev_PinMapping)); // Load PinMapping SerialPortManager.init(); SPIPortManager.init(); diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index fdfa6e218..e06c8105a 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -744,6 +744,9 @@ "PollInterval": "@:dtuadmin.PollInterval", "Seconds": "@:dtuadmin.Seconds", "UpdatesOnly": "@:base.UpdatesOnly", + "SurplusPower": "Surplus Power Mode", + "SurplusPowerInfo": "Der Surplus-Power-Mode regelt die Inverter-Leistung auf Basis der überschüssigen SolarLeistung. Überschüssige Solar-Leistung ist immer dann vorhanden, wenn die Batterie (fast) voll ist und die verfügbare Solar-Leistung höher ist als die Leistung, die im Haushalt verbraucht wird.", + "EnableSurplusPower": "Aktiviere Surplus Power Mode", "SolarPassthrough": "Solar-Passthrough", "EnableSolarPassthrough": "Aktiviere Solar Pass-trough", "SolarPassthroughLosses": "(Full) Solar-Passthrough Verluste:", @@ -794,16 +797,14 @@ "BatteryParameter": "Batterie Parameter", "EnableBattery": "Aktiviere Batterie Bus Schnittstelle", "VerboseLogging": "@:base.VerboseLogging", - "Provider": "Datenanbieter", - "ProviderPylontechRS485": "Pylontech Batterie per RS485 Bus", - "ProviderPylontechCan0": "Pylontech Batterie per CAN0-Bus", - "ProviderPylontechMCP2515": "Pylontech Batterie per CAN-Bus (MCP2515)", - "ProviderJkBmsSerial": "Jikong (JK) BMS per serieller Verbindung", + "Provider": "Datenanbieter per", + "Interface": "Schnittstelle", + "ProviderPylontech": "Pylontech Batterie", + "ProviderPytes": "Pytes Batterie", + "ProviderJkBmsSerial": "Jikong (JK) BMS", + "ProviderDalyBms": "Daly BMS", "ProviderVictron": "Victron SmartShunt per VE.Direct Schnittstelle", - "ProviderDalyBmsRS485": "Daly BMS per RS485 oder RS232 Verbindung", "ProviderMqtt": "Batteriewerte aus MQTT Broker", - "ProviderPytesCan0": "Pytes Batterie per CAN0-Bus", - "ProviderPytesMCP2515": "Pytes Batterie per CAN-Bus (MCP2515)", "CanControllerConfiguration": "MCP2515 Einstellungen", "CanControllerFrequency": "Frequenz des Quarzes am MPC2515 CAN Controller", "MqttSoCConfiguration": "Einstellungen SoC", @@ -1050,7 +1051,7 @@ "ChargerSettings": "AC-Ladegerät Einstellungen", "ChargerConfiguration": "AC-Ladegerät Konfiguration", "ChargerParameter": "MeanWell Parameter", - "EnableMeanWell": "MeanWell NPB-450/750/1200/1700-24/48 an CAN0 Bus Interface aktiv", + "EnableMeanWell": "Aktiviere MeanWell NPB-450/750/1200/1700-24/48 an ", "UpdatesOnly": "@:base.UpdatesOnly", "PollInterval": "@:dtuadmin.PollInterval", "VerboseLogging": "@:base.VerboseLogging", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index f791fee20..69ea16208 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -744,6 +744,9 @@ "Seconds": "@:dtuadmin.Seconds", "UpdatesOnly": "@:base.UpdatesOnly", "VerboseLogging": "@:base.VerboseLogging", + "SurplusPower": "Surplus Power Mode", + "SurplusPowerInfo": "The surplus power mode regulates the inverter power based on the excess solar power. Excess solar power is always available when the battery is (almost) full and the available solar power is higher than the power used in the household.", + "EnableSurplusPower": "Enable Surplus Power Mode", "SolarPassthrough": "Solar-Passthrough", "EnableSolarPassthrough": "Enable Solar-Passthrough", "SolarPassthroughLosses": "(Full) Solar-Passthrough Losses", @@ -794,16 +797,14 @@ "BatteryParameter": "Battery Parameter", "EnableBattery": "Enable Battery bus Interface", "VerboseLogging": "@:base.VerboseLogging", - "Provider": "Data Provider", - "ProviderPylontechRS485": "Pylontech battery using RS485 bus", - "ProviderPylontechCan0": "Pylontech battery using CAN0 bus", - "ProviderPylontechMCP2515": "Pylontech battery using CAN bus (MCP2515)", - "ProviderJkBmsSerial": "Jikong (JK) BMS using serial connection", + "Provider": "Data Provider using", + "Interface": "interface", + "ProviderPylontech": "Pylontech battery", + "ProviderPytes": "Pytes battery", + "ProviderJkBmsSerial": "Jikong (JK) BMS", + "ProviderDalyBms": "Daly BMS", "ProviderVictron": "Victron SmartShunt using VE.Direct interface", - "ProviderDalyBmsRS485": "Daly BMS using RS485 or RS232", "ProviderMqtt": "Battery data from MQTT broker", - "ProviderPytesCan0": "Pytes battery using CAN0-Bus", - "ProviderPytesMCP2515": "Pytes battery using CAN-Bus (MCP2515)", "CanControllerConfiguration": "MCP2515 Settings", "CanControllerFrequency": "MCP2515 CAN controller quarz frequency", "MqttSocConfiguration": "SoC Settings", @@ -1052,7 +1053,7 @@ "VerboseLogging": "@:base.VerboseLogging", "CanControllerConfiguration": "@:batteryadmin.CanControllerConfiguration", "CanControllerFrequency": "@:batteryadmin.CanControllerFrequency", - "EnableMeanWell": "Enable MeanWell NPB-450/750/1200/1700-24/48 on CAN0 bus Interface", + "EnableMeanWell": "Enable MeanWell NPB-450/750/1200/1700-24/48 on ", "Min_Voltage": "Min. output voltage", "Max_Voltage": "Max. output voltage", "Min_Current": "Min. output current", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 55caacc93..614c914c2 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -677,16 +677,14 @@ "BatteryParameter": "Paramètre de batterie", "EnableBattery": "Activer l'interface du bus de batterie", "VerboseLogging": "@:base.VerboseLogging", - "Provider": "Données fourniesr", - "ProviderPylontechRS485": "Pylontech utilisant le bus RS485", - "ProviderPylontechCan0": "Pylontech batterie en utilisant le bus CAN0-Bus", - "ProviderPylontechMCP2515": "Pylontech en utilisant le bus CAN-Bus (MCP2515)", - "ProviderJkBmsSerial": "Jikong (JK) BMS en utilisant une connexion série", + "Provider": "Données fourniesr en utilisant", + "Interface": "interface", + "ProviderPylontech": "Pylontech batterie", + "ProviderPytes": "Pytes batterie", + "ProviderJkBmsSerial": "Jikong (JK) BMS", + "ProviderDalyBms": "Daly BMS", "ProviderVictron": "Victron SmartShunt en utilisant l'interface VE.Direct", - "ProviderDalyBmsRS485": "Daly BMS utilisant RS485 ou RS232", "ProviderMqtt": "Battery data from MQTT broker", - "ProviderPytesCan0": "Pytes batterie en utilisant le bus CAN0-Bus", - "ProviderPytesMCP2515": "Pytes batterie en utilisant le bus CAN-Bus (MCP2515)", "CanControllerConfiguration": "MCP2515 Settings", "CanControllerFrequency": "MPC2515 CAN fréquence de quartz de contrôleur", "MqttSocConfiguration": "Paramètres du SoC", @@ -857,6 +855,9 @@ "VerboseLogging": "@:base.VerboseLogging", "General": "Général", "Enable": "Activer le limiteur de puissance", + "SurplusPower": "Mode de puissance excédentaire", + "SurplusPowerInfo": "Le mode de puissance excédentaire régule la puissance de l'onduleur en fonction de l'énergie solaire excédentaire. L’énergie solaire excédentaire est toujours disponible lorsque la batterie est (presque) pleine et que l’énergie solaire disponible est supérieure à l’énergie utilisée dans le foyer.", + "EnableSurplusPower": "Activer le mode de puissance excédentaire", "EnableSolarPassthrough": "Activer le passage solaire", "SolarPassthroughLosses": "(Full) Pertes dues au rayonnement solaire", "SolarPassthroughLossesInfo": "Hint: Des pertes de ligne sont à prévoir lors du transfert d'énergie du contrôleur de charge solaire à l'onduleur. Ces pertes peuvent être prises en compte pour éviter que la batterie ne se décharge progressivement en mode (complet) de passage solaire. La limite de puissance à régler sur l'onduleur est en outre réduite de ce facteur après prise en compte de son efficacité.", @@ -1050,7 +1051,7 @@ "VerboseLogging": "@:base.VerboseLogging", "CanControllerConfiguration": "@:batteryadmin.CanControllerConfiguration", "CanControllerFrequency": "@:batteryadmin.CanControllerFrequency", - "EnableMeanWell": "Activer MeanWell NPB-450/750/1200/1700-24/48 sur interface bus CAN0", + "EnableMeanWell": "Activer MeanWell NPB-450/750/1200/1700-24/48 sur ", "Min_Voltage": "Tension de sortie minimale", "Max_Voltage": "Tension de sortie maximale", "Min_Current": "Courant de sortie minimal", diff --git a/webapp/src/types/AcChargerConfig.ts b/webapp/src/types/AcChargerConfig.ts index e9b10cc42..48834ec9a 100644 --- a/webapp/src/types/AcChargerConfig.ts +++ b/webapp/src/types/AcChargerConfig.ts @@ -1,6 +1,7 @@ export interface AcChargerConfig { enabled: boolean; verbose_logging: boolean; + io_providername: string; can_controller_frequency: number; pollinterval: number; updatesonly: boolean; diff --git a/webapp/src/types/BatteryConfig.ts b/webapp/src/types/BatteryConfig.ts index bb225e5b1..f43bb32a8 100644 --- a/webapp/src/types/BatteryConfig.ts +++ b/webapp/src/types/BatteryConfig.ts @@ -5,6 +5,7 @@ export interface BatteryConfig { updatesonly: boolean; verbose_logging: boolean; provider: number; + io_providername: string; can_controller_frequency: number; jkbms_interface: number; jkbms_polling_interval: number; diff --git a/webapp/src/types/PowerLimiterConfig.ts b/webapp/src/types/PowerLimiterConfig.ts index ff09bc1e2..6476217c7 100644 --- a/webapp/src/types/PowerLimiterConfig.ts +++ b/webapp/src/types/PowerLimiterConfig.ts @@ -47,4 +47,5 @@ export interface PowerLimiterConfig { full_solar_passthrough_soc: number; full_solar_passthrough_start_voltage: number; full_solar_passthrough_stop_voltage: number; + surplus_power_enabled: boolean; } diff --git a/webapp/src/views/AcChargerAdminView.vue b/webapp/src/views/AcChargerAdminView.vue index d01c1d7e9..084b980fa 100644 --- a/webapp/src/views/AcChargerAdminView.vue +++ b/webapp/src/views/AcChargerAdminView.vue @@ -6,7 +6,7 @@
-
diff --git a/webapp/src/views/BatteryAdminView.vue b/webapp/src/views/BatteryAdminView.vue index 7efa5f2fe..15d1ffeef 100644 --- a/webapp/src/views/BatteryAdminView.vue +++ b/webapp/src/views/BatteryAdminView.vue @@ -17,7 +17,7 @@ type="checkbox" wide4_1 />