First simple implementation of polling algorithm

This commit is contained in:
Thomas Basler 2022-06-15 19:23:47 +02:00
parent 37dbb343a9
commit 9bfe6a9e63
6 changed files with 492 additions and 19 deletions

View File

@ -1,4 +1,6 @@
#include "Hoymiles.h"
#include "inverters/HM_1CH.h"
#include "inverters/HM_2CH.h"
#include "inverters/HM_4CH.h"
#include <Arduino.h>
#include <Every.h>
@ -15,6 +17,33 @@ void HoymilesClass::init()
void HoymilesClass::loop()
{
_radio->loop();
if (getNumInverters() > 0) {
EVERY_N_SECONDS(_pollInterval)
{
static uint8_t inverterPos = 0;
std::shared_ptr<InverterAbstract> iv = getInverterByPos(inverterPos);
if (iv != nullptr && _radio->isIdle()) {
Serial.print("Fetch inverter: ");
Serial.println(iv->serial());
iv->clearRxFragmentBuffer();
time_t now;
time(&now);
if (now > 0) {
_radio->sendTimePacket(iv, now);
} else {
Serial.println("Cancled. Time not yet synced.");
}
}
if (++inverterPos >= getNumInverters()) {
inverterPos = 0;
}
}
}
}
std::shared_ptr<InverterAbstract> HoymilesClass::addInverter(const char* name, uint64_t serial)
@ -23,6 +52,12 @@ std::shared_ptr<InverterAbstract> HoymilesClass::addInverter(const char* name, u
if (HM_4CH::isValidSerial(serial)) {
i = std::make_shared<HM_4CH>();
}
else if (HM_2CH::isValidSerial(serial)) {
i = std::make_shared<HM_2CH>();
}
else if (HM_1CH::isValidSerial(serial)) {
i = std::make_shared<HM_1CH>();
}
if (i) {
i->setSerial(serial);
@ -78,7 +113,9 @@ std::shared_ptr<InverterAbstract> HoymilesClass::getInverterByFragment(fragment_
void HoymilesClass::removeInverterByPos(uint8_t pos)
{
if (pos < _inverters.size()) {
_inverters.erase(_inverters.begin() + pos);
}
}
size_t HoymilesClass::getNumInverters()

View File

@ -35,9 +35,6 @@ void HoymilesRadio::loop()
switchRxCh(1);
}
// Irgendwie muss man hier die paket crc prüfen und ggf. einen retransmit anfordern
// ggf aber immer nur ein paket analysieren damit die loop schnell bleibt
if (_packetReceived) {
Serial.println(F("Interrupt received"));
while (_radio->available()) {
@ -45,7 +42,6 @@ void HoymilesRadio::loop()
fragment_t* f;
f = _rxBuffer.getFront();
memset(f->fragment, 0xcc, MAX_RF_PAYLOAD_SIZE);
f->rxCh = _rxChLst[_rxChIdx];
f->len = _radio->getDynamicPayloadSize();
if (f->len > MAX_RF_PAYLOAD_SIZE)
f->len = MAX_RF_PAYLOAD_SIZE;
@ -64,23 +60,56 @@ void HoymilesRadio::loop()
if (!_rxBuffer.empty()) {
fragment_t* f = _rxBuffer.getBack();
if (checkFragmentCrc(f)) {
Serial.println("Frame Ok");
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterByFragment(f);
if (nullptr != inv) {
Serial.println("Found Inverter");
// Save packet in inverter rx buffer
dumpBuf("RX ", f->fragment, f->len);
inv->addRxFragment(f->fragment, f->len);
} else {
Serial.println("Inverter Not found!");
Serial.println(F("Inverter Not found!"));
}
} else {
Serial.println("Frame kaputt");
Serial.println(F("Frame kaputt"));
}
// Remove paket from buffer even it was corrupted
_rxBuffer.popBack();
}
}
if (_busyFlag && _rxTimeout.occured()) {
Serial.println("Timeout");
std::shared_ptr<InverterAbstract> inv = Hoymiles.getInverterBySerial(_activeSerial.u64);
if (nullptr != inv) {
uint8_t verifyResult = inv->verifyAllFragments();
if (verifyResult == 255) {
Serial.println("Should Retransmit whole thing");
// todo: irgendwas tun wenn garnichts ankam....
_busyFlag = false;
} else if (verifyResult == 254) {
Serial.println("Retransmit timeout");
_busyFlag = false;
} else if (verifyResult == 253) {
Serial.println("Packet CRC error");
_busyFlag = false;
} else if (verifyResult > 0) {
// Perform Retransmit
Serial.print(F("Request retransmit: "));
Serial.println(verifyResult);
sendRetransmitPacket(verifyResult);
} else {
// Successfull received all packages
_busyFlag = false;
}
}
}
}
void HoymilesRadio::setPALevel(rf24_pa_dbm_e paLevel)
@ -99,6 +128,11 @@ void HoymilesRadio::setDtuSerial(uint64_t serial)
openReadingPipe();
}
bool HoymilesRadio::isIdle()
{
return !_busyFlag;
}
void HoymilesRadio::openReadingPipe()
{
serial_u s;
@ -106,6 +140,13 @@ void HoymilesRadio::openReadingPipe()
_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;
@ -118,6 +159,13 @@ uint8_t HoymilesRadio::getRxNxtChannel()
return _rxChLst[_rxChIdx];
}
uint8_t HoymilesRadio::getTxNxtChannel()
{
if (++_txChIdx >= 1)
_txChIdx = 0;
return _txChLst[_txChIdx];
}
bool HoymilesRadio::switchRxCh(uint8_t addLoop)
{
_rxLoopCnt += addLoop;
@ -144,8 +192,99 @@ serial_u HoymilesRadio::convertSerialToRadioId(serial_u serial)
return radioId;
}
void HoymilesRadio::convertSerialToPacketId(uint8_t buffer[], serial_u serial)
{
buffer[3] = serial.b[0];
buffer[2] = serial.b[1];
buffer[1] = serial.b[2];
buffer[0] = serial.b[3];
}
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(serial_u target, uint8_t mainCmd, uint8_t subCmd, uint8_t payload[], uint8_t len, uint32_t timeout, bool resend)
{
static uint8_t txBuffer[MAX_RF_PAYLOAD_SIZE];
if (!resend) {
memset(txBuffer, 0, MAX_RF_PAYLOAD_SIZE);
txBuffer[0] = mainCmd;
convertSerialToPacketId(&txBuffer[1], target); // 4 byte long
convertSerialToPacketId(&txBuffer[5], DtuSerial()); // 4 byte long
txBuffer[9] = subCmd;
memcpy(&txBuffer[10], payload, len);
txBuffer[10 + len] = crc8(txBuffer, 10 + len);
}
_radio->stopListening();
_radio->setChannel(getTxNxtChannel());
openWritingPipe(target);
_radio->setRetries(3, 15);
dumpBuf(NULL, txBuffer, 10 + len + 1);
_radio->write(txBuffer, 10 + len + 1);
_radio->setRetries(0, 0);
openReadingPipe();
_radio->setChannel(getRxNxtChannel());
_radio->startListening();
_busyFlag = true;
_rxTimeout.set(timeout);
}
void HoymilesRadio::sendTimePacket(std::shared_ptr<InverterAbstract> iv, time_t ts)
{
uint8_t payload[16] = { 0 };
payload[0] = 0x0b;
payload[1] = 0x00;
u32CpyLittleEndian(&payload[2], ts); // sets the 4 following elements {2, 3, 4, 5}
payload[9] = 0x05;
uint16_t crc = crc16(&payload[0], 14);
payload[14] = (crc >> 8) & 0xff;
payload[15] = (crc)&0xff;
serial_u s;
s.u64 = iv->serial();
_activeSerial.u64 = iv->serial();
sendEsbPacket(s, 0x15, 0x80, payload, 16, 60);
}
void HoymilesRadio::sendRetransmitPacket(uint8_t fragment_id)
{
sendEsbPacket(_activeSerial, 0x15, (uint8_t)(0x80 + fragment_id), 0, 0, 60);
}
void HoymilesRadio::sendLastPacketAgain()
{
sendEsbPacket(_activeSerial, 0, 0, 0, 0, 60, true);
}
void HoymilesRadio::u32CpyLittleEndian(uint8_t dest[], uint32_t src)
{
dest[0] = ((src >> 24) & 0xff);
dest[1] = ((src >> 16) & 0xff);
dest[2] = ((src >> 8) & 0xff);
dest[3] = ((src)&0xff);
}
void HoymilesRadio::dumpBuf(const char* info, uint8_t buf[], uint8_t len)
{
if (NULL != info)
Serial.print(String(info));
for (uint8_t i = 0; i < len; i++) {
Serial.print(buf[i], 16);
Serial.print(" ");
}
Serial.println("");
}

View File

@ -1,22 +1,17 @@
#pragma once
#include "CircularBuffer.h"
#include "TimeoutHelper.h"
#include "inverters/InverterAbstract.h"
#include "types.h"
#include <RF24.h>
#include <memory>
#include <nRF24L01.h>
// maximum buffer length of packet received / sent to RF24 module
#define MAX_RF_PAYLOAD_SIZE 32
// number of fragments hold in buffer
#define FRAGMENT_BUFFER_SIZE 30
typedef struct {
uint8_t rxCh;
uint8_t fragment[MAX_RF_PAYLOAD_SIZE];
uint8_t len;
} fragment_t;
class HoymilesRadio {
public:
@ -27,22 +22,40 @@ public:
serial_u DtuSerial();
void setDtuSerial(uint64_t serial);
bool isIdle();
void sendEsbPacket(serial_u target, uint8_t mainCmd, uint8_t subCmd, uint8_t payload[], uint8_t len, uint32_t timeout, bool resend = false);
void sendTimePacket(std::shared_ptr<InverterAbstract> iv, time_t ts);
void sendRetransmitPacket(uint8_t fragment_id);
void sendLastPacketAgain();
private:
void ARDUINO_ISR_ATTR handleIntr();
static serial_u convertSerialToRadioId(serial_u serial);
static void convertSerialToPacketId(uint8_t buffer[], serial_u serial);
uint8_t getRxNxtChannel();
uint8_t getTxNxtChannel();
bool switchRxCh(uint8_t addLoop = 0);
void openReadingPipe();
void openWritingPipe(serial_u serial);
bool checkFragmentCrc(fragment_t* fragment);
void dumpBuf(const char* info, uint8_t buf[], uint8_t len);
void u32CpyLittleEndian(uint8_t dest[], uint32_t src);
std::unique_ptr<RF24> _radio;
uint8_t _rxChLst[4] = { 3, 23, 61, 75 };
uint8_t _rxChIdx;
uint16_t _rxLoopCnt;
uint8_t _txChLst[1] = { 40 };
uint8_t _txChIdx;
volatile bool _packetReceived;
CircularBuffer<fragment_t, FRAGMENT_BUFFER_SIZE> _rxBuffer;
TimeoutHelper _rxTimeout;
serial_u _dtuSerial;
serial_u _activeSerial;
bool _busyFlag = false;
};

View File

@ -1,4 +1,5 @@
#include "InverterAbstract.h"
#include "crc.h"
#include <cstring>
void InverterAbstract::setSerial(uint64_t serial)
@ -14,7 +15,11 @@ uint64_t InverterAbstract::serial()
void InverterAbstract::setName(const char* name)
{
uint8_t len = strlen(name);
strncpy(_name, name, (len > MAX_NAME_LENGTH) ? MAX_NAME_LENGTH : len);
if (len + 1 > MAX_NAME_LENGTH) {
len = MAX_NAME_LENGTH - 1;
}
strncpy(_name, name, len);
_name[len] = '\0';
}
const char* InverterAbstract::name()
@ -24,5 +29,230 @@ const char* InverterAbstract::name()
void InverterAbstract::clearRxFragmentBuffer()
{
memset(_rxFragmentBuffer, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE);
_rxFragmentMaxPacketId = 0;
_rxFragmentLastPacketId = 0;
_rxFragmentRetransmitCnt = 0;
}
void InverterAbstract::addRxFragment(uint8_t fragment[], uint8_t len)
{
uint8_t fragmentCount = fragment[9];
if ((fragmentCount & 0b01111111) < MAX_RF_FRAGMENT_COUNT) {
// Packets with 0x81 will be seen as 1
memcpy(_rxFragmentBuffer[(fragmentCount & 0b01111111) - 1].fragment, &fragment[10], len - 11);
_rxFragmentBuffer[(fragmentCount & 0b01111111) - 1].len = len - 11;
if ((fragmentCount & 0b01111111) > _rxFragmentLastPacketId) {
_rxFragmentLastPacketId = fragmentCount & 0b01111111;
}
}
// 0b10000000 == 0x80
if ((fragmentCount & 0b10000000) == 0b10000000) {
_rxFragmentMaxPacketId = fragmentCount & 0b01111111;
}
}
// Returns Zero on Success or the Fragment ID for retransmit or error code
uint8_t InverterAbstract::verifyAllFragments()
{
// All missing
if (_rxFragmentLastPacketId == 0) {
Serial.println(F("All missing"));
return 255;
}
// Last fragment is missing (thte one with 0x80)
if (_rxFragmentMaxPacketId == 0) {
Serial.println(F("Last missing"));
if (_rxFragmentRetransmitCnt++ < MAX_RETRANSMIT_COUNT) {
return _rxFragmentLastPacketId + 1;
} else {
return 254;
}
}
// Middle fragment is missing
for (uint8_t i = 0; i < _rxFragmentMaxPacketId - 1; i++) {
if (_rxFragmentBuffer[i].len == 0) {
Serial.println(F("Middle missing"));
if (_rxFragmentRetransmitCnt++ < MAX_RETRANSMIT_COUNT) {
return i + 1;
} else {
return 254;
}
}
}
// All fragments are available --> Check CRC
uint16_t crc = 0xffff, crcRcv;
for (uint8_t i = 0; i < _rxFragmentMaxPacketId; i++) {
if (i == _rxFragmentMaxPacketId - 1) {
// Last packet
crc = crc16(_rxFragmentBuffer[i].fragment, _rxFragmentBuffer[i].len - 2, crc);
crcRcv = (_rxFragmentBuffer[i].fragment[_rxFragmentBuffer[i].len - 2] << 8)
| (_rxFragmentBuffer[i].fragment[_rxFragmentBuffer[i].len - 1]);
} else {
crc = crc16(_rxFragmentBuffer[i].fragment, _rxFragmentBuffer[i].len, crc);
}
}
if (crc != crcRcv) {
return 253;
}
// todo: hier muss noch ein check bzgl. packet type usw rein (ist ja nicht alles statistik)
// Move all fragments into target buffer
memset(_payloadStats, 0, MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE);
uint8_t offs = 0;
for (uint8_t i = 0; i < _rxFragmentMaxPacketId; i++) {
memcpy(&_payloadStats[offs], _rxFragmentBuffer[i].fragment, _rxFragmentBuffer[i].len);
offs += (_rxFragmentBuffer[i].len);
}
return 0;
}
uint8_t InverterAbstract::getChannelCount()
{
const byteAssign_t* b = getByteAssignment();
uint8_t cnt = 0;
for (uint8_t pos = 0; pos < sizeof(b) / sizeof(byteAssign_t); pos++) {
if (b[pos].ch > cnt) {
cnt = b[pos].ch;
}
}
return cnt;
}
uint16_t InverterAbstract::getChannelMaxPower(uint8_t channel)
{
// todo;
return 0;
}
uint8_t InverterAbstract::getAssignIdxByChannelField(uint8_t channel, uint8_t fieldId)
{
const byteAssign_t* b = getByteAssignment();
uint8_t pos;
for (pos = 0; pos < sizeof(b) / sizeof(byteAssign_t); pos++) {
if (b[pos].ch == channel && b[pos].fieldId == fieldId) {
return pos;
}
}
return 0xff;
}
float InverterAbstract::getValue(uint8_t channel, uint8_t fieldId)
{
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
if (pos = 0xff) {
return 0;
}
const byteAssign_t* b = getByteAssignment();
uint8_t ptr = b[pos].start;
uint8_t end = ptr + b[pos].num;
uint16_t div = b[pos].div;
if (CMD_CALC != div) {
// Value is a static value
uint32_t val = 0;
do {
val <<= 8;
val |= _payloadStats[ptr];
} while (++ptr != end);
return (float)(val) / (float)(div);
} else {
// Value has to be calculated
return calcFunctions[b[pos].start].func(this, b[pos].num);
}
return 0;
}
bool InverterAbstract::hasValue(uint8_t channel, uint8_t fieldId)
{
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
return pos != 0xff;
}
const char* InverterAbstract::getUnit(uint8_t channel, uint8_t fieldId)
{
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
const byteAssign_t* b = getByteAssignment();
return units[b[pos].unitId];
}
const char* InverterAbstract::getName(uint8_t channel, uint8_t fieldId)
{
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
const byteAssign_t* b = getByteAssignment();
return fields[b[pos].fieldId];
}
static float calcYieldTotalCh0(InverterAbstract* iv, uint8_t arg0)
{
float yield = 0;
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
yield += iv->getValue(i, FLD_YT);
}
return yield;
}
static float calcYieldDayCh0(InverterAbstract* iv, uint8_t arg0)
{
float yield = 0;
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
yield += iv->getValue(i, FLD_YD);
}
return yield;
}
// arg0 = channel of source
static float calcUdcCh(InverterAbstract* iv, uint8_t arg0)
{
return iv->getValue(arg0, FLD_UDC);
}
static float calcPowerDcCh0(InverterAbstract* iv, uint8_t arg0)
{
float dcPower = 0;
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
dcPower += iv->getValue(i, FLD_PDC);
}
return dcPower;
}
// arg0 = channel
static float calcEffiencyCh0(InverterAbstract* iv, uint8_t arg0)
{
float acPower = iv->getValue(CH0, FLD_PAC);
float dcPower = 0;
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
dcPower += iv->getValue(i, FLD_PDC);
}
if (dcPower > 0) {
return acPower / dcPower * 100.0f;
}
return 0.0;
}
// arg0 = channel
static float calcIrradiation(InverterAbstract* iv, uint8_t arg0)
{
if (NULL != iv) {
if (iv->getChannelMaxPower(arg0) > 0)
return iv->getValue(arg0, FLD_PDC) / iv->getChannelMaxPower(arg0) * 100.0f;
}
return 0.0;
}

