diff --git a/README.md b/README.md index 40d66b7b..26bfb437 100644 --- a/README.md +++ b/README.md @@ -270,7 +270,9 @@ This can be achieved by copying one of the [env:....] sections from 'platformio. -DHOYMILES_PIN_CE=4 -DHOYMILES_PIN_CS=5 -DVICTRON_PIN_TX=21 --DVICTRON_PIN_RX=22 +-DVICTRON_PIN_RX=22 +-DPYLONTECH_PIN_RX=27 +-DPYLONTECH_PIN_TX=14 ``` It is recommended to make all changes only in the 'platformio_override.ini', this is your personal copy. You can also change the pins by creating a custom [device profile](docs/DeviceProfiles.md). diff --git a/include/PinMapping.h b/include/PinMapping.h index cdd1e73d..4e2900f0 100644 --- a/include/PinMapping.h +++ b/include/PinMapping.h @@ -31,6 +31,8 @@ struct PinMapping_t { uint8_t display_reset; uint8_t victron_tx; uint8_t victron_rx; + uint8_t battery_rx; + uint8_t battery_tx; }; class PinMappingClass { @@ -42,9 +44,10 @@ public: bool isValidNrf24Config(); bool isValidEthConfig(); bool isValidVictronConfig(); + bool isValidBatteryConfig(); private: PinMapping_t _pinMapping; }; -extern PinMappingClass PinMapping; \ No newline at end of file +extern PinMappingClass PinMapping; diff --git a/include/PylontechCanReceiver.h b/include/PylontechCanReceiver.h index 1efb03ac..2cb13ac1 100644 --- a/include/PylontechCanReceiver.h +++ b/include/PylontechCanReceiver.h @@ -17,7 +17,8 @@ class PylontechCanReceiverClass { public: - void init(); + void init(int8_t rx, int8_t tx); + void enable(); void loop(); void parseCanPackets(); void mqtt(); diff --git a/platformio.ini b/platformio.ini index 96d8e7e5..5ccc4346 100644 --- a/platformio.ini +++ b/platformio.ini @@ -55,6 +55,8 @@ build_flags = ${env.build_flags} -DHOYMILES_PIN_CS=5 -DVICTRON_PIN_TX=21 -DVICTRON_PIN_RX=22 + -DPYLONTECH_PIN_RX=27 + -DPYLONTECH_PIN_TX=14 [env:olimex_esp32_poe] diff --git a/src/PinMapping.cpp b/src/PinMapping.cpp index 486834d7..248e369b 100644 --- a/src/PinMapping.cpp +++ b/src/PinMapping.cpp @@ -64,6 +64,8 @@ PinMappingClass::PinMappingClass() _pinMapping.victron_tx = VICTRON_PIN_TX; _pinMapping.victron_rx = VICTRON_PIN_RX; + _pinMapping.battery_rx = PYLONTECH_PIN_RX; + _pinMapping.battery_tx = PYLONTECH_PIN_TX; } PinMapping_t& PinMappingClass::get() @@ -119,6 +121,9 @@ bool PinMappingClass::init(const String& deviceMapping) _pinMapping.victron_rx = doc[i]["victron"]["rx"] | VICTRON_PIN_RX; _pinMapping.victron_tx = doc[i]["victron"]["tx"] | VICTRON_PIN_TX; + _pinMapping.battery_rx = doc[i]["battery"]["rx"] | PYLONTECH_PIN_RX; + _pinMapping.battery_tx = doc[i]["battery"]["tx"] | PYLONTECH_PIN_TX; + return true; } } @@ -145,4 +150,10 @@ bool PinMappingClass::isValidVictronConfig() { return _pinMapping.victron_rx > 0 && _pinMapping.victron_tx > 0; -} \ No newline at end of file +} + +bool PinMappingClass::isValidBatteryConfig() +{ + return _pinMapping.battery_rx > 0 + && _pinMapping.battery_tx > 0; +} diff --git a/src/PowerLimiter.cpp b/src/PowerLimiter.cpp index 0e60a1f8..0b2cac15 100644 --- a/src/PowerLimiter.cpp +++ b/src/PowerLimiter.cpp @@ -40,7 +40,7 @@ void PowerLimiterClass::init() } _consumeSolarPowerOnly = true; - _lastCommandSent = 0; + _lastCommandSent = 0; _lastLoop = 0; _lastPowerMeterUpdate = 0; _lastRequestedPowerLimit = 0; @@ -48,8 +48,6 @@ void PowerLimiterClass::init() void PowerLimiterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) { - MessageOutput.printf("PowerLimiterClass: Received MQTT message on topic: %s\r\n", topic); - CONFIG_T& config = Configuration.get(); if (strcmp(topic, config.PowerLimiter_MqttTopicPowerMeter1) == 0) { @@ -109,11 +107,10 @@ void PowerLimiterClass::loop() float acPower = inverter->Statistics()->getChannelFieldValue(TYPE_AC, (ChannelNum_t) config.PowerLimiter_InverterChannelId, FLD_PAC); float correctedDcVoltage = dcVoltage + (acPower * config.PowerLimiter_VoltageLoadCorrectionFactor); - if ((_consumeSolarPowerOnly && isStartThresholdReached(inverter)) - || !canUseDirectSolarPower()) { + if ((_consumeSolarPowerOnly && isStartThresholdReached(inverter))) { // The battery is full enough again, use the full battery power from now on. _consumeSolarPowerOnly = false; - } else if (!_consumeSolarPowerOnly && !isStopThresholdReached(inverter) && canUseDirectSolarPower()) { + } else if (!_consumeSolarPowerOnly && isStopThresholdReached(inverter) && canUseDirectSolarPower()) { // The battery voltage dropped too low _consumeSolarPowerOnly = true; } @@ -224,7 +221,7 @@ bool PowerLimiterClass::canUseDirectSolarPower() uint16_t PowerLimiterClass::getDirectSolarPower() { - if (!this->canUseDirectSolarPower()) { + if (!canUseDirectSolarPower()) { return 0; } @@ -249,7 +246,7 @@ bool PowerLimiterClass::isStartThresholdReached(std::shared_ptr 0.0 && (millis() - Battery.stateOfChargeLastUpdate) < 60000 @@ -270,7 +267,7 @@ bool PowerLimiterClass::isStopThresholdReached(std::shared_ptr { CONFIG_T& config = Configuration.get(); - // If the Battery interface is enabled, use the SOC value + // Check if the Battery interface is enabled and the SOC stop threshold is reached if (config.Battery_Enabled && config.PowerLimiter_BatterySocStopThreshold > 0.0 && (millis() - Battery.stateOfChargeLastUpdate) < 60000 diff --git a/src/PylontechCanReceiver.cpp b/src/PylontechCanReceiver.cpp index 0219642c..27955ed7 100644 --- a/src/PylontechCanReceiver.cpp +++ b/src/PylontechCanReceiver.cpp @@ -9,16 +9,21 @@ PylontechCanReceiverClass PylontechCanReceiver; -void PylontechCanReceiverClass::init() +void PylontechCanReceiverClass::init(int8_t rx, int8_t tx) { + CAN.setPins(rx, tx); + CONFIG_T& config = Configuration.get(); if (!config.Battery_Enabled) { return; } - - CAN.setPins(PYLONTECH_PIN_RX, PYLONTECH_PIN_TX); + enable(); +} + +void PylontechCanReceiverClass::enable() +{ if (!CAN.begin(500E3)) { Hoymiles.getMessageOutput()->println("Starting CAN failed!"); } diff --git a/src/WebApi_battery.cpp b/src/WebApi_battery.cpp index 4726f2b5..a11d14fb 100644 --- a/src/WebApi_battery.cpp +++ b/src/WebApi_battery.cpp @@ -108,6 +108,6 @@ void WebApiBatteryClass::onAdminPost(AsyncWebServerRequest* request) request->send(response); if (config.Battery_Enabled) { - PylontechCanReceiver.init(); + PylontechCanReceiver.enable(); } } diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index abde3123..2cc191cf 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -73,6 +73,10 @@ void WebApiDeviceClass::onDeviceAdminGet(AsyncWebServerRequest* request) victronPinObj[F("rx")] = pin.victron_rx; victronPinObj[F("tx")] = pin.victron_tx; + JsonObject batteryPinObj = curPin.createNestedObject("battery"); + batteryPinObj[F("rx")] = pin.battery_rx; + batteryPinObj[F("tx")] = pin.battery_tx; + response->setLength(); request->send(response); } diff --git a/src/main.cpp b/src/main.cpp index e30b73eb..33066ac9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include "InverterSettings.h" #include "MessageOutput.h" #include "VeDirectFrameHandler.h" +#include "PylontechCanReceiver.h" #include "MqttHandleDtu.h" #include "MqttHandleHass.h" #include "MqttHandleVedirectHass.h" @@ -20,7 +21,6 @@ #include "Utils.h" #include "WebApi.h" #include "PowerLimiter.h" -#include "PylontechCanReceiver.h" #include "defaults.h" #include #include @@ -149,8 +149,15 @@ void setup() // Dynamic power limiter PowerLimiter.init(); - // Pylontech / CAN bus - PylontechCanReceiver.init(); + // Initialize Pylontech Battery / CAN bus + MessageOutput.println(F("Initialize Pylontech battery interface... ")); + if (PinMapping.isValidBatteryConfig()) { + MessageOutput.printf("Pylontech Battery rx = %d, tx = %d\r\n", pin.battery_rx, pin.battery_tx); + PylontechCanReceiver.init(pin.battery_rx, pin.battery_tx); + MessageOutput.println(F("done")); + } else { + MessageOutput.println(F("Invalid pin config")); + } } void loop() diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index 1dc76270..771637ae 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -447,6 +447,12 @@ "Save": "@:dtuadmin.Save" }, "powerlimiteradmin": { + "PowerLimiterSettings": "Power Limiter Einstellungen", + "PowerLimiterConfiguration": "Power Limiter Konfiguration", + "General": "Allgemein", + "Enable": "Aktiviert", + "EnableSolarPasstrough": "Aktiviere Solar Pass-trough", + "SolarpasstroughInfo": "Diese Einstellung aktiviert die direkte Weitergabe der aktuell vom Laderegler gemeldeten Solarleistung an den Wechselrichter um eine unnötige Speicherung zu vermeiden und die Energieverluste zu minimieren.", "InverterId": "Wechselrichter ID", "InverterIdHint": "Wähle den Wechselrichter an dem die Batterie hängt.", "InverterChannelId": "Kanal ID", @@ -454,8 +460,23 @@ "TargetPowerConsumption": "Erlaubter Stromverbrauch", "TargetPowerConsumptionHint": "Angestrebter erlaubter Stromverbrauch.", "TargetPowerConsumptionHysteresis": "Hysterese für den Zielstromverbrauch", - "TargetPowerConsumptionHysteresisHint": "Wert um den der Zielstromverbrauch schwanken darf, ohne dass nachgeregelt wird." - + "TargetPowerConsumptionHysteresisHint": "Wert um den der Zielstromverbrauch schwanken darf, ohne dass nachgeregelt wird.", + "LowerPowerLimit": "Unteres Leistungslimit", + "UpperPowerLimit": "Oberes Leistungslimit", + "PowerMeters": "Leistungsmesser - MQTT", + "MqttTopicPowerMeter1": "MQTT topic - Power meter #1", + "MqttTopicPowerMeter2": "MQTT topic - Power meter #2 (Optional)", + "MqttTopicPowerMeter3": "MQTT topic - Power meter #3 (Optional)", + "BatterySocStartThreshold": "Akku SOC - Start", + "BatterySocStopThreshold": "Akku SOC - Stop", + "VoltageStartThreshold": "DC Spannung - Start", + "VoltageStopThreshold": "DC Spannung - Stop", + "VoltageLoadCorrectionFactor": "DC Spannung - Lastkorrekturfaktor", + "BatterySocInfo": "Hinweis: Der Battery SOC (State of charge) -Wert kann nur benutzt werden wenn das Battery CAN Bus Interface aktiviert ist. Wenn die Batterie innerhalb der letzten Minute keine Werte geschickt hat, werden als Fallback-Option die Spannungseinstellungen verwendet.", + "InverterIsBehindPowerMeter": "Welchselrichter ist hinter Leistungsmesser", + "Battery": "DC / Akku", + "VoltageLoadCorrectionInfo": "Hinweis: Wenn Leistung von der Batterie abgegeben wird, bricht normalerweise die Spannung etwas ein. Damit nicht vorzeitig der Wechelrichter ausgeschaltet wird sobald der \"Stop\"-Schwellenwert erreicht wird, wird der hier angegebene Korrekturfaktor mit einberechnet. Korrigierte Spannung = DC Spannung + (Aktuelle Leistung (W) + Korrekturfaktor).", + "Save": "@:dtuadmin.Save" }, "batteryadmin": { "BatterySettings": "Batterie Einstellungen", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index 9dcd1628..88a7e8f3 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -451,7 +451,7 @@ "PowerLimiterConfiguration": "Power Limiter Configuration", "General": "General", "Enable": "Enable", - "EnableSolarPasstrough": "Enable Solar Passtrough", + "EnableSolarPasstrough": "Enable Solar-Passtrough", "SolarpasstroughInfo": "When the sun is shining, this setting enables the sychronization of the inverter limit with the current solar power of the Victron MPPT charger. This optimizes battery degradation and loses.", "InverterId": "Inverter ID", "InverterIdHint": "Select proper inverter ID where battery is connected to.", @@ -461,12 +461,12 @@ "TargetPowerConsumptionHint": "Set the grid power consumption the limiter tries to achieve.", "TargetPowerConsumptionHysteresis": "Hysteresis for power consumption", "TargetPowerConsumptionHysteresisHint": "Value around which the target power consumption fluctuates without readjustment.", - "LowerPowerLimit": "Lower power limit / continuous feed", + "LowerPowerLimit": "Lower power limit", "UpperPowerLimit": "Upper power limit", "PowerMeters": "Power meters - MQTT", "MqttTopicPowerMeter1": "MQTT topic - Power meter #1", - "MqttTopicPowerMeter2": "MQTT topic - Power meter #2", - "MqttTopicPowerMeter3": "MQTT topic - Power meter #3", + "MqttTopicPowerMeter2": "MQTT topic - Power meter #2 (optional)", + "MqttTopicPowerMeter3": "MQTT topic - Power meter #3 (optional)", "BatterySocStartThreshold": "Battery SOC - Start threshold", "BatterySocStopThreshold": "Battery SOC - Stop threshold", "VoltageStartThreshold": "DC Voltage - Start threshold", diff --git a/webapp_dist/index.html.gz b/webapp_dist/index.html.gz index 0830730b..9996657e 100644 Binary files a/webapp_dist/index.html.gz and b/webapp_dist/index.html.gz differ diff --git a/webapp_dist/js/app.js.gz b/webapp_dist/js/app.js.gz index 0eb04518..ade61ebc 100644 Binary files a/webapp_dist/js/app.js.gz and b/webapp_dist/js/app.js.gz differ diff --git a/webapp_dist/zones.json.gz b/webapp_dist/zones.json.gz index f1ea50bf..14578ad0 100644 Binary files a/webapp_dist/zones.json.gz and b/webapp_dist/zones.json.gz differ