diff --git a/include/MqttSettings.h b/include/MqttSettings.h index add6a690b..63f9ad97c 100644 --- a/include/MqttSettings.h +++ b/include/MqttSettings.h @@ -21,7 +21,7 @@ class MqttSettingsClass { void unsubscribe(const String& topic); String getPrefix() const; - String getClientId(); + String getClientId() const; private: void NetworkEvent(network_event event); diff --git a/lang/es.lang.json b/lang/es.lang.json index 629b97990..d28d9b67d 100644 --- a/lang/es.lang.json +++ b/lang/es.lang.json @@ -45,7 +45,9 @@ "Refreshing": "Refrescando", "Pull": "Tira hacia abajo para refrescar", "Release": "Soltar para refrescar", - "Close": "Cerrar" + "Close": "Cerrar", + "Yes": "Yes", + "No": "No" }, "wait": { "NotReady": "OpenDTU is not yet ready", @@ -193,7 +195,10 @@ "FirmwareVersion": "Versión del firmware", "FirmwareBuildDate": "Fecha de construcción del firmware", "HardwarePartNumber": "Número de parte de hardware", - "HardwareVersion": "Versión de hardware" + "HardwareVersion": "Versión de hardware", + "SupportsPowerDistributionLogic": "'Power Distribution Logic' supported", + "Yes": "@:base.Yes", + "No": "@:base.No" }, "gridprofile": { "NoInfo": "@:devinfo.NoInfo", @@ -637,7 +642,8 @@ "TimeSync": "El reloj aún no ha sido sincronizado. Sin un reloj correctamente ajustado, no se realizan solicitudes al inversor. Esto es normal poco después del inicio. Sin embargo, después de un tiempo de ejecución más largo (>1 minuto), indica que el servidor NTP no es accesible.", "TimeSyncLink": "Por favor, verifica la configuración de tu hora.", "DefaultPassword": "Estás utilizando la contraseña predeterminada para la interfaz web y el punto de acceso de emergencia. Esto potencialmente es inseguro.", - "DefaultPasswordLink": "Por favor, cambia la contraseña." + "DefaultPasswordLink": "Por favor, cambia la contraseña.", + "PinMappingIssue": "You are using a generic firmware image, but have not yet uploaded a file with device profiles (pin_mapping.json) or have not selected a profile defined there. Please refer to the documentation for details." }, "deviceadmin": { "DeviceManager": "Administrador de Dispositivos", diff --git a/lang/it.lang.json b/lang/it.lang.json index eaafda630..5a670665d 100644 --- a/lang/it.lang.json +++ b/lang/it.lang.json @@ -45,7 +45,9 @@ "Refreshing": "Aggiorna", "Pull": "Trascina in basso per aggiornare", "Release": "Rilascia per aggiornare", - "Close": "Chiudi" + "Close": "Chiudi", + "Yes": "Yes", + "No": "No" }, "wait": { "NotReady": "OpenDTU is not yet ready", @@ -193,7 +195,10 @@ "FirmwareVersion": "Versione Firmware", "FirmwareBuildDate": "Data Firmware", "HardwarePartNumber": "Hardware Part Number", - "HardwareVersion": "Hardware Version" + "HardwareVersion": "Hardware Version", + "SupportsPowerDistributionLogic": "'Power Distribution Logic' supported", + "Yes": "@:base.Yes", + "No": "@:base.No" }, "gridprofile": { "NoInfo": "@:devinfo.NoInfo", @@ -637,7 +642,8 @@ "TimeSync": "La Data/Ora non sono state sincronizzate, ed in tal caso non è possibile eseguire richieste all'inverter. Questa condizione è normale appena avviato, tuttavia dopo un po' (>1 minuto), questa situazione potrebbe indicare un problema di accesso al server NTP.", "TimeSyncLink": "Controlla le impostazioni Data/Ora.", "DefaultPassword": "Stai usando la password di default per accedere all'interfaccia web e per la modalità Access Point di emergenza. Questo può portare ad un rischio di sicurezza.", - "DefaultPasswordLink": "Per favore cambia la password." + "DefaultPasswordLink": "Per favore cambia la password.", + "PinMappingIssue": "You are using a generic firmware image, but have not yet uploaded a file with device profiles (pin_mapping.json) or have not selected a profile defined there. Please refer to the documentation for details." }, "deviceadmin": { "DeviceManager": "Device-Manager", diff --git a/lib/CpuTemperature/src/CpuTemperature.cpp b/lib/CpuTemperature/src/CpuTemperature.cpp index 60e3fc7b4..0ce68d5ee 100644 --- a/lib/CpuTemperature/src/CpuTemperature.cpp +++ b/lib/CpuTemperature/src/CpuTemperature.cpp @@ -19,6 +19,12 @@ CpuTemperatureClass CpuTemperature; float CpuTemperatureClass::read() { +#ifdef CONFIG_IDF_TARGET_ESP32S2 + // Disabling temperature reading for ESP32-S2 models as it might lead to WDT resets. + // See: https://github.com/espressif/esp-idf/issues/8088 + return NAN; +#endif + std::lock_guard lock(_mutex); float temperature = NAN; diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index 662ab6d10..7fdcd09dd 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -57,7 +57,7 @@ void HoymilesClass::loop() } } - if (iv != nullptr && iv->getRadio()->isInitialized() && iv->getRadio()->isQueueEmpty()) { + if (iv != nullptr && iv->getRadio()->isInitialized()) { if (iv->getZeroValuesIfUnreachable() && !iv->isReachable()) { iv->Statistics()->zeroRuntimeData(); @@ -119,6 +119,7 @@ void HoymilesClass::loop() iv->sendGridOnProFileParaRequest(); } + _messageOutput->printf("Queue size - NRF: %" PRId32 " CMT: %" PRId32 "\r\n", _radioNrf->getQueueSize(), _radioCmt->getQueueSize()); _lastPoll = millis(); } @@ -229,6 +230,7 @@ void HoymilesClass::removeInverterBySerial(const uint64_t serial) for (uint8_t i = 0; i < _inverters.size(); i++) { if (_inverters[i]->serial() == serial) { std::lock_guard lock(_mutex); + _inverters[i]->getRadio()->removeCommands(_inverters[i].get()); _inverters.erase(_inverters.begin() + i); return; } diff --git a/lib/Hoymiles/src/HoymilesRadio.cpp b/lib/Hoymiles/src/HoymilesRadio.cpp index ea039dc90..d68b32f3c 100644 --- a/lib/Hoymiles/src/HoymilesRadio.cpp +++ b/lib/Hoymiles/src/HoymilesRadio.cpp @@ -156,6 +156,16 @@ bool HoymilesRadio::isInitialized() const return _isInitialized; } +void HoymilesRadio::removeCommands(InverterAbstract* inv) +{ + _commandQueue.removeAllEntriesForInverter(inv); +} + +uint8_t HoymilesRadio::countSimilarCommands(std::shared_ptr cmd) +{ + return _commandQueue.countSimilarCommands(cmd); +} + bool HoymilesRadio::isIdle() const { return !_busyFlag; @@ -165,3 +175,8 @@ bool HoymilesRadio::isQueueEmpty() const { return _commandQueue.size() == 0; } + +uint32_t HoymilesRadio::getQueueSize() const +{ + return _commandQueue.size(); +} diff --git a/lib/Hoymiles/src/HoymilesRadio.h b/lib/Hoymiles/src/HoymilesRadio.h index 296b479bb..95dfe8b01 100644 --- a/lib/Hoymiles/src/HoymilesRadio.h +++ b/lib/Hoymiles/src/HoymilesRadio.h @@ -1,11 +1,17 @@ // SPDX-License-Identifier: GPL-2.0-or-later #pragma once +#include "Arduino.h" #include "commands/CommandAbstract.h" +#include "queue/CommandQueue.h" #include "types.h" -#include #include -#include + +#ifdef HOY_DEBUG_QUEUE +#define DEBUG_PRINT(fmt, args...) Serial.printf(fmt, ##args) +#else +#define DEBUG_PRINT(fmt, args...) /* Don't do anything in release builds */ +#endif class HoymilesRadio { public: @@ -14,11 +20,48 @@ class HoymilesRadio { bool isIdle() const; bool isQueueEmpty() const; + uint32_t getQueueSize() const; bool isInitialized() const; + void removeCommands(InverterAbstract* inv); + uint8_t countSimilarCommands(std::shared_ptr cmd); + void enqueCommand(std::shared_ptr cmd) { + DEBUG_PRINT("Queue size before: %ld\r\n", _commandQueue.size()); + DEBUG_PRINT("Handling command %s with type %d\r\n", cmd.get()->getCommandName().c_str(), static_cast(cmd.get()->getQueueInsertType())); + switch (cmd.get()->getQueueInsertType()) { + case QueueInsertType::RemoveOldest: + _commandQueue.removeDuplicatedEntries(cmd); + break; + case QueueInsertType::ReplaceExistent: + // Checks if the queue already contains a command like the new one + // and replaces the existing one with the new one. + // (The new one will not be pushed at the end of the queue) + if (_commandQueue.countSimilarCommands(cmd) > 0) { + DEBUG_PRINT(" ... existing entry will be replaced\r\n"); + _commandQueue.replaceEntries(cmd); + return; + } + break; + case QueueInsertType::RemoveNewest: + // Checks if the queue already contains a command like the new one + // and drops the new one. The new one will not be inserted. + if (_commandQueue.countSimilarCommands(cmd) > 0) { + DEBUG_PRINT(" ... new entry will be dropped\r\n"); + return; + } + break; + case QueueInsertType::AllowMultiple: + // Dont do anything, just fall through and insert the command. + break; + } + + // Push the command into the queue if we reach this position of the code + DEBUG_PRINT(" ... new entry will be appended\r\n"); _commandQueue.push(cmd); + + DEBUG_PRINT("Queue size after: %ld\r\n", _commandQueue.size()); } template @@ -38,7 +81,7 @@ class HoymilesRadio { void handleReceivedPackage(); serial_u _dtuSerial; - ThreadSafeQueue> _commandQueue; + CommandQueue _commandQueue; bool _isInitialized = false; bool _busyFlag = false; diff --git a/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp b/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp index 4ce3c6e55..4ceb69ab2 100644 --- a/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp +++ b/lib/Hoymiles/src/commands/ActivePowerControlCommand.cpp @@ -44,7 +44,15 @@ ActivePowerControlCommand::ActivePowerControlCommand(InverterAbstract* inv, cons String ActivePowerControlCommand::getCommandName() const { - return "ActivePowerControl"; + char buffer[30]; + snprintf(buffer, sizeof(buffer), "ActivePowerControl (%02X)", getType()); + return buffer; +} + +bool ActivePowerControlCommand::areSameParameter(CommandAbstract* other) +{ + return CommandAbstract::areSameParameter(other) + && this->getType() == static_cast(other)->getType(); } void ActivePowerControlCommand::setActivePowerLimit(const float limit, const PowerLimitControlType type) @@ -79,7 +87,10 @@ bool ActivePowerControlCommand::handleResponse(const fragment_t fragment[], cons } } _inv->SystemConfigPara()->setLastUpdateCommand(millis()); - _inv->SystemConfigPara()->setLastLimitCommandSuccess(CMD_OK); + std::shared_ptr cmd(std::shared_ptr(), this); + if (_inv->getRadio()->countSimilarCommands(cmd) == 1) { + _inv->SystemConfigPara()->setLastLimitCommandSuccess(CMD_OK); + } return true; } @@ -89,7 +100,7 @@ float ActivePowerControlCommand::getLimit() const return l / 10; } -PowerLimitControlType ActivePowerControlCommand::getType() +PowerLimitControlType ActivePowerControlCommand::getType() const { return (PowerLimitControlType)((static_cast(_payload[14]) << 8) | _payload[15]); } diff --git a/lib/Hoymiles/src/commands/ActivePowerControlCommand.h b/lib/Hoymiles/src/commands/ActivePowerControlCommand.h index 375b278bb..8425d248f 100644 --- a/lib/Hoymiles/src/commands/ActivePowerControlCommand.h +++ b/lib/Hoymiles/src/commands/ActivePowerControlCommand.h @@ -15,11 +15,13 @@ class ActivePowerControlCommand : public DevControlCommand { explicit ActivePowerControlCommand(InverterAbstract* inv, const uint64_t router_address = 0); virtual String getCommandName() const; + virtual QueueInsertType getQueueInsertType() const { return QueueInsertType::RemoveOldest; } + virtual bool areSameParameter(CommandAbstract* other); virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); virtual void gotTimeout(); void setActivePowerLimit(const float limit, const PowerLimitControlType type = RelativNonPersistent); float getLimit() const; - PowerLimitControlType getType(); + PowerLimitControlType getType() const; }; diff --git a/lib/Hoymiles/src/commands/CommandAbstract.cpp b/lib/Hoymiles/src/commands/CommandAbstract.cpp index 16a7857e1..6196a96f9 100644 --- a/lib/Hoymiles/src/commands/CommandAbstract.cpp +++ b/lib/Hoymiles/src/commands/CommandAbstract.cpp @@ -138,3 +138,9 @@ uint8_t CommandAbstract::getMaxRetransmitCount() const { return MAX_RETRANSMIT_COUNT; } + +bool CommandAbstract::areSameParameter(CommandAbstract* other) +{ + return this->getCommandName() == other->getCommandName() + && this->_targetAddress == other->getTargetAddress(); +} diff --git a/lib/Hoymiles/src/commands/CommandAbstract.h b/lib/Hoymiles/src/commands/CommandAbstract.h index c93cb3416..64c0bcd4a 100644 --- a/lib/Hoymiles/src/commands/CommandAbstract.h +++ b/lib/Hoymiles/src/commands/CommandAbstract.h @@ -11,6 +11,18 @@ class InverterAbstract; +enum class QueueInsertType { + AllowMultiple, + // Remove from beginning of the queue + RemoveOldest, + + // Don't insert command if it already exist + RemoveNewest, + + // Replace the existing entry in the queue by the one to be added + ReplaceExistent, +}; + class CommandAbstract { public: explicit CommandAbstract(InverterAbstract* inv, const uint64_t router_address = 0); @@ -46,6 +58,10 @@ class CommandAbstract { // Sets the amount how often a missing fragment is re-requested if it was not available virtual uint8_t getMaxRetransmitCount() const; + // Returns whether multiple instances of this command are allowed in the command queue. + virtual QueueInsertType getQueueInsertType() const { return QueueInsertType::RemoveNewest; } + virtual bool areSameParameter(CommandAbstract* other); + protected: uint8_t _payload[RF_LEN]; uint8_t _payload_size; diff --git a/lib/Hoymiles/src/commands/PowerControlCommand.h b/lib/Hoymiles/src/commands/PowerControlCommand.h index d40c356db..a86b56786 100644 --- a/lib/Hoymiles/src/commands/PowerControlCommand.h +++ b/lib/Hoymiles/src/commands/PowerControlCommand.h @@ -8,6 +8,7 @@ class PowerControlCommand : public DevControlCommand { explicit PowerControlCommand(InverterAbstract* inv, const uint64_t router_address = 0); virtual String getCommandName() const; + virtual QueueInsertType getQueueInsertType() const { return QueueInsertType::AllowMultiple; } virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); virtual void gotTimeout(); diff --git a/lib/Hoymiles/src/inverters/HERF_1CH.cpp b/lib/Hoymiles/src/inverters/HERF_1CH.cpp index 17e0a6183..6835bf7f9 100644 --- a/lib/Hoymiles/src/inverters/HERF_1CH.cpp +++ b/lib/Hoymiles/src/inverters/HERF_1CH.cpp @@ -34,7 +34,9 @@ static const channelMetaData_t channelMetaData[] = { }; HERF_1CH::HERF_1CH(HoymilesRadio* radio, const uint64_t serial) - : HM_Abstract(radio, serial) {}; + : HM_Abstract(radio, serial) +{ +} bool HERF_1CH::isValidSerial(const uint64_t serial) { diff --git a/lib/Hoymiles/src/inverters/HERF_2CH.cpp b/lib/Hoymiles/src/inverters/HERF_2CH.cpp index 14f4644bc..0fd81df74 100644 --- a/lib/Hoymiles/src/inverters/HERF_2CH.cpp +++ b/lib/Hoymiles/src/inverters/HERF_2CH.cpp @@ -42,7 +42,9 @@ static const channelMetaData_t channelMetaData[] = { }; HERF_2CH::HERF_2CH(HoymilesRadio* radio, const uint64_t serial) - : HM_Abstract(radio, serial) {}; + : HM_Abstract(radio, serial) +{ +} bool HERF_2CH::isValidSerial(const uint64_t serial) { @@ -53,7 +55,7 @@ bool HERF_2CH::isValidSerial(const uint64_t serial) String HERF_2CH::typeName() const { - return "HERF-800-2T"; + return "HERF-600/800-2T"; } const byteAssign_t* HERF_2CH::getByteAssignment() const diff --git a/lib/Hoymiles/src/inverters/HERF_4CH.cpp b/lib/Hoymiles/src/inverters/HERF_4CH.cpp index dcd01b6d5..a3484734a 100644 --- a/lib/Hoymiles/src/inverters/HERF_4CH.cpp +++ b/lib/Hoymiles/src/inverters/HERF_4CH.cpp @@ -5,7 +5,9 @@ #include "HERF_4CH.h" HERF_4CH::HERF_4CH(HoymilesRadio* radio, const uint64_t serial) - : HM_4CH(radio, serial) {}; + : HM_4CH(radio, serial) +{ +} bool HERF_4CH::isValidSerial(const uint64_t serial) { diff --git a/lib/Hoymiles/src/inverters/HMS_1CH.cpp b/lib/Hoymiles/src/inverters/HMS_1CH.cpp index a04b0b7d2..a5ae00d59 100644 --- a/lib/Hoymiles/src/inverters/HMS_1CH.cpp +++ b/lib/Hoymiles/src/inverters/HMS_1CH.cpp @@ -33,7 +33,9 @@ static const channelMetaData_t channelMetaData[] = { }; HMS_1CH::HMS_1CH(HoymilesRadio* radio, const uint64_t serial) - : HMS_Abstract(radio, serial) {}; + : HMS_Abstract(radio, serial) +{ +} bool HMS_1CH::isValidSerial(const uint64_t serial) { diff --git a/lib/Hoymiles/src/inverters/HMS_1CHv2.cpp b/lib/Hoymiles/src/inverters/HMS_1CHv2.cpp index 3716cd98f..024b15f5a 100644 --- a/lib/Hoymiles/src/inverters/HMS_1CHv2.cpp +++ b/lib/Hoymiles/src/inverters/HMS_1CHv2.cpp @@ -33,18 +33,20 @@ static const channelMetaData_t channelMetaData[] = { }; HMS_1CHv2::HMS_1CHv2(HoymilesRadio* radio, const uint64_t serial) - : HMS_Abstract(radio, serial) {}; + : HMS_Abstract(radio, serial) +{ +} bool HMS_1CHv2::isValidSerial(const uint64_t serial) { // serial >= 0x112500000000 && serial <= 0x1125ffffffff uint16_t preSerial = (serial >> 32) & 0xffff; - return preSerial == 0x1125; + return preSerial == 0x1125 || preSerial == 0x1400; } String HMS_1CHv2::typeName() const { - return "HMS-500-1T v2"; + return "HMS-450/500-1T v2"; } const byteAssign_t* HMS_1CHv2::getByteAssignment() const diff --git a/lib/Hoymiles/src/inverters/HMS_2CH.cpp b/lib/Hoymiles/src/inverters/HMS_2CH.cpp index c22fcf0d4..fa7d5e3bf 100644 --- a/lib/Hoymiles/src/inverters/HMS_2CH.cpp +++ b/lib/Hoymiles/src/inverters/HMS_2CH.cpp @@ -41,7 +41,9 @@ static const channelMetaData_t channelMetaData[] = { }; HMS_2CH::HMS_2CH(HoymilesRadio* radio, const uint64_t serial) - : HMS_Abstract(radio, serial) {}; + : HMS_Abstract(radio, serial) +{ +} bool HMS_2CH::isValidSerial(const uint64_t serial) { diff --git a/lib/Hoymiles/src/inverters/HMS_4CH.cpp b/lib/Hoymiles/src/inverters/HMS_4CH.cpp index 0d8d16702..791259d28 100644 --- a/lib/Hoymiles/src/inverters/HMS_4CH.cpp +++ b/lib/Hoymiles/src/inverters/HMS_4CH.cpp @@ -57,7 +57,9 @@ static const channelMetaData_t channelMetaData[] = { }; HMS_4CH::HMS_4CH(HoymilesRadio* radio, const uint64_t serial) - : HMS_Abstract(radio, serial) {}; + : HMS_Abstract(radio, serial) +{ +} bool HMS_4CH::isValidSerial(const uint64_t serial) { @@ -81,6 +83,13 @@ uint8_t HMS_4CH::getByteAssignmentSize() const return sizeof(byteAssignment) / sizeof(byteAssignment[0]); } +bool HMS_4CH::supportsPowerDistributionLogic() +{ + // This feature was added in inverter firmware version 01.01.12 and + // will limit the AC output instead of limiting the DC inputs. + return DevInfo()->getFwBuildVersion() >= 10112U; +} + const channelMetaData_t* HMS_4CH::getChannelMetaData() const { return channelMetaData; @@ -90,10 +99,3 @@ uint8_t HMS_4CH::getChannelMetaDataSize() const { return sizeof(channelMetaData) / sizeof(channelMetaData[0]); } - -bool HMS_4CH::supportsPowerDistributionLogic() -{ - // This feature was added in inverter firmware version 01.01.12 and - // will limit the AC output instead of limiting the DC inputs. - return DevInfo()->getFwBuildVersion() >= 10112U; -}; diff --git a/lib/Hoymiles/src/inverters/HMS_4CH.h b/lib/Hoymiles/src/inverters/HMS_4CH.h index fb2d1662f..9bd0b1706 100644 --- a/lib/Hoymiles/src/inverters/HMS_4CH.h +++ b/lib/Hoymiles/src/inverters/HMS_4CH.h @@ -10,7 +10,7 @@ class HMS_4CH : public HMS_Abstract { String typeName() const; const byteAssign_t* getByteAssignment() const; uint8_t getByteAssignmentSize() const; + bool supportsPowerDistributionLogic() final; const channelMetaData_t* getChannelMetaData() const; uint8_t getChannelMetaDataSize() const; - bool supportsPowerDistributionLogic() final; }; diff --git a/lib/Hoymiles/src/inverters/HMT_4CH.cpp b/lib/Hoymiles/src/inverters/HMT_4CH.cpp index 48caf8ce8..7e2e05f9b 100644 --- a/lib/Hoymiles/src/inverters/HMT_4CH.cpp +++ b/lib/Hoymiles/src/inverters/HMT_4CH.cpp @@ -66,7 +66,9 @@ static const channelMetaData_t channelMetaData[] = { }; HMT_4CH::HMT_4CH(HoymilesRadio* radio, const uint64_t serial) - : HMT_Abstract(radio, serial) {}; + : HMT_Abstract(radio, serial) +{ +} bool HMT_4CH::isValidSerial(const uint64_t serial) { diff --git a/lib/Hoymiles/src/inverters/HMT_6CH.cpp b/lib/Hoymiles/src/inverters/HMT_6CH.cpp index 9e835671d..6adf2112b 100644 --- a/lib/Hoymiles/src/inverters/HMT_6CH.cpp +++ b/lib/Hoymiles/src/inverters/HMT_6CH.cpp @@ -82,7 +82,9 @@ static const channelMetaData_t channelMetaData[] = { }; HMT_6CH::HMT_6CH(HoymilesRadio* radio, const uint64_t serial) - : HMT_Abstract(radio, serial) {}; + : HMT_Abstract(radio, serial) +{ +} bool HMT_6CH::isValidSerial(const uint64_t serial) { diff --git a/lib/Hoymiles/src/inverters/HMT_Abstract.cpp b/lib/Hoymiles/src/inverters/HMT_Abstract.cpp index 50c895cc6..095b3d2e9 100644 --- a/lib/Hoymiles/src/inverters/HMT_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HMT_Abstract.cpp @@ -12,7 +12,7 @@ HMT_Abstract::HMT_Abstract(HoymilesRadio* radio, const uint64_t serial) : HM_Abstract(radio, serial) { EventLog()->setMessageType(AlarmMessageType_t::HMT); -}; +} bool HMT_Abstract::sendChangeChannelRequest() { @@ -26,4 +26,4 @@ bool HMT_Abstract::sendChangeChannelRequest() _radio->enqueCommand(cmdChannel); return true; -}; +} diff --git a/lib/Hoymiles/src/inverters/HM_1CH.cpp b/lib/Hoymiles/src/inverters/HM_1CH.cpp index 37761942b..cd241228f 100644 --- a/lib/Hoymiles/src/inverters/HM_1CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_1CH.cpp @@ -33,7 +33,9 @@ static const channelMetaData_t channelMetaData[] = { }; HM_1CH::HM_1CH(HoymilesRadio* radio, const uint64_t serial) - : HM_Abstract(radio, serial) {}; + : HM_Abstract(radio, serial) +{ +} bool HM_1CH::isValidSerial(const uint64_t serial) { diff --git a/lib/Hoymiles/src/inverters/HM_2CH.cpp b/lib/Hoymiles/src/inverters/HM_2CH.cpp index 28778463d..4ea19a8a5 100644 --- a/lib/Hoymiles/src/inverters/HM_2CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_2CH.cpp @@ -42,7 +42,9 @@ static const channelMetaData_t channelMetaData[] = { }; HM_2CH::HM_2CH(HoymilesRadio* radio, const uint64_t serial) - : HM_Abstract(radio, serial) {}; + : HM_Abstract(radio, serial) +{ +} bool HM_2CH::isValidSerial(const uint64_t serial) { diff --git a/lib/Hoymiles/src/inverters/HM_4CH.cpp b/lib/Hoymiles/src/inverters/HM_4CH.cpp index 4d90e4ad4..11534e214 100644 --- a/lib/Hoymiles/src/inverters/HM_4CH.cpp +++ b/lib/Hoymiles/src/inverters/HM_4CH.cpp @@ -57,7 +57,9 @@ static const channelMetaData_t channelMetaData[] = { }; HM_4CH::HM_4CH(HoymilesRadio* radio, const uint64_t serial) - : HM_Abstract(radio, serial) {}; + : HM_Abstract(radio, serial) +{ +} bool HM_4CH::isValidSerial(const uint64_t serial) { diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.cpp b/lib/Hoymiles/src/inverters/HM_Abstract.cpp index 9f426af4b..fc818714f 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HM_Abstract.cpp @@ -14,7 +14,9 @@ #include "commands/SystemConfigParaCommand.h" HM_Abstract::HM_Abstract(HoymilesRadio* radio, const uint64_t serial) - : InverterAbstract(radio, serial) {}; + : InverterAbstract(radio, serial) +{ +} bool HM_Abstract::sendStatsRequest() { @@ -225,3 +227,8 @@ bool HM_Abstract::sendGridOnProFileParaRequest() return true; } + +bool HM_Abstract::supportsPowerDistributionLogic() +{ + return false; +} diff --git a/lib/Hoymiles/src/inverters/HM_Abstract.h b/lib/Hoymiles/src/inverters/HM_Abstract.h index 1088403ba..9f5c4b31a 100644 --- a/lib/Hoymiles/src/inverters/HM_Abstract.h +++ b/lib/Hoymiles/src/inverters/HM_Abstract.h @@ -16,7 +16,7 @@ class HM_Abstract : public InverterAbstract { bool sendRestartControlRequest(); bool resendPowerControlRequest(); bool sendGridOnProFileParaRequest(); - bool supportsPowerDistributionLogic() override { return false; }; + bool supportsPowerDistributionLogic() override; private: uint8_t _lastAlarmLogCnt = 0; diff --git a/lib/Hoymiles/src/inverters/README.md b/lib/Hoymiles/src/inverters/README.md index b55445328..93878b139 100644 --- a/lib/Hoymiles/src/inverters/README.md +++ b/lib/Hoymiles/src/inverters/README.md @@ -1,16 +1,16 @@ # Class overview | Class | Models | Serial range | -| --------------| --------------------------- | ------------- -- | +| ------------- | --------------------------- | ---------------- | | HM_1CH | HM-300/350/400-1T | 1121 | | HM_2CH | HM-600/700/800-2T | 1141 | | HM_4CH | HM-1000/1200/1500-4T | 1161 | | HMS_1CH | HMS-300/350/400/450/500-1T | 1124 | -| HMS_1CHv2 | HMS-500-1T v2 | 1125 | +| HMS_1CHv2 | HMS-450/500-1T v2 | 1125, 1400 | | HMS_2CH | HMS-600/700/800/900/1000-2T | 1143, 1144, 1410 | | HMS_4CH | HMS-1600/1800/2000-4T | 1164 | | HMT_4CH | HMT-1600/1800/2000-4T | 1361 | | HMT_6CH | HMT-1800/2250-6T | 1382 | | HERF_1CH | HERF 300 | 2841 | -| HERF_2CH | HERF 800 | 2821 | +| HERF_2CH | HERF 600/800 | 2821 | | HERF_4CH | HERF 1800 | 2801 | diff --git a/lib/Hoymiles/src/parser/DevInfoParser.cpp b/lib/Hoymiles/src/parser/DevInfoParser.cpp index fb0fe3e89..b0bd11dd8 100644 --- a/lib/Hoymiles/src/parser/DevInfoParser.cpp +++ b/lib/Hoymiles/src/parser/DevInfoParser.cpp @@ -81,6 +81,7 @@ const devInfo_t devInfo[] = { { { 0x10, 0x33, 0x11, ALL }, 1800, "HMT-1800-6T" }, // 01 { { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250-6T" }, // 01 + { { 0xF1, 0x01, 0x10, ALL }, 600, "HERF-600" }, // 00 { { 0xF1, 0x01, 0x14, ALL }, 800, "HERF-800" }, // 00 { { 0xF1, 0x01, 0x24, ALL }, 1600, "HERF-1600" }, // 00 { { 0xF1, 0x01, 0x22, ALL }, 1800, "HERF-1800" }, // 00 diff --git a/lib/Hoymiles/src/queue/CommandQueue.cpp b/lib/Hoymiles/src/queue/CommandQueue.cpp new file mode 100644 index 000000000..e4b6069ab --- /dev/null +++ b/lib/Hoymiles/src/queue/CommandQueue.cpp @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024 Thomas Basler and others + */ +#include "CommandQueue.h" +#include "../inverters/InverterAbstract.h" +#include + +void CommandQueue::removeAllEntriesForInverter(InverterAbstract* inv) +{ + std::lock_guard lock(_mutex); + + auto it = std::remove_if(_queue.begin(), _queue.end(), + [&inv](std::shared_ptr v) -> bool { return v.get()->getTargetAddress() == inv->serial(); }); + _queue.erase(it, _queue.end()); +} + +void CommandQueue::removeDuplicatedEntries(std::shared_ptr cmd) +{ + std::lock_guard lock(_mutex); + + auto it = std::remove_if(_queue.begin() + 1, _queue.end(), + [&cmd](std::shared_ptr v) -> bool { + return cmd->areSameParameter(v.get()) + && cmd.get()->getQueueInsertType() == QueueInsertType::RemoveOldest; + }); + _queue.erase(it, _queue.end()); +} + +void CommandQueue::replaceEntries(std::shared_ptr cmd) +{ + std::lock_guard lock(_mutex); + + std::replace_if(_queue.begin() + 1, _queue.end(), + [&cmd](std::shared_ptr v)-> bool { + return cmd.get()->getQueueInsertType() == QueueInsertType::ReplaceExistent + && cmd->areSameParameter(v.get()); + }, + cmd + ); +} + +uint8_t CommandQueue::countSimilarCommands(std::shared_ptr cmd) +{ + std::lock_guard lock(_mutex); + + return std::count_if(_queue.begin(), _queue.end(), + [&cmd](std::shared_ptr v) -> bool { + return cmd->areSameParameter(v.get()); + }); +} diff --git a/lib/Hoymiles/src/queue/CommandQueue.h b/lib/Hoymiles/src/queue/CommandQueue.h new file mode 100644 index 000000000..f27aeaa3b --- /dev/null +++ b/lib/Hoymiles/src/queue/CommandQueue.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "../commands/CommandAbstract.h" +#include +#include + +class InverterAbstract; + +class CommandQueue : public ThreadSafeQueue> { +public: + void removeAllEntriesForInverter(InverterAbstract* inv); + void removeDuplicatedEntries(std::shared_ptr cmd); + void replaceEntries(std::shared_ptr cmd); + + uint8_t countSimilarCommands(std::shared_ptr cmd); +}; diff --git a/lib/ThreadSafeQueue/src/ThreadSafeQueue.h b/lib/ThreadSafeQueue/src/ThreadSafeQueue.h index 9a195c603..2569e630d 100644 --- a/lib/ThreadSafeQueue/src/ThreadSafeQueue.h +++ b/lib/ThreadSafeQueue/src/ThreadSafeQueue.h @@ -3,7 +3,7 @@ #include #include -#include +#include template class ThreadSafeQueue { @@ -33,14 +33,14 @@ class ThreadSafeQueue { return {}; } T tmp = _queue.front(); - _queue.pop(); + _queue.pop_front(); return tmp; } void push(const T& item) { std::lock_guard lock(_mutex); - _queue.push(item); + _queue.push_back(item); } T front() @@ -49,6 +49,10 @@ class ThreadSafeQueue { return _queue.front(); } +protected: + std::deque _queue; + mutable std::mutex _mutex; + private: // Moved out of public interface to prevent races between this // and pop(). @@ -56,7 +60,4 @@ class ThreadSafeQueue { { return _queue.empty(); } - - std::queue _queue; - mutable std::mutex _mutex; }; diff --git a/pio-scripts/auto_firmware_version.py b/pio-scripts/auto_firmware_version.py index c1fa9771d..2d2f3be71 100644 --- a/pio-scripts/auto_firmware_version.py +++ b/pio-scripts/auto_firmware_version.py @@ -49,7 +49,7 @@ def get_firmware_specifier_build_flag(): build_version = get_build_version() build_flag = "-D AUTO_GIT_HASH=\\\"" + build_version + "\\\"" build_branch = get_build_branch() - build_flag += " -D AUTO_GIT_BRANCH=\\\"" + branch_name + "\\\"" + build_flag += " -D AUTO_GIT_BRANCH=\\\"" + build_branch + "\\\"" return (build_flag) diff --git a/pio-scripts/create_factory_bin.py b/pio-scripts/create_factory_bin.py index ec8274e21..e9647d986 100644 --- a/pio-scripts/create_factory_bin.py +++ b/pio-scripts/create_factory_bin.py @@ -130,4 +130,5 @@ def esp32_create_combined_bin(source, target, env): esptool.main(cmd) -env.AddPostAction("buildprog", esp32_create_combined_bin) +from SCons.Script import AlwaysBuild +AlwaysBuild(env.AddPostAction("buildprog", esp32_create_combined_bin)) diff --git a/platformio.ini b/platformio.ini index 42d6abf41..7c733e6c6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,6 +30,7 @@ build_flags = -DCONFIG_ASYNC_TCP_EVENT_QUEUE_SIZE=128 -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128 -DEMC_TASK_STACK_SIZE=6400 +; -DHOY_DEBUG_QUEUE -Wall -Wextra -Wunused -Wmisleading-indentation -Wduplicated-cond -Wlogical-op -Wnull-dereference ; Have to remove -Werror because of ; https://github.com/espressif/arduino-esp32/issues/9044 and @@ -41,8 +42,8 @@ build_unflags = -std=gnu++11 lib_deps = - mathieucarbou/ESPAsyncWebServer @ 3.3.22 - bblanchon/ArduinoJson @ 7.2.0 + mathieucarbou/ESPAsyncWebServer @ 3.4.2 + bblanchon/ArduinoJson @ 7.3.0 https://github.com/bertmelis/espMqttClient.git#v1.7.0 nrf24/RF24 @ 1.4.10 olikraus/U8g2 @ 2.36.2 diff --git a/src/MqttHandleDtu.cpp b/src/MqttHandleDtu.cpp index df025f12c..63aec7e18 100644 --- a/src/MqttHandleDtu.cpp +++ b/src/MqttHandleDtu.cpp @@ -32,7 +32,7 @@ void MqttHandleDtuClass::loop() return; } - MqttSettings.publish("dtu/uptime", String(millis() / 1000)); + MqttSettings.publish("dtu/uptime", String(esp_timer_get_time() / 1000000)); MqttSettings.publish("dtu/ip", NetworkSettings.localIP().toString()); MqttSettings.publish("dtu/hostname", NetworkSettings.getHostname()); MqttSettings.publish("dtu/heap/size", String(ESP.getHeapSize())); diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index b7901545f..83b2e559e 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -70,6 +70,7 @@ void MqttHandleHassClass::publishConfig() publishDtuSensor("Yield Total", "ac/yieldtotal", "kWh", "", DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING, CATEGORY_NONE); publishDtuSensor("Yield Day", "ac/yieldday", "Wh", "", DEVICE_CLS_ENERGY, STATE_CLS_TOTAL_INCREASING, CATEGORY_NONE); publishDtuSensor("AC Power", "ac/power", "W", "", DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT, CATEGORY_NONE); + publishDtuSensor("DC Power", "dc/power", "W", "", DEVICE_CLS_PWR, STATE_CLS_MEASUREMENT, CATEGORY_NONE); publishDtuBinarySensor("Status", config.Mqtt.Lwt.Topic, config.Mqtt.Lwt.Value_Online, config.Mqtt.Lwt.Value_Offline, DEVICE_CLS_CONNECTIVITY, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); diff --git a/src/MqttSettings.cpp b/src/MqttSettings.cpp index 63455f622..d81152b0d 100644 --- a/src/MqttSettings.cpp +++ b/src/MqttSettings.cpp @@ -182,7 +182,7 @@ String MqttSettingsClass::getPrefix() const return Configuration.get().Mqtt.Topic; } -String MqttSettingsClass::getClientId() +String MqttSettingsClass::getClientId() const { String clientId = Configuration.get().Mqtt.ClientId; if (clientId == "") { diff --git a/src/NetworkSettings.cpp b/src/NetworkSettings.cpp index 3852db9fa..81ec21dce 100644 --- a/src/NetworkSettings.cpp +++ b/src/NetworkSettings.cpp @@ -290,8 +290,7 @@ void NetworkSettingsClass::applyConfig() MessageOutput.print("new credentials... "); WiFi.begin( Configuration.get().WiFi.Ssid, - Configuration.get().WiFi.Password, - WIFI_ALL_CHANNEL_SCAN); + Configuration.get().WiFi.Password); } else { MessageOutput.print("existing credentials... "); WiFi.begin(); diff --git a/src/WebApi_device.cpp b/src/WebApi_device.cpp index d55a5322b..c7f1f358e 100644 --- a/src/WebApi_device.cpp +++ b/src/WebApi_device.cpp @@ -160,10 +160,14 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) return; } + bool performRestart = false; + { auto guard = Configuration.getWriteGuard(); auto& config = guard.getConfig(); + performRestart = root["curPin"]["name"].as() != config.Dev_PinMapping; + strlcpy(config.Dev_PinMapping, root["curPin"]["name"].as().c_str(), sizeof(config.Dev_PinMapping)); config.Display.Rotation = root["display"]["rotation"].as(); config.Display.PowerSafe = root["display"]["power_safe"].as(); @@ -180,7 +184,6 @@ void WebApiDeviceClass::onDeviceAdminPost(AsyncWebServerRequest* request) } auto const& config = Configuration.get(); - bool performRestart = root["curPin"]["name"].as() != config.Dev_PinMapping; Display.setDiagramMode(static_cast(config.Display.Diagram.Mode)); Display.setOrientation(config.Display.Rotation); diff --git a/src/WebApi_firmware.cpp b/src/WebApi_firmware.cpp index 1511ae590..a2a9205ee 100644 --- a/src/WebApi_firmware.cpp +++ b/src/WebApi_firmware.cpp @@ -5,11 +5,10 @@ #include "WebApi_firmware.h" #include "Configuration.h" #include "RestartHelper.h" -#include "Update.h" -#include "Utils.h" #include "WebApi.h" #include "helper.h" #include +#include #include "esp_partition.h" void WebApiFirmwareClass::init(AsyncWebServer& server, Scheduler& scheduler) diff --git a/src/main.cpp b/src/main.cpp index ec97d7b52..7f09c8541 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -105,7 +105,7 @@ void setup() // Load PinMapping MessageOutput.print("Reading PinMapping... "); - if (PinMapping.init(String(Configuration.get().Dev_PinMapping))) { + if (PinMapping.init(Configuration.get().Dev_PinMapping)) { MessageOutput.print("found valid mapping "); } else { MessageOutput.print("using default config "); @@ -115,7 +115,7 @@ void setup() SerialPortManager.init(); - // Initialize WiFi + // Initialize Network MessageOutput.print("Initialize Network... "); NetworkSettings.init(scheduler); MessageOutput.println("done"); diff --git a/webapp/package.json b/webapp/package.json index a646f6c0f..d057a7f15 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -17,35 +17,35 @@ "bootstrap": "^5.3.3", "bootstrap-icons-vue": "^1.11.3", "mitt": "^3.0.1", - "sortablejs": "^1.15.3", + "sortablejs": "^1.15.6", "spark-md5": "^3.0.2", - "vue": "^3.5.12", - "vue-i18n": "10.0.4", - "vue-router": "^4.4.5" + "vue": "^3.5.13", + "vue-i18n": "11.0.1", + "vue-router": "^4.5.0" }, "devDependencies": { - "@intlify/unplugin-vue-i18n": "^5.2.0", + "@intlify/unplugin-vue-i18n": "^6.0.3", "@tsconfig/node22": "^22.0.0", "@types/bootstrap": "^5.2.10", - "@types/node": "^22.9.0", + "@types/node": "^22.10.6", "@types/pulltorefreshjs": "^0.1.7", "@types/sortablejs": "^1.15.8", "@types/spark-md5": "^3.0.5", - "@vitejs/plugin-vue": "^5.1.4", - "@vue/eslint-config-typescript": "^14.1.3", - "@vue/tsconfig": "^0.5.1", - "eslint": "^9.14.0", - "eslint-plugin-vue": "^9.30.0", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/eslint-config-typescript": "^14.3.0", + "@vue/tsconfig": "^0.7.0", + "eslint": "^9.18.0", + "eslint-plugin-vue": "^9.32.0", "npm-run-all": "^4.1.5", - "prettier": "^3.3.3", + "prettier": "^3.4.2", "pulltorefreshjs": "^0.1.22", "sass": "=1.77.6", - "terser": "^5.36.0", - "typescript": "^5.6.3", - "vite": "^5.4.10", + "terser": "^5.37.0", + "typescript": "~5.6.3", + "vite": "^6.0.7", "vite-plugin-compression": "^0.5.1", "vite-plugin-css-injected-by-js": "^3.5.2", - "vue-tsc": "^2.1.10" + "vue-tsc": "^2.2.0" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/webapp/src/components/BatteryView.vue b/webapp/src/components/BatteryView.vue index 78e151280..0b4c0cffa 100644 --- a/webapp/src/components/BatteryView.vue +++ b/webapp/src/components/BatteryView.vue @@ -13,7 +13,7 @@ class="card-header d-flex justify-content-between align-items-center" :class="{ 'text-bg-danger': batteryData.data_age >= 20, - 'text-bg-primary': batteryData.data_age < 20, + 'text-bg-success': batteryData.data_age < 20, }" >
diff --git a/webapp/src/components/DevInfo.vue b/webapp/src/components/DevInfo.vue index a56f8ce64..da2d81aa1 100644 --- a/webapp/src/components/DevInfo.vue +++ b/webapp/src/components/DevInfo.vue @@ -48,8 +48,9 @@ {{ $t('devinfo.SupportsPowerDistributionLogic') }} - {{ $t('devinfo.yes') }} - {{ $t('devinfo.no') }} + + + @@ -58,6 +59,7 @@