View File

@ -60,6 +60,35 @@ typedef struct {
uint16_t div; // divisor / calc command
} byteAssign_t;
#define MAX_RF_FRAGMENT_COUNT 5
#define MAX_RETRANSMIT_COUNT 5
class InverterAbstract;
// prototypes
static float calcYieldTotalCh0(InverterAbstract* iv, uint8_t arg0);
static float calcYieldDayCh0(InverterAbstract* iv, uint8_t arg0);
static float calcUdcCh(InverterAbstract* iv, uint8_t arg0);
static float calcPowerDcCh0(InverterAbstract* iv, uint8_t arg0);
static float calcEffiencyCh0(InverterAbstract* iv, uint8_t arg0);
static float calcIrradiation(InverterAbstract* iv, uint8_t arg0);
using func_t = float(InverterAbstract*, uint8_t);
struct calcFunc_t {
uint8_t funcId; // unique id
func_t* func; // function pointer
};
const calcFunc_t calcFunctions[] = {
{ CALC_YT_CH0, &calcYieldTotalCh0 },
{ CALC_YD_CH0, &calcYieldDayCh0 },
{ CALC_UDC_CH, &calcUdcCh },
{ CALC_PDC_CH0, &calcPowerDcCh0 },
{ CALC_EFF_CH0, &calcEffiencyCh0 },
{ CALC_IRR_CH, &calcIrradiation }
};
class InverterAbstract {
public:
void setSerial(uint64_t serial);
@ -68,9 +97,26 @@ public:
const char* name();
virtual String typeName() = 0;
virtual const byteAssign_t* getByteAssignment() = 0;
uint8_t getChannelCount();
uint16_t getChannelMaxPower(uint8_t channel);
void clearRxFragmentBuffer();
void addRxFragment(uint8_t fragment[], uint8_t len);
uint8_t verifyAllFragments();
uint8_t getAssignIdxByChannelField(uint8_t channel, uint8_t fieldId);
float getValue(uint8_t channel, uint8_t fieldId);
bool hasValue(uint8_t channel, uint8_t fieldId);
const char* getUnit(uint8_t channel, uint8_t fieldId);
const char* getName(uint8_t channel, uint8_t fieldId);
private:
serial_u _serial;
char _name[MAX_NAME_LENGTH];
fragment_t _rxFragmentBuffer[MAX_RF_FRAGMENT_COUNT];
uint8_t _rxFragmentMaxPacketId = 0;
uint8_t _rxFragmentLastPacketId = 0;
uint8_t _rxFragmentRetransmitCnt = 0;
uint8_t _payloadStats[MAX_RF_FRAGMENT_COUNT * MAX_RF_PAYLOAD_SIZE];
};

View File

@ -6,3 +6,11 @@ union serial_u {
uint64_t u64;
uint8_t b[8];
};
// maximum buffer length of packet received / sent to RF24 module
#define MAX_RF_PAYLOAD_SIZE 32
typedef struct {
uint8_t fragment[MAX_RF_PAYLOAD_SIZE];
uint8_t len;
} fragment_t;