Add a HoymilesRadio base class
This enables to have multiple radio implementations while the inverter classes just refere to the base class
This commit is contained in:
parent
a7e9aaa862
commit
8404dd57a7
@ -22,20 +22,20 @@ void HoymilesClass::init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pin
|
|||||||
HOY_SEMAPHORE_GIVE(); // release before first use
|
HOY_SEMAPHORE_GIVE(); // release before first use
|
||||||
|
|
||||||
_pollInterval = 0;
|
_pollInterval = 0;
|
||||||
_radio.reset(new HoymilesRadio());
|
_radioNrf.reset(new HoymilesRadio_NRF());
|
||||||
_radio->init(initialisedSpiBus, pinCE, pinIRQ);
|
_radioNrf->init(initialisedSpiBus, pinCE, pinIRQ);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HoymilesClass::loop()
|
void HoymilesClass::loop()
|
||||||
{
|
{
|
||||||
HOY_SEMAPHORE_TAKE();
|
HOY_SEMAPHORE_TAKE();
|
||||||
_radio->loop();
|
_radioNrf->loop();
|
||||||
|
|
||||||
if (getNumInverters() > 0) {
|
if (getNumInverters() > 0) {
|
||||||
if (millis() - _lastPoll > (_pollInterval * 1000)) {
|
if (millis() - _lastPoll > (_pollInterval * 1000)) {
|
||||||
static uint8_t inverterPos = 0;
|
static uint8_t inverterPos = 0;
|
||||||
|
|
||||||
if (_radio->isIdle()) {
|
if (_radioNrf->isIdle()) {
|
||||||
std::shared_ptr<InverterAbstract> iv = getInverterByPos(inverterPos);
|
std::shared_ptr<InverterAbstract> iv = getInverterByPos(inverterPos);
|
||||||
if (iv != nullptr) {
|
if (iv != nullptr) {
|
||||||
_messageOutput->print("Fetch inverter: ");
|
_messageOutput->print("Fetch inverter: ");
|
||||||
@ -89,17 +89,17 @@ std::shared_ptr<InverterAbstract> HoymilesClass::addInverter(const char* name, u
|
|||||||
{
|
{
|
||||||
std::shared_ptr<InverterAbstract> i = nullptr;
|
std::shared_ptr<InverterAbstract> i = nullptr;
|
||||||
if (HMS_4CH::isValidSerial(serial)) {
|
if (HMS_4CH::isValidSerial(serial)) {
|
||||||
i = std::make_shared<HMS_4CH>(_radio.get(), serial);
|
i = std::make_shared<HMS_4CH>(_radioNrf.get(), serial);
|
||||||
} else if (HMS_2CH::isValidSerial(serial)) {
|
} else if (HMS_2CH::isValidSerial(serial)) {
|
||||||
i = std::make_shared<HMS_2CH>(_radio.get(), serial);
|
i = std::make_shared<HMS_2CH>(_radioNrf.get(), serial);
|
||||||
} else if (HMS_1CH::isValidSerial(serial)) {
|
} else if (HMS_1CH::isValidSerial(serial)) {
|
||||||
i = std::make_shared<HMS_1CH>(_radio.get(), serial);
|
i = std::make_shared<HMS_1CH>(_radioNrf.get(), serial);
|
||||||
} else if (HM_4CH::isValidSerial(serial)) {
|
} else if (HM_4CH::isValidSerial(serial)) {
|
||||||
i = std::make_shared<HM_4CH>(_radio.get(), serial);
|
i = std::make_shared<HM_4CH>(_radioNrf.get(), serial);
|
||||||
} else if (HM_2CH::isValidSerial(serial)) {
|
} else if (HM_2CH::isValidSerial(serial)) {
|
||||||
i = std::make_shared<HM_2CH>(_radio.get(), serial);
|
i = std::make_shared<HM_2CH>(_radioNrf.get(), serial);
|
||||||
} else if (HM_1CH::isValidSerial(serial)) {
|
} else if (HM_1CH::isValidSerial(serial)) {
|
||||||
i = std::make_shared<HM_1CH>(_radio.get(), serial);
|
i = std::make_shared<HM_1CH>(_radioNrf.get(), serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i) {
|
if (i) {
|
||||||
@ -171,9 +171,9 @@ size_t HoymilesClass::getNumInverters()
|
|||||||
return _inverters.size();
|
return _inverters.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
HoymilesRadio* HoymilesClass::getRadio()
|
HoymilesRadio_NRF* HoymilesClass::getRadioNrf()
|
||||||
{
|
{
|
||||||
return _radio.get();
|
return _radioNrf.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t HoymilesClass::PollInterval()
|
uint32_t HoymilesClass::PollInterval()
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "HoymilesRadio.h"
|
#include "HoymilesRadio_NRF.h"
|
||||||
#include "inverters/InverterAbstract.h"
|
#include "inverters/InverterAbstract.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <Print.h>
|
#include <Print.h>
|
||||||
@ -27,14 +27,14 @@ public:
|
|||||||
void removeInverterBySerial(uint64_t serial);
|
void removeInverterBySerial(uint64_t serial);
|
||||||
size_t getNumInverters();
|
size_t getNumInverters();
|
||||||
|
|
||||||
HoymilesRadio* getRadio();
|
HoymilesRadio_NRF* getRadioNrf();
|
||||||
|
|
||||||
uint32_t PollInterval();
|
uint32_t PollInterval();
|
||||||
void setPollInterval(uint32_t interval);
|
void setPollInterval(uint32_t interval);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::shared_ptr<InverterAbstract>> _inverters;
|
std::vector<std::shared_ptr<InverterAbstract>> _inverters;
|
||||||
std::unique_ptr<HoymilesRadio> _radio;
|
std::unique_ptr<HoymilesRadio_NRF> _radioNrf;
|
||||||
|
|
||||||
SemaphoreHandle_t _xSemaphore;
|
SemaphoreHandle_t _xSemaphore;
|
||||||
|
|
||||||
|
|||||||
@ -1,158 +1,9 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Thomas Basler and others
|
* Copyright (C) 2023 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "HoymilesRadio.h"
|
#include "HoymilesRadio.h"
|
||||||
#include "Hoymiles.h"
|
#include "Hoymiles.h"
|
||||||
#include "commands/RequestFrameCommand.h"
|
|
||||||
#include "crc.h"
|
|
||||||
#include <Every.h>
|
|
||||||
#include <FunctionalInterrupt.h>
|
|
||||||
|
|
||||||
void HoymilesRadio::init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ)
|
|
||||||
{
|
|
||||||
_dtuSerial.u64 = 0;
|
|
||||||
|
|
||||||
_spiPtr.reset(initialisedSpiBus);
|
|
||||||
_radio.reset(new RF24(pinCE, initialisedSpiBus->pinSS()));
|
|
||||||
|
|
||||||
_radio->begin(_spiPtr.get());
|
|
||||||
|
|
||||||
_radio->setDataRate(RF24_250KBPS);
|
|
||||||
_radio->enableDynamicPayloads();
|
|
||||||
_radio->setCRCLength(RF24_CRC_16);
|
|
||||||
_radio->setAddressWidth(5);
|
|
||||||
_radio->setRetries(0, 0);
|
|
||||||
_radio->maskIRQ(true, true, false); // enable only receiving interrupts
|
|
||||||
if (_radio->isChipConnected()) {
|
|
||||||
Hoymiles.getMessageOutput()->println("Connection successful");
|
|
||||||
} else {
|
|
||||||
Hoymiles.getMessageOutput()->println("Connection error!!");
|
|
||||||
}
|
|
||||||
|
|
||||||
attachInterrupt(digitalPinToInterrupt(pinIRQ), std::bind(&HoymilesRadio::handleIntr, this), FALLING);
|
|
||||||
|
|
||||||
openReadingPipe();
|
|
||||||
_radio->startListening();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HoymilesRadio::loop()
|
|
||||||
{
|
|
||||||
EVERY_N_MILLIS(4)
|
|
||||||
{
|
|
||||||
switchRxCh();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_packetReceived) {
|
|
||||||
Hoymiles.getMessageOutput()->println("Interrupt received");
|
|
||||||
while (_radio->available()) {
|
|
||||||
if (!(_rxBuffer.size() > FRAGMENT_BUFFER_SIZE)) {
|
|
||||||
fragment_t f;
|
|
||||||
memset(f.fragment, 0xcc, MAX_RF_PAYLOAD_SIZE);
|
|
||||||
f.len = _radio->getDynamicPayloadSize();
|
|
||||||
f.channel = _radio->getChannel();
|
|
||||||
if (f.len > MAX_RF_PAYLOAD_SIZE)
|
|
||||||
f.len = MAX_RF_PAYLOAD_SIZE;
|
|
||||||
_radio->read(f.fragment, f.len);
|
|
||||||
_rxBuffer.push(f);
|
|
||||||
} else {
|
|
||||||
Hoymiles.getMessageOutput()->println("Buffer full");
|
|
||||||
_radio->flush_rx();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_packetReceived = false;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Perform package parsing only if no packages are received
|
|
||||||
if (!_rxBuffer.empty()) {
|
|
||||||
fragment_t f = _rxBuffer.back();
|
|
||||||
if (checkFragmentCrc(&f)) {
|
|
||||||
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterByFragment(&f);
|
|
||||||
|
|
||||||
if (nullptr != inv) {
|
|
||||||
// Save packet in inverter rx buffer
|
|
||||||
char buf[30];
|
|
||||||
snprintf(buf, sizeof(buf), "RX Channel: %d --> ", f.channel);
|
|
||||||
dumpBuf(buf, f.fragment, f.len);
|
|
||||||
inv->addRxFragment(f.fragment, f.len);
|
|
||||||
} else {
|
|
||||||
Hoymiles.getMessageOutput()->println("Inverter Not found!");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
Hoymiles.getMessageOutput()->println("Frame kaputt");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove paket from buffer even it was corrupted
|
|
||||||
_rxBuffer.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_busyFlag && _rxTimeout.occured()) {
|
|
||||||
Hoymiles.getMessageOutput()->println("RX Period End");
|
|
||||||
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(_commandQueue.front().get()->getTargetAddress());
|
|
||||||
|
|
||||||
if (nullptr != inv) {
|
|
||||||
CommandAbstract* cmd = _commandQueue.front().get();
|
|
||||||
uint8_t verifyResult = inv->verifyAllFragments(cmd);
|
|
||||||
if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) {
|
|
||||||
Hoymiles.getMessageOutput()->println("Nothing received, resend whole request");
|
|
||||||
sendLastPacketAgain();
|
|
||||||
|
|
||||||
} else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) {
|
|
||||||
Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded");
|
|
||||||
_commandQueue.pop();
|
|
||||||
_busyFlag = false;
|
|
||||||
|
|
||||||
} else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) {
|
|
||||||
Hoymiles.getMessageOutput()->println("Retransmit timeout");
|
|
||||||
_commandQueue.pop();
|
|
||||||
_busyFlag = false;
|
|
||||||
|
|
||||||
} else if (verifyResult == FRAGMENT_HANDLE_ERROR) {
|
|
||||||
Hoymiles.getMessageOutput()->println("Packet handling error");
|
|
||||||
_commandQueue.pop();
|
|
||||||
_busyFlag = false;
|
|
||||||
|
|
||||||
} else if (verifyResult > 0) {
|
|
||||||
// Perform Retransmit
|
|
||||||
Hoymiles.getMessageOutput()->print("Request retransmit: ");
|
|
||||||
Hoymiles.getMessageOutput()->println(verifyResult);
|
|
||||||
sendRetransmitPacket(verifyResult);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Successful received all packages
|
|
||||||
Hoymiles.getMessageOutput()->println("Success");
|
|
||||||
_commandQueue.pop();
|
|
||||||
_busyFlag = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If inverter was not found, assume the command is invalid
|
|
||||||
Hoymiles.getMessageOutput()->println("RX: Invalid inverter found");
|
|
||||||
_commandQueue.pop();
|
|
||||||
_busyFlag = false;
|
|
||||||
}
|
|
||||||
} else if (!_busyFlag) {
|
|
||||||
// Currently in idle mode --> send packet if one is in the queue
|
|
||||||
if (!_commandQueue.empty()) {
|
|
||||||
CommandAbstract* cmd = _commandQueue.front().get();
|
|
||||||
|
|
||||||
auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress());
|
|
||||||
if (nullptr != inv) {
|
|
||||||
inv->clearRxFragmentBuffer();
|
|
||||||
sendEsbPacket(cmd);
|
|
||||||
} else {
|
|
||||||
Hoymiles.getMessageOutput()->println("TX: Invalid inverter found");
|
|
||||||
_commandQueue.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HoymilesRadio::setPALevel(rf24_pa_dbm_e paLevel)
|
|
||||||
{
|
|
||||||
_radio->setPALevel(paLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
serial_u HoymilesRadio::DtuSerial()
|
serial_u HoymilesRadio::DtuSerial()
|
||||||
{
|
{
|
||||||
@ -162,62 +13,6 @@ serial_u HoymilesRadio::DtuSerial()
|
|||||||
void HoymilesRadio::setDtuSerial(uint64_t serial)
|
void HoymilesRadio::setDtuSerial(uint64_t serial)
|
||||||
{
|
{
|
||||||
_dtuSerial.u64 = serial;
|
_dtuSerial.u64 = serial;
|
||||||
openReadingPipe();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HoymilesRadio::isIdle()
|
|
||||||
{
|
|
||||||
return !_busyFlag;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HoymilesRadio::isConnected()
|
|
||||||
{
|
|
||||||
return _radio->isChipConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HoymilesRadio::isPVariant()
|
|
||||||
{
|
|
||||||
return _radio->isPVariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HoymilesRadio::openReadingPipe()
|
|
||||||
{
|
|
||||||
serial_u s;
|
|
||||||
s = convertSerialToRadioId(_dtuSerial);
|
|
||||||
_radio->openReadingPipe(1, s.u64);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HoymilesRadio::openWritingPipe(serial_u serial)
|
|
||||||
{
|
|
||||||
serial_u s;
|
|
||||||
s = convertSerialToRadioId(serial);
|
|
||||||
_radio->openWritingPipe(s.u64);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ARDUINO_ISR_ATTR HoymilesRadio::handleIntr()
|
|
||||||
{
|
|
||||||
_packetReceived = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t HoymilesRadio::getRxNxtChannel()
|
|
||||||
{
|
|
||||||
if (++_rxChIdx >= sizeof(_rxChLst))
|
|
||||||
_rxChIdx = 0;
|
|
||||||
return _rxChLst[_rxChIdx];
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t HoymilesRadio::getTxNxtChannel()
|
|
||||||
{
|
|
||||||
if (++_txChIdx >= sizeof(_txChLst))
|
|
||||||
_txChIdx = 0;
|
|
||||||
return _txChLst[_txChIdx];
|
|
||||||
}
|
|
||||||
|
|
||||||
void HoymilesRadio::switchRxCh()
|
|
||||||
{
|
|
||||||
_radio->stopListening();
|
|
||||||
_radio->setChannel(getRxNxtChannel());
|
|
||||||
_radio->startListening();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
serial_u HoymilesRadio::convertSerialToRadioId(serial_u serial)
|
serial_u HoymilesRadio::convertSerialToRadioId(serial_u serial)
|
||||||
@ -232,59 +27,6 @@ serial_u HoymilesRadio::convertSerialToRadioId(serial_u serial)
|
|||||||
return radioId;
|
return radioId;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HoymilesRadio::checkFragmentCrc(fragment_t* fragment)
|
|
||||||
{
|
|
||||||
uint8_t crc = crc8(fragment->fragment, fragment->len - 1);
|
|
||||||
return (crc == fragment->fragment[fragment->len - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HoymilesRadio::sendEsbPacket(CommandAbstract* cmd)
|
|
||||||
{
|
|
||||||
cmd->incrementSendCount();
|
|
||||||
|
|
||||||
cmd->setRouterAddress(DtuSerial().u64);
|
|
||||||
|
|
||||||
_radio->stopListening();
|
|
||||||
_radio->setChannel(getTxNxtChannel());
|
|
||||||
|
|
||||||
serial_u s;
|
|
||||||
s.u64 = cmd->getTargetAddress();
|
|
||||||
openWritingPipe(s);
|
|
||||||
_radio->setRetries(3, 15);
|
|
||||||
|
|
||||||
Hoymiles.getMessageOutput()->print("TX ");
|
|
||||||
Hoymiles.getMessageOutput()->print(cmd->getCommandName());
|
|
||||||
Hoymiles.getMessageOutput()->print(" Channel: ");
|
|
||||||
Hoymiles.getMessageOutput()->print(_radio->getChannel());
|
|
||||||
Hoymiles.getMessageOutput()->print(" --> ");
|
|
||||||
cmd->dumpDataPayload(Hoymiles.getMessageOutput());
|
|
||||||
_radio->write(cmd->getDataPayload(), cmd->getDataSize());
|
|
||||||
|
|
||||||
_radio->setRetries(0, 0);
|
|
||||||
openReadingPipe();
|
|
||||||
_radio->setChannel(getRxNxtChannel());
|
|
||||||
_radio->startListening();
|
|
||||||
_busyFlag = true;
|
|
||||||
_rxTimeout.set(cmd->getTimeout());
|
|
||||||
}
|
|
||||||
|
|
||||||
void HoymilesRadio::sendRetransmitPacket(uint8_t fragment_id)
|
|
||||||
{
|
|
||||||
CommandAbstract* cmd = _commandQueue.front().get();
|
|
||||||
|
|
||||||
CommandAbstract* requestCmd = cmd->getRequestFrameCommand(fragment_id);
|
|
||||||
|
|
||||||
if (requestCmd != nullptr) {
|
|
||||||
sendEsbPacket(requestCmd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void HoymilesRadio::sendLastPacketAgain()
|
|
||||||
{
|
|
||||||
CommandAbstract* cmd = _commandQueue.front().get();
|
|
||||||
sendEsbPacket(cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
void HoymilesRadio::dumpBuf(const char* info, uint8_t buf[], uint8_t len)
|
void HoymilesRadio::dumpBuf(const char* info, uint8_t buf[], uint8_t len)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@ -1,30 +1,15 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "TimeoutHelper.h"
|
|
||||||
#include "commands/CommandAbstract.h"
|
#include "commands/CommandAbstract.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <RF24.h>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <nRF24L01.h>
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <cmt2300a.h>
|
|
||||||
|
|
||||||
// number of fragments hold in buffer
|
|
||||||
#define FRAGMENT_BUFFER_SIZE 30
|
|
||||||
|
|
||||||
class HoymilesRadio {
|
class HoymilesRadio {
|
||||||
public:
|
public:
|
||||||
void init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ);
|
|
||||||
void loop();
|
|
||||||
void setPALevel(rf24_pa_dbm_e paLevel);
|
|
||||||
|
|
||||||
serial_u DtuSerial();
|
serial_u DtuSerial();
|
||||||
void setDtuSerial(uint64_t serial);
|
virtual void setDtuSerial(uint64_t serial);
|
||||||
|
|
||||||
bool isIdle();
|
|
||||||
bool isConnected();
|
|
||||||
bool isPVariant();
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T* enqueCommand()
|
T* enqueCommand()
|
||||||
@ -33,37 +18,10 @@ public:
|
|||||||
return static_cast<T*>(_commandQueue.back().get());
|
return static_cast<T*>(_commandQueue.back().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
void ARDUINO_ISR_ATTR handleIntr();
|
|
||||||
static serial_u convertSerialToRadioId(serial_u serial);
|
static serial_u convertSerialToRadioId(serial_u serial);
|
||||||
uint8_t getRxNxtChannel();
|
|
||||||
uint8_t getTxNxtChannel();
|
|
||||||
void switchRxCh();
|
|
||||||
void openReadingPipe();
|
|
||||||
void openWritingPipe(serial_u serial);
|
|
||||||
bool checkFragmentCrc(fragment_t* fragment);
|
|
||||||
void dumpBuf(const char* info, uint8_t buf[], uint8_t len);
|
void dumpBuf(const char* info, uint8_t buf[], uint8_t len);
|
||||||
|
|
||||||
void sendEsbPacket(CommandAbstract* cmd);
|
|
||||||
void sendRetransmitPacket(uint8_t fragment_id);
|
|
||||||
void sendLastPacketAgain();
|
|
||||||
|
|
||||||
std::unique_ptr<SPIClass> _spiPtr;
|
|
||||||
std::unique_ptr<RF24> _radio;
|
|
||||||
uint8_t _rxChLst[5] = { 3, 23, 40, 61, 75 };
|
|
||||||
uint8_t _rxChIdx = 0;
|
|
||||||
|
|
||||||
uint8_t _txChLst[5] = { 3, 23, 40, 61, 75 };
|
|
||||||
uint8_t _txChIdx = 0;
|
|
||||||
|
|
||||||
volatile bool _packetReceived = false;
|
|
||||||
|
|
||||||
std::queue<fragment_t> _rxBuffer;
|
|
||||||
TimeoutHelper _rxTimeout;
|
|
||||||
|
|
||||||
serial_u _dtuSerial;
|
serial_u _dtuSerial;
|
||||||
|
|
||||||
bool _busyFlag = false;
|
|
||||||
|
|
||||||
std::queue<std::shared_ptr<CommandAbstract>> _commandQueue;
|
std::queue<std::shared_ptr<CommandAbstract>> _commandQueue;
|
||||||
};
|
};
|
||||||
270
lib/Hoymiles/src/HoymilesRadio_NRF.cpp
Normal file
270
lib/Hoymiles/src/HoymilesRadio_NRF.cpp
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "HoymilesRadio_NRF.h"
|
||||||
|
#include "Hoymiles.h"
|
||||||
|
#include "commands/RequestFrameCommand.h"
|
||||||
|
#include "crc.h"
|
||||||
|
#include <Every.h>
|
||||||
|
#include <FunctionalInterrupt.h>
|
||||||
|
|
||||||
|
void HoymilesRadio_NRF::init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ)
|
||||||
|
{
|
||||||
|
_dtuSerial.u64 = 0;
|
||||||
|
|
||||||
|
_spiPtr.reset(initialisedSpiBus);
|
||||||
|
_radio.reset(new RF24(pinCE, initialisedSpiBus->pinSS()));
|
||||||
|
|
||||||
|
_radio->begin(_spiPtr.get());
|
||||||
|
|
||||||
|
_radio->setDataRate(RF24_250KBPS);
|
||||||
|
_radio->enableDynamicPayloads();
|
||||||
|
_radio->setCRCLength(RF24_CRC_16);
|
||||||
|
_radio->setAddressWidth(5);
|
||||||
|
_radio->setRetries(0, 0);
|
||||||
|
_radio->maskIRQ(true, true, false); // enable only receiving interrupts
|
||||||
|
if (_radio->isChipConnected()) {
|
||||||
|
Hoymiles.getMessageOutput()->println("Connection successful");
|
||||||
|
} else {
|
||||||
|
Hoymiles.getMessageOutput()->println("Connection error!!");
|
||||||
|
}
|
||||||
|
|
||||||
|
attachInterrupt(digitalPinToInterrupt(pinIRQ), std::bind(&HoymilesRadio_NRF::handleIntr, this), FALLING);
|
||||||
|
|
||||||
|
openReadingPipe();
|
||||||
|
_radio->startListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoymilesRadio_NRF::loop()
|
||||||
|
{
|
||||||
|
EVERY_N_MILLIS(4)
|
||||||
|
{
|
||||||
|
switchRxCh();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_packetReceived) {
|
||||||
|
Hoymiles.getMessageOutput()->println("Interrupt received");
|
||||||
|
while (_radio->available()) {
|
||||||
|
if (!(_rxBuffer.size() > FRAGMENT_BUFFER_SIZE)) {
|
||||||
|
fragment_t f;
|
||||||
|
memset(f.fragment, 0xcc, MAX_RF_PAYLOAD_SIZE);
|
||||||
|
f.len = _radio->getDynamicPayloadSize();
|
||||||
|
f.channel = _radio->getChannel();
|
||||||
|
if (f.len > MAX_RF_PAYLOAD_SIZE)
|
||||||
|
f.len = MAX_RF_PAYLOAD_SIZE;
|
||||||
|
_radio->read(f.fragment, f.len);
|
||||||
|
_rxBuffer.push(f);
|
||||||
|
} else {
|
||||||
|
Hoymiles.getMessageOutput()->println("Buffer full");
|
||||||
|
_radio->flush_rx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_packetReceived = false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Perform package parsing only if no packages are received
|
||||||
|
if (!_rxBuffer.empty()) {
|
||||||
|
fragment_t f = _rxBuffer.back();
|
||||||
|
if (checkFragmentCrc(&f)) {
|
||||||
|
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterByFragment(&f);
|
||||||
|
|
||||||
|
if (nullptr != inv) {
|
||||||
|
// Save packet in inverter rx buffer
|
||||||
|
char buf[30];
|
||||||
|
snprintf(buf, sizeof(buf), "RX Channel: %d --> ", f.channel);
|
||||||
|
dumpBuf(buf, f.fragment, f.len);
|
||||||
|
inv->addRxFragment(f.fragment, f.len);
|
||||||
|
} else {
|
||||||
|
Hoymiles.getMessageOutput()->println("Inverter Not found!");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Hoymiles.getMessageOutput()->println("Frame kaputt");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove paket from buffer even it was corrupted
|
||||||
|
_rxBuffer.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_busyFlag && _rxTimeout.occured()) {
|
||||||
|
Hoymiles.getMessageOutput()->println("RX Period End");
|
||||||
|
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(_commandQueue.front().get()->getTargetAddress());
|
||||||
|
|
||||||
|
if (nullptr != inv) {
|
||||||
|
CommandAbstract* cmd = _commandQueue.front().get();
|
||||||
|
uint8_t verifyResult = inv->verifyAllFragments(cmd);
|
||||||
|
if (verifyResult == FRAGMENT_ALL_MISSING_RESEND) {
|
||||||
|
Hoymiles.getMessageOutput()->println("Nothing received, resend whole request");
|
||||||
|
sendLastPacketAgain();
|
||||||
|
|
||||||
|
} else if (verifyResult == FRAGMENT_ALL_MISSING_TIMEOUT) {
|
||||||
|
Hoymiles.getMessageOutput()->println("Nothing received, resend count exeeded");
|
||||||
|
_commandQueue.pop();
|
||||||
|
_busyFlag = false;
|
||||||
|
|
||||||
|
} else if (verifyResult == FRAGMENT_RETRANSMIT_TIMEOUT) {
|
||||||
|
Hoymiles.getMessageOutput()->println("Retransmit timeout");
|
||||||
|
_commandQueue.pop();
|
||||||
|
_busyFlag = false;
|
||||||
|
|
||||||
|
} else if (verifyResult == FRAGMENT_HANDLE_ERROR) {
|
||||||
|
Hoymiles.getMessageOutput()->println("Packet handling error");
|
||||||
|
_commandQueue.pop();
|
||||||
|
_busyFlag = false;
|
||||||
|
|
||||||
|
} else if (verifyResult > 0) {
|
||||||
|
// Perform Retransmit
|
||||||
|
Hoymiles.getMessageOutput()->print("Request retransmit: ");
|
||||||
|
Hoymiles.getMessageOutput()->println(verifyResult);
|
||||||
|
sendRetransmitPacket(verifyResult);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Successful received all packages
|
||||||
|
Hoymiles.getMessageOutput()->println("Success");
|
||||||
|
_commandQueue.pop();
|
||||||
|
_busyFlag = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If inverter was not found, assume the command is invalid
|
||||||
|
Hoymiles.getMessageOutput()->println("RX: Invalid inverter found");
|
||||||
|
_commandQueue.pop();
|
||||||
|
_busyFlag = false;
|
||||||
|
}
|
||||||
|
} else if (!_busyFlag) {
|
||||||
|
// Currently in idle mode --> send packet if one is in the queue
|
||||||
|
if (!_commandQueue.empty()) {
|
||||||
|
CommandAbstract* cmd = _commandQueue.front().get();
|
||||||
|
|
||||||
|
auto inv = Hoymiles.getInverterBySerial(cmd->getTargetAddress());
|
||||||
|
if (nullptr != inv) {
|
||||||
|
inv->clearRxFragmentBuffer();
|
||||||
|
sendEsbPacket(cmd);
|
||||||
|
} else {
|
||||||
|
Hoymiles.getMessageOutput()->println("TX: Invalid inverter found");
|
||||||
|
_commandQueue.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoymilesRadio_NRF::setPALevel(rf24_pa_dbm_e paLevel)
|
||||||
|
{
|
||||||
|
_radio->setPALevel(paLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoymilesRadio_NRF::setDtuSerial(uint64_t serial)
|
||||||
|
{
|
||||||
|
HoymilesRadio::setDtuSerial(serial);
|
||||||
|
openReadingPipe();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HoymilesRadio_NRF::isIdle()
|
||||||
|
{
|
||||||
|
return !_busyFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HoymilesRadio_NRF::isConnected()
|
||||||
|
{
|
||||||
|
return _radio->isChipConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HoymilesRadio_NRF::isPVariant()
|
||||||
|
{
|
||||||
|
return _radio->isPVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoymilesRadio_NRF::openReadingPipe()
|
||||||
|
{
|
||||||
|
serial_u s;
|
||||||
|
s = convertSerialToRadioId(_dtuSerial);
|
||||||
|
_radio->openReadingPipe(1, s.u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoymilesRadio_NRF::openWritingPipe(serial_u serial)
|
||||||
|
{
|
||||||
|
serial_u s;
|
||||||
|
s = convertSerialToRadioId(serial);
|
||||||
|
_radio->openWritingPipe(s.u64);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARDUINO_ISR_ATTR HoymilesRadio_NRF::handleIntr()
|
||||||
|
{
|
||||||
|
_packetReceived = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t HoymilesRadio_NRF::getRxNxtChannel()
|
||||||
|
{
|
||||||
|
if (++_rxChIdx >= sizeof(_rxChLst))
|
||||||
|
_rxChIdx = 0;
|
||||||
|
return _rxChLst[_rxChIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t HoymilesRadio_NRF::getTxNxtChannel()
|
||||||
|
{
|
||||||
|
if (++_txChIdx >= sizeof(_txChLst))
|
||||||
|
_txChIdx = 0;
|
||||||
|
return _txChLst[_txChIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoymilesRadio_NRF::switchRxCh()
|
||||||
|
{
|
||||||
|
_radio->stopListening();
|
||||||
|
_radio->setChannel(getRxNxtChannel());
|
||||||
|
_radio->startListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HoymilesRadio_NRF::checkFragmentCrc(fragment_t* fragment)
|
||||||
|
{
|
||||||
|
uint8_t crc = crc8(fragment->fragment, fragment->len - 1);
|
||||||
|
return (crc == fragment->fragment[fragment->len - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoymilesRadio_NRF::sendEsbPacket(CommandAbstract* cmd)
|
||||||
|
{
|
||||||
|
cmd->incrementSendCount();
|
||||||
|
|
||||||
|
cmd->setRouterAddress(DtuSerial().u64);
|
||||||
|
|
||||||
|
_radio->stopListening();
|
||||||
|
_radio->setChannel(getTxNxtChannel());
|
||||||
|
|
||||||
|
serial_u s;
|
||||||
|
s.u64 = cmd->getTargetAddress();
|
||||||
|
openWritingPipe(s);
|
||||||
|
_radio->setRetries(3, 15);
|
||||||
|
|
||||||
|
Hoymiles.getMessageOutput()->print("TX ");
|
||||||
|
Hoymiles.getMessageOutput()->print(cmd->getCommandName());
|
||||||
|
Hoymiles.getMessageOutput()->print(" Channel: ");
|
||||||
|
Hoymiles.getMessageOutput()->print(_radio->getChannel());
|
||||||
|
Hoymiles.getMessageOutput()->print(" --> ");
|
||||||
|
cmd->dumpDataPayload(Hoymiles.getMessageOutput());
|
||||||
|
_radio->write(cmd->getDataPayload(), cmd->getDataSize());
|
||||||
|
|
||||||
|
_radio->setRetries(0, 0);
|
||||||
|
openReadingPipe();
|
||||||
|
_radio->setChannel(getRxNxtChannel());
|
||||||
|
_radio->startListening();
|
||||||
|
_busyFlag = true;
|
||||||
|
_rxTimeout.set(cmd->getTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoymilesRadio_NRF::sendRetransmitPacket(uint8_t fragment_id)
|
||||||
|
{
|
||||||
|
CommandAbstract* cmd = _commandQueue.front().get();
|
||||||
|
|
||||||
|
CommandAbstract* requestCmd = cmd->getRequestFrameCommand(fragment_id);
|
||||||
|
|
||||||
|
if (requestCmd != nullptr) {
|
||||||
|
sendEsbPacket(requestCmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HoymilesRadio_NRF::sendLastPacketAgain()
|
||||||
|
{
|
||||||
|
CommandAbstract* cmd = _commandQueue.front().get();
|
||||||
|
sendEsbPacket(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
54
lib/Hoymiles/src/HoymilesRadio_NRF.h
Normal file
54
lib/Hoymiles/src/HoymilesRadio_NRF.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "HoymilesRadio.h"
|
||||||
|
#include "TimeoutHelper.h"
|
||||||
|
#include "commands/CommandAbstract.h"
|
||||||
|
#include <RF24.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <nRF24L01.h>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
// number of fragments hold in buffer
|
||||||
|
#define FRAGMENT_BUFFER_SIZE 30
|
||||||
|
|
||||||
|
class HoymilesRadio_NRF : public HoymilesRadio {
|
||||||
|
public:
|
||||||
|
void init(SPIClass* initialisedSpiBus, uint8_t pinCE, uint8_t pinIRQ);
|
||||||
|
void loop();
|
||||||
|
void setPALevel(rf24_pa_dbm_e paLevel);
|
||||||
|
|
||||||
|
virtual void setDtuSerial(uint64_t serial);
|
||||||
|
|
||||||
|
bool isIdle();
|
||||||
|
bool isConnected();
|
||||||
|
bool isPVariant();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ARDUINO_ISR_ATTR handleIntr();
|
||||||
|
uint8_t getRxNxtChannel();
|
||||||
|
uint8_t getTxNxtChannel();
|
||||||
|
void switchRxCh();
|
||||||
|
void openReadingPipe();
|
||||||
|
void openWritingPipe(serial_u serial);
|
||||||
|
bool checkFragmentCrc(fragment_t* fragment);
|
||||||
|
|
||||||
|
void sendEsbPacket(CommandAbstract* cmd);
|
||||||
|
void sendRetransmitPacket(uint8_t fragment_id);
|
||||||
|
void sendLastPacketAgain();
|
||||||
|
|
||||||
|
std::unique_ptr<SPIClass> _spiPtr;
|
||||||
|
std::unique_ptr<RF24> _radio;
|
||||||
|
uint8_t _rxChLst[5] = { 3, 23, 40, 61, 75 };
|
||||||
|
uint8_t _rxChIdx = 0;
|
||||||
|
|
||||||
|
uint8_t _txChLst[5] = { 3, 23, 40, 61, 75 };
|
||||||
|
uint8_t _txChIdx = 0;
|
||||||
|
|
||||||
|
volatile bool _packetReceived = false;
|
||||||
|
|
||||||
|
std::queue<fragment_t> _rxBuffer;
|
||||||
|
TimeoutHelper _rxTimeout;
|
||||||
|
|
||||||
|
bool _busyFlag = false;
|
||||||
|
};
|
||||||
@ -29,10 +29,10 @@ void InverterSettingsClass::init()
|
|||||||
Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq);
|
Hoymiles.init(spiClass, pin.nrf24_en, pin.nrf24_irq);
|
||||||
|
|
||||||
MessageOutput.println(" Setting radio PA level... ");
|
MessageOutput.println(" Setting radio PA level... ");
|
||||||
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
|
Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
|
||||||
|
|
||||||
MessageOutput.println(" Setting DTU serial... ");
|
MessageOutput.println(" Setting DTU serial... ");
|
||||||
Hoymiles.getRadio()->setDtuSerial(config.Dtu_Serial);
|
Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu_Serial);
|
||||||
|
|
||||||
MessageOutput.println(" Setting poll interval... ");
|
MessageOutput.println(" Setting poll interval... ");
|
||||||
Hoymiles.setPollInterval(config.Dtu_PollInterval);
|
Hoymiles.setPollInterval(config.Dtu_PollInterval);
|
||||||
|
|||||||
@ -16,7 +16,7 @@ void MqttHandleDtuClass::init()
|
|||||||
|
|
||||||
void MqttHandleDtuClass::loop()
|
void MqttHandleDtuClass::loop()
|
||||||
{
|
{
|
||||||
if (!MqttSettings.getConnected() || !Hoymiles.getRadio()->isIdle()) {
|
if (!MqttSettings.getConnected() || !Hoymiles.getRadioNrf()->isIdle()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,7 @@ void MqttHandleHassClass::publishConfig()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!MqttSettings.getConnected() && Hoymiles.getRadio()->isIdle()) {
|
if (!MqttSettings.getConnected() && Hoymiles.getRadioNrf()->isIdle()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,7 @@ void MqttHandleInverterClass::init()
|
|||||||
|
|
||||||
void MqttHandleInverterClass::loop()
|
void MqttHandleInverterClass::loop()
|
||||||
{
|
{
|
||||||
if (!MqttSettings.getConnected() || !Hoymiles.getRadio()->isIdle()) {
|
if (!MqttSettings.getConnected() || !Hoymiles.getRadioNrf()->isIdle()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -132,7 +132,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
|
|
||||||
Hoymiles.getRadio()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
|
Hoymiles.getRadioNrf()->setPALevel((rf24_pa_dbm_e)config.Dtu_PaLevel);
|
||||||
Hoymiles.getRadio()->setDtuSerial(config.Dtu_Serial);
|
Hoymiles.getRadioNrf()->setDtuSerial(config.Dtu_Serial);
|
||||||
Hoymiles.setPollInterval(config.Dtu_PollInterval);
|
Hoymiles.setPollInterval(config.Dtu_PollInterval);
|
||||||
}
|
}
|
||||||
@ -69,8 +69,8 @@ void WebApiSysstatusClass::onSystemStatus(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
root["uptime"] = esp_timer_get_time() / 1000000;
|
root["uptime"] = esp_timer_get_time() / 1000000;
|
||||||
|
|
||||||
root["radio_connected"] = Hoymiles.getRadio()->isConnected();
|
root["radio_connected"] = Hoymiles.getRadioNrf()->isConnected();
|
||||||
root["radio_pvariant"] = Hoymiles.getRadio()->isPVariant();
|
root["radio_pvariant"] = Hoymiles.getRadioNrf()->isPVariant();
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
request->send(response);
|
request->send(response);
|
||||||
|
|||||||
@ -175,7 +175,7 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
|
|||||||
JsonObject hintObj = root.createNestedObject("hints");
|
JsonObject hintObj = root.createNestedObject("hints");
|
||||||
struct tm timeinfo;
|
struct tm timeinfo;
|
||||||
hintObj["time_sync"] = !getLocalTime(&timeinfo, 5);
|
hintObj["time_sync"] = !getLocalTime(&timeinfo, 5);
|
||||||
hintObj["radio_problem"] = (!Hoymiles.getRadio()->isConnected() || !Hoymiles.getRadio()->isPVariant());
|
hintObj["radio_problem"] = (!Hoymiles.getRadioNrf()->isConnected() || !Hoymiles.getRadioNrf()->isPVariant());
|
||||||
if (!strcmp(Configuration.get().Security_Password, ACCESS_POINT_PASSWORD)) {
|
if (!strcmp(Configuration.get().Security_Password, ACCESS_POINT_PASSWORD)) {
|
||||||
hintObj["default_password"] = true;
|
hintObj["default_password"] = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user