diff --git a/lang/el.lang.json b/lang/el.lang.json index 5c5ef7cc96..5e3299689c 100644 --- a/lang/el.lang.json +++ b/lang/el.lang.json @@ -170,6 +170,8 @@ "RxFailNothing": "Αποτυχημένα RX: Δεν λαμβάνετε τίποτα", "RxFailPartial": "Αποτυχημένα RX: Μερική λήψη", "RxFailCorrupt": "Αποτυχημένα RX: Λήψη κατεστραμμένου", + "RxLastFrequency": "RX frequency of the latest package received", + "MHz": "{mhz} MHz", "TxReRequest": "TX Επαναίτηση Fragment", "StatsReset": "Επαναφορά Στατιστικών", "StatsResetting": "Επαναφορά...", diff --git a/lang/es.lang.json b/lang/es.lang.json index 1918f680e4..e7d2208919 100644 --- a/lang/es.lang.json +++ b/lang/es.lang.json @@ -170,6 +170,8 @@ "RxFailNothing": "RX Fail: Receive Nothing", "RxFailPartial": "RX Fail: Receive Partial", "RxFailCorrupt": "RX Fail: Receive Corrupt", + "RxLastFrequency": "RX frequency of the latest package received", + "MHz": "{mhz} MHz", "TxReRequest": "TX Re-Request Fragment", "StatsReset": "Reset Statistics", "StatsResetting": "Resetting...", diff --git a/lang/it.lang.json b/lang/it.lang.json index 1f53753222..fdb93c0c77 100644 --- a/lang/it.lang.json +++ b/lang/it.lang.json @@ -170,6 +170,8 @@ "RxFailNothing": "RX Fail: Receive Nothing", "RxFailPartial": "RX Fail: Receive Partial", "RxFailCorrupt": "RX Fail: Receive Corrupt", + "RxLastFrequency": "RX frequency of the latest package received", + "MHz": "{mhz} MHz", "TxReRequest": "TX Re-Request Fragment", "StatsReset": "Reset Statistics", "StatsResetting": "Resetting...", diff --git a/lib/Hoymiles/src/Hoymiles.cpp b/lib/Hoymiles/src/Hoymiles.cpp index 806a473321..446adc0976 100644 --- a/lib/Hoymiles/src/Hoymiles.cpp +++ b/lib/Hoymiles/src/Hoymiles.cpp @@ -67,7 +67,9 @@ void HoymilesClass::loop() _messageOutput->print("Fetch inverter: "); _messageOutput->println(iv->serial(), HEX); - if (!iv->isReachable()) { + iv->getFrequencyManager()->startNextFetch(); + + if (!iv->isReachable() || iv->getFrequencyManager()->shouldSendChangeChannelCommand()) { iv->sendChangeChannelRequest(); } diff --git a/lib/Hoymiles/src/HoymilesRadio.cpp b/lib/Hoymiles/src/HoymilesRadio.cpp index 896137e5c0..3fd152bcb9 100644 --- a/lib/Hoymiles/src/HoymilesRadio.cpp +++ b/lib/Hoymiles/src/HoymilesRadio.cpp @@ -5,6 +5,7 @@ #include "HoymilesRadio.h" #include "Hoymiles.h" #include "crc.h" +#include "frequencymanagers/FrequencyManagerAbstract.h" serial_u HoymilesRadio::DtuSerial() const { @@ -34,21 +35,21 @@ bool HoymilesRadio::checkFragmentCrc(const fragment_t& fragment) const return (crc == fragment.fragment[fragment.len - 1]); } -void HoymilesRadio::sendRetransmitPacket(const uint8_t fragment_id) +void HoymilesRadio::sendRetransmitPacket(const uint8_t fragment_id, FrequencyManagerAbstract& freq_mgr) { CommandAbstract* cmd = _commandQueue.front().get(); CommandAbstract* requestCmd = cmd->getRequestFrameCommand(fragment_id); if (requestCmd != nullptr) { - sendEsbPacket(*requestCmd); + sendEsbPacket(*requestCmd, freq_mgr); } } -void HoymilesRadio::sendLastPacketAgain() +void HoymilesRadio::sendLastPacketAgain(FrequencyManagerAbstract &freq_mgr) { CommandAbstract* cmd = _commandQueue.front().get(); - sendEsbPacket(*cmd); + sendEsbPacket(*cmd, freq_mgr); } void HoymilesRadio::handleReceivedPackage() @@ -60,9 +61,10 @@ void HoymilesRadio::handleReceivedPackage() if (nullptr != inv) { CommandAbstract* cmd = _commandQueue.front().get(); uint8_t verifyResult = inv->verifyAllFragments(*cmd); + inv->getFrequencyManager()->processRXResult(cmd, verifyResult); if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) { Hoymiles.getMessageOutput()->println("Nothing received, resend whole request"); - sendLastPacketAgain(); + sendLastPacketAgain(*inv->getFrequencyManager()); } else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) { Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded"); @@ -101,7 +103,7 @@ void HoymilesRadio::handleReceivedPackage() // Statistics: Count TX Re-Request Fragment inv->RadioStats.TxReRequestFragment++; - sendRetransmitPacket(verifyResult); + sendRetransmitPacket(verifyResult, *inv->getFrequencyManager()); } else { // Successful received all packages @@ -132,7 +134,7 @@ void HoymilesRadio::handleReceivedPackage() // Statistics: TX Requests inv->RadioStats.TxRequestData++; - sendEsbPacket(*cmd); + sendEsbPacket(*cmd, *inv->getFrequencyManager()); } else { Hoymiles.getMessageOutput()->println("TX: Invalid inverter found"); _commandQueue.pop(); diff --git a/lib/Hoymiles/src/HoymilesRadio.h b/lib/Hoymiles/src/HoymilesRadio.h index 95dfe8b016..a7cd5be1fd 100644 --- a/lib/Hoymiles/src/HoymilesRadio.h +++ b/lib/Hoymiles/src/HoymilesRadio.h @@ -3,6 +3,7 @@ #include "Arduino.h" #include "commands/CommandAbstract.h" +#include "frequencymanagers/FrequencyManagerAbstract.h" #include "queue/CommandQueue.h" #include "types.h" #include @@ -75,9 +76,9 @@ class HoymilesRadio { static void dumpBuf(const uint8_t buf[], const uint8_t len, const bool appendNewline = true); bool checkFragmentCrc(const fragment_t& fragment) const; - virtual void sendEsbPacket(CommandAbstract& cmd) = 0; - void sendRetransmitPacket(const uint8_t fragment_id); - void sendLastPacketAgain(); + virtual void sendEsbPacket(CommandAbstract& cmd, FrequencyManagerAbstract& freq_mgr) = 0; + void sendRetransmitPacket(const uint8_t fragment_id, FrequencyManagerAbstract& freq_mgr); + void sendLastPacketAgain(FrequencyManagerAbstract& freq_mgr); void handleReceivedPackage(); serial_u _dtuSerial; diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp index e58221cd9c..732c934a3e 100644 --- a/lib/Hoymiles/src/HoymilesRadio_CMT.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.cpp @@ -7,6 +7,7 @@ #include "crc.h" #include #include +#include "frequencymanagers/FrequencyManagerAbstract.h" constexpr CountryFrequencyDefinition_t make_value(FrequencyBand_t Band, uint32_t Freq_Legal_Min, uint32_t Freq_Legal_Max, uint32_t Freq_Default, uint32_t Freq_StartUp) { @@ -192,6 +193,7 @@ void HoymilesRadio_CMT::setPALevel(const int8_t paLevel) if (!_isInitialized) { return; } + this->_pa_level = paLevel; if (_radio->setPALevel(paLevel)) { Hoymiles.getMessageOutput()->printf("CMT TX power set to %" PRId8 " dBm\r\n", paLevel); @@ -232,6 +234,16 @@ uint32_t HoymilesRadio_CMT::getMaxFrequency() const return countryDefinition.at(_countryMode).Freq_Max; } +uint32_t HoymilesRadio_CMT::getLegalMinFrequency() const +{ + return countryDefinition.at(_countryMode).Freq_Legal_Min; +} + +uint32_t HoymilesRadio_CMT::getLegalMaxFrequency() const +{ + return countryDefinition.at(_countryMode).Freq_Legal_Max; +} + CountryModeId_t HoymilesRadio_CMT::getCountryMode() const { return _countryMode; @@ -262,7 +274,29 @@ void ARDUINO_ISR_ATTR HoymilesRadio_CMT::handleInt2() _packetReceived = true; } -void HoymilesRadio_CMT::sendEsbPacket(CommandAbstract& cmd) +void HoymilesRadio_CMT::handleTxError(bool is_error) { + if(!is_error) { + this->_tx_error_counter = 0; + return; + } + this->_tx_error_counter++; + if(_tx_error_counter==5 || _tx_error_counter == 10 || _tx_error_counter == 15) { + Hoymiles.getMessageOutput()->println("TX recovery: Re-applying PA level"); + this->setPALevel(this->_pa_level); + return; + } + if(_tx_error_counter==20 || _tx_error_counter == 25) { + Hoymiles.getMessageOutput()->println("TX recovery: Re-initializing radio"); + this->_radio->begin(); + } + if(_tx_error_counter >= 30) { + Hoymiles.getMessageOutput()->println("TX recovery: Giving up"); + this->_isInitialized = false; + } + +} + +void HoymilesRadio_CMT::sendEsbPacket(CommandAbstract& cmd, FrequencyManagerAbstract &freq_mgr) { cmd.incrementSendCount(); @@ -270,18 +304,20 @@ void HoymilesRadio_CMT::sendEsbPacket(CommandAbstract& cmd) _radio->stopListening(); - if (cmd.getDataPayload()[0] == 0x56) { // @todo(tbnobody) Bad hack to identify ChannelChange Command - cmtSwitchDtuFreq(getInvBootFrequency()); - } + cmtSwitchDtuFreq(freq_mgr.getTXFrequency(cmd)); Hoymiles.getMessageOutput()->printf("TX %s %.2f MHz --> ", cmd.getCommandName().c_str(), getFrequencyFromChannel(_radio->getChannel()) / 1000000.0); cmd.dumpDataPayload(Hoymiles.getMessageOutput()); - if (!_radio->write(cmd.getDataPayload(), cmd.getDataSize())) { + bool tx_worked = _radio->write(cmd.getDataPayload(), cmd.getDataSize()); + if (!tx_worked) { Hoymiles.getMessageOutput()->println("TX SPI Timeout"); } - cmtSwitchDtuFreq(_inverterTargetFrequency); + this->handleTxError(!tx_worked); + + + cmtSwitchDtuFreq(freq_mgr.getRXFrequency(cmd)); _radio->startListening(); _busyFlag = true; _rxTimeout.set(cmd.getTimeout()); diff --git a/lib/Hoymiles/src/HoymilesRadio_CMT.h b/lib/Hoymiles/src/HoymilesRadio_CMT.h index 770617fe36..ad59097e90 100644 --- a/lib/Hoymiles/src/HoymilesRadio_CMT.h +++ b/lib/Hoymiles/src/HoymilesRadio_CMT.h @@ -51,6 +51,8 @@ class HoymilesRadio_CMT : public HoymilesRadio { uint32_t getMinFrequency() const; uint32_t getMaxFrequency() const; + uint32_t getLegalMinFrequency() const; + uint32_t getLegalMaxFrequency() const; static constexpr uint32_t getChannelWidth() { return FH_OFFSET * CMT2300A_ONE_STEP_SIZE; @@ -70,7 +72,7 @@ class HoymilesRadio_CMT : public HoymilesRadio { void ARDUINO_ISR_ATTR handleInt1(); void ARDUINO_ISR_ATTR handleInt2(); - void sendEsbPacket(CommandAbstract& cmd); + void sendEsbPacket(CommandAbstract& cmd, FrequencyManagerAbstract &freq_mgr); std::unique_ptr _radio; @@ -79,6 +81,7 @@ class HoymilesRadio_CMT : public HoymilesRadio { bool _gpio2_configured = false; bool _gpio3_configured = false; + int8_t _pa_level = 0; std::queue _rxBuffer; TimeoutHelper _txTimeout; @@ -88,4 +91,7 @@ class HoymilesRadio_CMT : public HoymilesRadio { bool cmtSwitchDtuFreq(const uint32_t to_frequency); CountryModeId_t _countryMode; + + int _tx_error_counter = 0; + void handleTxError(bool is_error); }; diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp index 0019a4bdb5..d3e9d7fae2 100644 --- a/lib/Hoymiles/src/HoymilesRadio_NRF.cpp +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.cpp @@ -169,8 +169,10 @@ void HoymilesRadio_NRF::switchRxCh() _radio->startListening(); } -void HoymilesRadio_NRF::sendEsbPacket(CommandAbstract& cmd) +void HoymilesRadio_NRF::sendEsbPacket(CommandAbstract& cmd, FrequencyManagerAbstract& freq_mgr) { + (void) freq_mgr; + cmd.incrementSendCount(); cmd.setRouterAddress(DtuSerial().u64); diff --git a/lib/Hoymiles/src/HoymilesRadio_NRF.h b/lib/Hoymiles/src/HoymilesRadio_NRF.h index a6777ce52a..9c3b941217 100644 --- a/lib/Hoymiles/src/HoymilesRadio_NRF.h +++ b/lib/Hoymiles/src/HoymilesRadio_NRF.h @@ -30,7 +30,7 @@ class HoymilesRadio_NRF : public HoymilesRadio { void openReadingPipe(); void openWritingPipe(const serial_u serial); - void sendEsbPacket(CommandAbstract& cmd); + void sendEsbPacket(CommandAbstract& cmd, FrequencyManagerAbstract &freq_mgr); std::unique_ptr _spiPtr; std::unique_ptr _radio; diff --git a/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp b/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp index ad89f2d5f7..4223ad2bf2 100644 --- a/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp +++ b/lib/Hoymiles/src/commands/ChannelChangeCommand.cpp @@ -72,8 +72,8 @@ bool ChannelChangeCommand::handleResponse(const fragment_t fragment[], const uin return true; } -uint8_t ChannelChangeCommand::getMaxResendCount() +uint8_t ChannelChangeCommand::getMaxResendCount() const { - // This command will never retrieve an answer. Therefor it's not required to repeat it - return 0; + // This command will never retrieve an answer. Repeat anyway to allow FrequencyManager to send it on a few different frequencies. + return MAX_RESEND_COUNT; } diff --git a/lib/Hoymiles/src/commands/ChannelChangeCommand.h b/lib/Hoymiles/src/commands/ChannelChangeCommand.h index 70b5f64c7d..b8cc721187 100644 --- a/lib/Hoymiles/src/commands/ChannelChangeCommand.h +++ b/lib/Hoymiles/src/commands/ChannelChangeCommand.h @@ -17,5 +17,5 @@ class ChannelChangeCommand : public CommandAbstract { virtual bool handleResponse(const fragment_t fragment[], const uint8_t max_fragment_id); - virtual uint8_t getMaxResendCount(); + virtual uint8_t getMaxResendCount() const; }; diff --git a/lib/Hoymiles/src/frequencymanagers/FrequencyManagerAbstract.cpp b/lib/Hoymiles/src/frequencymanagers/FrequencyManagerAbstract.cpp new file mode 100644 index 0000000000..22794dbba5 --- /dev/null +++ b/lib/Hoymiles/src/frequencymanagers/FrequencyManagerAbstract.cpp @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "FrequencyManagerAbstract.h" +#include "inverters/InverterAbstract.h" +#include "commands/CommandAbstract.h" + +FrequencyManagerAbstract::FrequencyManagerAbstract(InverterAbstract* inv) { + this->_inv = inv; +} \ No newline at end of file diff --git a/lib/Hoymiles/src/frequencymanagers/FrequencyManagerAbstract.h b/lib/Hoymiles/src/frequencymanagers/FrequencyManagerAbstract.h new file mode 100644 index 0000000000..bb6a2ba673 --- /dev/null +++ b/lib/Hoymiles/src/frequencymanagers/FrequencyManagerAbstract.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "Arduino.h" +#include "types.h" + +class InverterAbstract; +class CommandAbstract; + +class FrequencyManagerAbstract { +public: + explicit FrequencyManagerAbstract(InverterAbstract* inv); + virtual ~FrequencyManagerAbstract() {}; + + virtual uint32_t getTXFrequency(CommandAbstract& cmd) = 0; + virtual uint32_t getRXFrequency(CommandAbstract& cmd) = 0; + + virtual void processRXResult(CommandAbstract *cmd, uint8_t verify_fragments_result) = 0; + virtual bool shouldSendChangeChannelCommand() = 0; + virtual void startNextFetch() = 0; +protected: + + InverterAbstract* _inv; +}; diff --git a/lib/Hoymiles/src/frequencymanagers/FrequencyManager_CMT.cpp b/lib/Hoymiles/src/frequencymanagers/FrequencyManager_CMT.cpp new file mode 100644 index 0000000000..bad5729dc0 --- /dev/null +++ b/lib/Hoymiles/src/frequencymanagers/FrequencyManager_CMT.cpp @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023-2024 Thomas Basler and others + */ + +#include "FrequencyManager_CMT.h" +#include "Hoymiles.h" +#include "inverters/InverterAbstract.h" +#include "commands/CommandAbstract.h" + +FrequencyManager_CMT::FrequencyManager_CMT(InverterAbstract* inv):FrequencyManagerAbstract(inv) { +} + +bool isChangeChannelCmd(CommandAbstract &cmd) { + return cmd.getDataPayload()[0] == 0x56; // @todo(tbnobody) Bad hack to identify ChannelChange Command +} + +uint32_t FrequencyManager_CMT::getTXFrequency(CommandAbstract& cmd) { + uint32_t freq = this->_getFrequency(cmd); + if (isChangeChannelCmd(cmd)) { + return Hoymiles.getRadioCmt()->getInvBootFrequency(); + } + return freq; +} + +uint32_t FrequencyManager_CMT::getRXFrequency(CommandAbstract& cmd) { + if (isChangeChannelCmd(cmd)) { + return Hoymiles.getRadioCmt()->getInverterTargetFrequency(); + } + return this->_getFrequency(cmd); +} + +// https://stackoverflow.com/a/14997413 +inline int positive_modulo(int i, int n) { + return (i % n + n) % n; +} + +uint32_t _get_cmt_search_frequency(int failed_fetch_count, int cmd_send_count, uint32_t inverter_target_frequency, uint32_t min_frequency, uint32_t max_frequency, uint32_t legal_min_frequency, uint32_t legal_max_frequency, uint32_t channel_width) { + // cmt_send_count gets incremented in sendEsbPacket just before we are called; we do -1 to undo this + // _getFrequency also grabs the first transmission, setting it to inverterTargetFrequency. we do -1 to undo this as well + int cmd_transmissions_we_did = cmd_send_count-2; // how often this particular command has already been sent on a freq determined by this function + + // failed_fetch_count starts at 1, due to how startsNextFetch counts + int fetches_we_did = failed_fetch_count - 1; + + int offset2 = (fetches_we_did + (cmd_transmissions_we_did/2)) % 20; + int offset3 = offset2 * (cmd_transmissions_we_did%2==0?-1:1); + + uint32_t min_usable_freq = max(min_frequency, legal_min_frequency); + uint32_t max_usable_freq = min(max_frequency, legal_max_frequency); + int min_offset = -((inverter_target_frequency - min_usable_freq)/channel_width); + int max_offset = (max_usable_freq - inverter_target_frequency)/channel_width; + + int final_offset = (positive_modulo(offset3 - min_offset, max_offset + 1 - min_offset)) + min_offset; + + int ret = inverter_target_frequency + (final_offset*channel_width); + // Hoymiles.getMessageOutput()->printf("cmt_search_frequency min_offset=%" PRId32 " max_offset=%" PRId32 "\r\n", min_offset, max_offset); + // Hoymiles.getMessageOutput()->printf("cmt_search_frequency failed_fetch_count=%" PRId32 " cmd_send_count=%" PRId32 " now trying %.3f MHz\r\n", failed_fetch_count, cmd_send_count, ret / 1000000.0); + // Hoymiles.getMessageOutput()->printf("cmt_search_frequency offset2 %" PRId32 " offset3 %" PRId32 " final_offset %" PRId32 " \r\n", offset2, offset3, final_offset); + return ret; +} + +uint32_t FrequencyManager_CMT::_getFrequency(CommandAbstract& cmd) { + HoymilesRadio_CMT *radio = Hoymiles.getRadioCmt(); + uint32_t tgt_freq = radio->getInverterTargetFrequency(); + int cmd_retransmit_count = cmd.getSendCount()-1; + + if(this->_inv->isReachable() || this->_failedFetchCount <= 0) { + // _lastWorkingFrequency was working or is 0. + bool isOnTgtFreq = this->_lastWorkingFrequency == tgt_freq || _lastWorkingFrequency == 0; + if(isOnTgtFreq) { + return tgt_freq; + } else { + // we are still sending ChangeChannelCommands, so we should keep checking if it worked + if(cmd_retransmit_count%2==0) { + return this->_lastWorkingFrequency; + } else { + return tgt_freq; + } + } + } else { + // we are sending ChangeChannelCommands, so we should keep checking if it worked + if(cmd_retransmit_count == 0) { + return tgt_freq; + } + // start searching + return _get_cmt_search_frequency(this->_failedFetchCount, cmd.getSendCount(), tgt_freq, radio->getMinFrequency(), radio->getMaxFrequency(), radio->getLegalMinFrequency(), radio->getLegalMaxFrequency(), radio->getChannelWidth()); + } +} + +void FrequencyManager_CMT::processRXResult(CommandAbstract *cmd, uint8_t verify_fragments_result) { + if(verify_fragments_result == FRAGMENT_OK) { + this->_lastWorkingFrequency = this->getRXFrequency(*cmd); + this->_failedFetchCount = -1; + this->_inv->RadioStats.RxLastFrequency = this->_lastWorkingFrequency; + } + + // FRAGMENT_ALL_MISSING_RESEND + // FRAGMENT_ALL_MISSING_TIMEOUT + // FRAGMENT_HANDLE_ERROR +} + + +bool FrequencyManager_CMT::shouldSendChangeChannelCommand() { + if (!this->_inv->isReachable()) { + return true; + } else { + if (this->_lastWorkingFrequency == 0) { + return false; // 0 means FRAGMENT_OK was never received, i.e. no packet since OpenDTU boot. return false to preserve old startup sequence + } + return this->_lastWorkingFrequency != Hoymiles.getRadioCmt()->getInverterTargetFrequency(); + } +} + +void FrequencyManager_CMT::startNextFetch() { + if(this->_inv->isReachable()) { + this->_failedFetchCount = 0; + } else { + this->_failedFetchCount++; + } +} diff --git a/lib/Hoymiles/src/frequencymanagers/FrequencyManager_CMT.h b/lib/Hoymiles/src/frequencymanagers/FrequencyManager_CMT.h new file mode 100644 index 0000000000..94cb513c64 --- /dev/null +++ b/lib/Hoymiles/src/frequencymanagers/FrequencyManager_CMT.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "FrequencyManagerAbstract.h" + +class FrequencyManager_CMT:public FrequencyManagerAbstract { +public: + explicit FrequencyManager_CMT(InverterAbstract* inv); + + uint32_t getTXFrequency(CommandAbstract& cmd); + uint32_t getRXFrequency(CommandAbstract& cmd); + + void processRXResult(CommandAbstract *cmd, uint8_t verify_fragments_result); + // if ok -> set lastworkingfrequency + // if not -> noop + + bool shouldSendChangeChannelCommand(); + void startNextFetch(); + +private: + uint32_t _lastWorkingFrequency = 0; + uint32_t _getFrequency(CommandAbstract& cmd); + int _failedFetchCount = -1; +}; + +uint32_t _get_cmt_search_frequency(int failed_fetch_count, int cmd_send_count, uint32_t inverter_target_frequency, uint32_t min_frequency, uint32_t max_frequency, uint32_t legal_min_frequency, uint32_t legal_max_frequency, uint32_t channel_width); diff --git a/lib/Hoymiles/src/frequencymanagers/FrequencyManager_NOOP.cpp b/lib/Hoymiles/src/frequencymanagers/FrequencyManager_NOOP.cpp new file mode 100644 index 0000000000..4aad71805b --- /dev/null +++ b/lib/Hoymiles/src/frequencymanagers/FrequencyManager_NOOP.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2023-2024 Thomas Basler and others + */ + + #include "FrequencyManager_NOOP.h" + #include "Hoymiles.h" + #include "inverters/InverterAbstract.h" + #include "commands/CommandAbstract.h" + + FrequencyManager_NOOP::FrequencyManager_NOOP(InverterAbstract* inv):FrequencyManagerAbstract(inv) { + } + + uint32_t FrequencyManager_NOOP::getTXFrequency(CommandAbstract& cmd) { + return Hoymiles.getRadioCmt()->getInverterTargetFrequency(); + } + + uint32_t FrequencyManager_NOOP::getRXFrequency(CommandAbstract& cmd) { + return Hoymiles.getRadioCmt()->getInverterTargetFrequency(); + } + + void FrequencyManager_NOOP::processRXResult(CommandAbstract *cmd, uint8_t verify_fragments_result) { + (void) cmd; + (void) verify_fragments_result; + } + + + bool FrequencyManager_NOOP::shouldSendChangeChannelCommand() { + return false; + } + + void FrequencyManager_NOOP::startNextFetch() { + } \ No newline at end of file diff --git a/lib/Hoymiles/src/frequencymanagers/FrequencyManager_NOOP.h b/lib/Hoymiles/src/frequencymanagers/FrequencyManager_NOOP.h new file mode 100644 index 0000000000..6ee910fcf3 --- /dev/null +++ b/lib/Hoymiles/src/frequencymanagers/FrequencyManager_NOOP.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include "FrequencyManagerAbstract.h" + +class FrequencyManager_NOOP:public FrequencyManagerAbstract { +public: + explicit FrequencyManager_NOOP(InverterAbstract* inv); + + uint32_t getTXFrequency(CommandAbstract& cmd); + uint32_t getRXFrequency(CommandAbstract& cmd); + + void processRXResult(CommandAbstract *cmd, uint8_t verify_fragments_result); + + bool shouldSendChangeChannelCommand(); + void startNextFetch(); +}; \ No newline at end of file diff --git a/lib/Hoymiles/src/inverters/HMS_Abstract.cpp b/lib/Hoymiles/src/inverters/HMS_Abstract.cpp index 4fc64b036b..536cf85197 100644 --- a/lib/Hoymiles/src/inverters/HMS_Abstract.cpp +++ b/lib/Hoymiles/src/inverters/HMS_Abstract.cpp @@ -6,10 +6,12 @@ #include "Hoymiles.h" #include "HoymilesRadio_CMT.h" #include "commands/ChannelChangeCommand.h" +#include "frequencymanagers/FrequencyManager_CMT.h" HMS_Abstract::HMS_Abstract(HoymilesRadio* radio, const uint64_t serial) : HM_Abstract(radio, serial) { + _frequencyManager.reset(new FrequencyManager_CMT(this)); } bool HMS_Abstract::sendChangeChannelRequest() diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.cpp b/lib/Hoymiles/src/inverters/InverterAbstract.cpp index 26a89c1310..5cf59ce547 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.cpp +++ b/lib/Hoymiles/src/inverters/InverterAbstract.cpp @@ -6,6 +6,7 @@ #include "../Hoymiles.h" #include "crc.h" #include +#include "frequencymanagers/FrequencyManager_NOOP.h" InverterAbstract::InverterAbstract(HoymilesRadio* radio, const uint64_t serial) { @@ -152,6 +153,14 @@ HoymilesRadio* InverterAbstract::getRadio() return _radio; } +FrequencyManagerAbstract* InverterAbstract::getFrequencyManager() +{ + if (_frequencyManager.get() == nullptr) { + _frequencyManager.reset(new FrequencyManager_NOOP(this)); + } + return _frequencyManager.get(); +} + AlarmLogParser* InverterAbstract::EventLog() { return _alarmLogParser.get(); diff --git a/lib/Hoymiles/src/inverters/InverterAbstract.h b/lib/Hoymiles/src/inverters/InverterAbstract.h index 10da4d69c6..fe43a7e301 100644 --- a/lib/Hoymiles/src/inverters/InverterAbstract.h +++ b/lib/Hoymiles/src/inverters/InverterAbstract.h @@ -8,6 +8,7 @@ #include "../parser/PowerCommandParser.h" #include "../parser/StatisticsParser.h" #include "../parser/SystemConfigParaParser.h" +#include "frequencymanagers/FrequencyManagerAbstract.h" #include "HoymilesRadio.h" #include "types.h" #include @@ -89,6 +90,9 @@ class InverterAbstract { // RX Fail Corrupt Data uint32_t RxFailCorruptData; + + // RX Frequency of last package. CMT only. + uint32_t RxLastFrequency; } RadioStats = {}; virtual bool sendStatsRequest() = 0; @@ -107,6 +111,7 @@ class InverterAbstract { virtual bool supportsPowerDistributionLogic() = 0; HoymilesRadio* getRadio(); + FrequencyManagerAbstract* getFrequencyManager(); AlarmLogParser* EventLog(); DevInfoParser* DevInfo(); @@ -117,6 +122,7 @@ class InverterAbstract { protected: HoymilesRadio* _radio; + std::unique_ptr _frequencyManager; private: serial_u _serial; diff --git a/src/MqttHandleHass.cpp b/src/MqttHandleHass.cpp index c39485dd8c..fd3f4000f1 100644 --- a/src/MqttHandleHass.cpp +++ b/src/MqttHandleHass.cpp @@ -70,7 +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); + 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); @@ -97,6 +97,7 @@ void MqttHandleHassClass::publishConfig() publishInverterSensor(inv, "RX Fail Receive Nothing", "radio/rx_fail_nothing", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); publishInverterSensor(inv, "RX Fail Receive Partial", "radio/rx_fail_partial", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); publishInverterSensor(inv, "RX Fail Receive Corrupt", "radio/rx_fail_corrupt", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); + publishInverterSensor(inv, "RX Last Frequency", "radio/rx_last_frequency", "MHz", "", DEVICE_CLS_FREQ, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); publishInverterSensor(inv, "TX Re-Request Fragment", "radio/tx_re_request", "", "", DEVICE_CLS_NONE, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); publishInverterSensor(inv, "RSSI", "radio/rssi", "dBm", "", DEVICE_CLS_SIGNAL_STRENGTH, STATE_CLS_NONE, CATEGORY_DIAGNOSTIC); diff --git a/src/MqttHandleInverter.cpp b/src/MqttHandleInverter.cpp index 70a7222d22..d60720a4c6 100644 --- a/src/MqttHandleInverter.cpp +++ b/src/MqttHandleInverter.cpp @@ -50,6 +50,7 @@ void MqttHandleInverterClass::loop() MqttSettings.publish(subtopic + "/radio/rx_fail_nothing", String(inv->RadioStats.RxFailNoAnswer)); MqttSettings.publish(subtopic + "/radio/rx_fail_partial", String(inv->RadioStats.RxFailPartialAnswer)); MqttSettings.publish(subtopic + "/radio/rx_fail_corrupt", String(inv->RadioStats.RxFailCorruptData)); + MqttSettings.publish(subtopic + "/radio/rx_last_frequency", String(inv->RadioStats.RxLastFrequency/1000000.0)); MqttSettings.publish(subtopic + "/radio/rssi", String(inv->getLastRssi())); if (inv->DevInfo()->getLastUpdate() > 0) { diff --git a/src/WebApi_ws_live.cpp b/src/WebApi_ws_live.cpp index c24a77f072..607ff25241 100644 --- a/src/WebApi_ws_live.cpp +++ b/src/WebApi_ws_live.cpp @@ -159,6 +159,7 @@ void WebApiWsLiveClass::generateInverterCommonJsonResponse(JsonObject& root, std root["radio_stats"]["rx_fail_nothing"] = inv->RadioStats.RxFailNoAnswer; root["radio_stats"]["rx_fail_partial"] = inv->RadioStats.RxFailPartialAnswer; root["radio_stats"]["rx_fail_corrupt"] = inv->RadioStats.RxFailCorruptData; + root["radio_stats"]["rx_last_frequency"] = inv->RadioStats.RxLastFrequency; root["radio_stats"]["rssi"] = inv->getLastRssi(); } diff --git a/webapp/src/locales/de.json b/webapp/src/locales/de.json index a6ddae70fb..7562c3ff45 100644 --- a/webapp/src/locales/de.json +++ b/webapp/src/locales/de.json @@ -155,6 +155,8 @@ "RxFailNothing": "Empfang Fehler: Nichts empfangen", "RxFailPartial": "Empfang Fehler: Teilweise empfangen", "RxFailCorrupt": "Empfang Fehler: Beschädigt empfangen", + "RxLastFrequency": "Empfangsfrequenz", + "MHz": "{mhz} MHz", "TxReRequest": "Gesendete Fragment Wiederanforderungen", "StatsReset": "Statistiken zurücksetzen", "StatsResetting": "Zurücksetzen...", diff --git a/webapp/src/locales/en.json b/webapp/src/locales/en.json index ef318ee6ae..d2570343a9 100644 --- a/webapp/src/locales/en.json +++ b/webapp/src/locales/en.json @@ -155,6 +155,8 @@ "RxFailNothing": "RX Fail: Receive Nothing", "RxFailPartial": "RX Fail: Receive Partial", "RxFailCorrupt": "RX Fail: Receive Corrupt", + "RxLastFrequency": "RX frequency of the latest package received", + "MHz": "{mhz} MHz", "TxReRequest": "TX Re-Request Fragment", "StatsReset": "Reset Statistics", "StatsResetting": "Resetting...", diff --git a/webapp/src/locales/fr.json b/webapp/src/locales/fr.json index 25eaf56997..43ed840688 100644 --- a/webapp/src/locales/fr.json +++ b/webapp/src/locales/fr.json @@ -155,6 +155,8 @@ "RxFailNothing": "RX Fail: Receive Nothing", "RxFailPartial": "RX Fail: Receive Partial", "RxFailCorrupt": "RX Fail: Receive Corrupt", + "RxLastFrequency": "RX frequency of the latest package received", + "MHz": "{mhz} MHz", "TxReRequest": "TX Re-Request Fragment", "StatsReset": "Reset Statistics", "StatsResetting": "Resetting...", diff --git a/webapp/src/types/LiveDataStatus.ts b/webapp/src/types/LiveDataStatus.ts index c681f1d911..76314ac0ea 100644 --- a/webapp/src/types/LiveDataStatus.ts +++ b/webapp/src/types/LiveDataStatus.ts @@ -28,6 +28,7 @@ export interface RadioStatistics { rx_fail_nothing: number; rx_fail_partial: number; rx_fail_corrupt: number; + rx_last_frequency: number; rssi: number; } diff --git a/webapp/src/views/HomeView.vue b/webapp/src/views/HomeView.vue index 4b9f848d13..f29a2ae2cc 100644 --- a/webapp/src/views/HomeView.vue +++ b/webapp/src/views/HomeView.vue @@ -301,6 +301,21 @@ }} + + {{ $t('home.RxLastFrequency') }} + + {{ + $t('home.MHz', { + mhz: $n( + inverter.radio_stats.rx_last_frequency / + 1000000.0, + { minimumFractionDigits: 2 } + ), + }) + }} + + + {{ $t('home.TxReRequest') }} {{ $n(inverter.radio_stats.tx_re_request) }} diff --git a/webapp_dist/js/app.js.gz b/webapp_dist/js/app.js.gz index 14dd3c7ff5..5dfedccaa1 100644 Binary files a/webapp_dist/js/app.js.gz and b/webapp_dist/js/app.js.gz differ