Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FrequencyManager] Search the band for unreachable HMS inverters #2573

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lang/el.lang.json
Original file line number Diff line number Diff line change
@@ -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": "Επαναφορά...",
2 changes: 2 additions & 0 deletions lang/es.lang.json
Original file line number Diff line number Diff line change
@@ -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...",
2 changes: 2 additions & 0 deletions lang/it.lang.json
Original file line number Diff line number Diff line change
@@ -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...",
4 changes: 3 additions & 1 deletion lib/Hoymiles/src/Hoymiles.cpp
Original file line number Diff line number Diff line change
@@ -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();
}

16 changes: 9 additions & 7 deletions lib/Hoymiles/src/HoymilesRadio.cpp
Original file line number Diff line number Diff line change
@@ -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();
7 changes: 4 additions & 3 deletions lib/Hoymiles/src/HoymilesRadio.h
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

#include "Arduino.h"
#include "commands/CommandAbstract.h"
#include "frequencymanagers/FrequencyManagerAbstract.h"
#include "queue/CommandQueue.h"
#include "types.h"
#include <TimeoutHelper.h>
@@ -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;
48 changes: 42 additions & 6 deletions lib/Hoymiles/src/HoymilesRadio_CMT.cpp
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
#include "crc.h"
#include <FunctionalInterrupt.h>
#include <frozen/map.h>
#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,26 +274,50 @@ 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();

cmd.setRouterAddress(DtuSerial().u64);

_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());
8 changes: 7 additions & 1 deletion lib/Hoymiles/src/HoymilesRadio_CMT.h
Original file line number Diff line number Diff line change
@@ -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<CMT2300A> _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<fragment_t> _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);
};
4 changes: 3 additions & 1 deletion lib/Hoymiles/src/HoymilesRadio_NRF.cpp
Original file line number Diff line number Diff line change
@@ -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);
2 changes: 1 addition & 1 deletion lib/Hoymiles/src/HoymilesRadio_NRF.h
Original file line number Diff line number Diff line change
@@ -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<SPIClass> _spiPtr;
std::unique_ptr<RF24> _radio;
6 changes: 3 additions & 3 deletions lib/Hoymiles/src/commands/ChannelChangeCommand.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
2 changes: 1 addition & 1 deletion lib/Hoymiles/src/commands/ChannelChangeCommand.h
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
@@ -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;
}
24 changes: 24 additions & 0 deletions lib/Hoymiles/src/frequencymanagers/FrequencyManagerAbstract.h
Original file line number Diff line number Diff line change
@@ -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;
};
121 changes: 121 additions & 0 deletions lib/Hoymiles/src/frequencymanagers/FrequencyManager_CMT.cpp
Original file line number Diff line number Diff line change
@@ -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++;
}
}
26 changes: 26 additions & 0 deletions lib/Hoymiles/src/frequencymanagers/FrequencyManager_CMT.h
Original file line number Diff line number Diff line change
@@ -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);
33 changes: 33 additions & 0 deletions lib/Hoymiles/src/frequencymanagers/FrequencyManager_NOOP.cpp
Original file line number Diff line number Diff line change
@@ -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() {
}
17 changes: 17 additions & 0 deletions lib/Hoymiles/src/frequencymanagers/FrequencyManager_NOOP.h
Original file line number Diff line number Diff line change
@@ -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();
};
2 changes: 2 additions & 0 deletions lib/Hoymiles/src/inverters/HMS_Abstract.cpp
Original file line number Diff line number Diff line change
@@ -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()
9 changes: 9 additions & 0 deletions lib/Hoymiles/src/inverters/InverterAbstract.cpp
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
#include "../Hoymiles.h"
#include "crc.h"
#include <cstring>
#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();
6 changes: 6 additions & 0 deletions lib/Hoymiles/src/inverters/InverterAbstract.h
Original file line number Diff line number Diff line change
@@ -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 <Arduino.h>
@@ -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<FrequencyManagerAbstract> _frequencyManager;

private:
serial_u _serial;
3 changes: 2 additions & 1 deletion src/MqttHandleHass.cpp
Original file line number Diff line number Diff line change
@@ -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);

1 change: 1 addition & 0 deletions src/MqttHandleInverter.cpp
Original file line number Diff line number Diff line change
@@ -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) {
1 change: 1 addition & 0 deletions src/WebApi_ws_live.cpp
Original file line number Diff line number Diff line change
@@ -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();
}

2 changes: 2 additions & 0 deletions webapp/src/locales/de.json
Original file line number Diff line number Diff line change
@@ -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...",
2 changes: 2 additions & 0 deletions webapp/src/locales/en.json
Original file line number Diff line number Diff line change
@@ -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...",
2 changes: 2 additions & 0 deletions webapp/src/locales/fr.json
Original file line number Diff line number Diff line change
@@ -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...",
1 change: 1 addition & 0 deletions webapp/src/types/LiveDataStatus.ts
Original file line number Diff line number Diff line change
@@ -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;
}

15 changes: 15 additions & 0 deletions webapp/src/views/HomeView.vue
Original file line number Diff line number Diff line change
@@ -301,6 +301,21 @@
}}
</td>
</tr>
<tr>
<td>{{ $t('home.RxLastFrequency') }}</td>
<td>
{{
$t('home.MHz', {
mhz: $n(
inverter.radio_stats.rx_last_frequency /
1000000.0,
{ minimumFractionDigits: 2 }
),
})
}}
</td>
<td></td>
</tr>
<tr>
<td>{{ $t('home.TxReRequest') }}</td>
<td>{{ $n(inverter.radio_stats.tx_re_request) }}</td>
Binary file modified webapp_dist/js/app.js.gz
Binary file not shown.