Browse Source

Merge branch 'development03' into Zero-Export

pull/1155/head
DanielR92 2 years ago
parent
commit
a6e6ddc3ae
  1. 7
      src/CHANGES.md
  2. 85
      src/app.cpp
  3. 17
      src/app.h
  4. 2
      src/defines.h
  5. 21
      src/hm/hmInverter.h
  6. 63
      src/hm/hmPayload.h
  7. 123
      src/hm/hmRadio.h
  8. 104
      src/hm/hmSystem.h
  9. 65
      src/hm/miPayload.h
  10. 102
      src/hm/radio.h
  11. 4
      src/hms/cmt2300a.h
  12. 9
      src/hms/esp32_3wSpi.h
  13. 415
      src/hms/hmsPayload.h
  14. 105
      src/hms/hmsRadio.h
  15. 2
      src/plugins/Display/Display.h
  16. 12
      src/utils/dbg.h
  17. 8
      src/utils/helper.h
  18. 31
      src/web/RestApi.h
  19. 9
      src/web/html/api.js
  20. 22
      src/web/html/system.html
  21. 59
      src/web/html/visualization.html
  22. 2
      src/web/web.h

7
src/CHANGES.md

@ -1,5 +1,12 @@
# Development Changes
## 0.7.61 - 2023-10-01
* merged `hmPayload` and `hmsPayload` into single class
* merged generic radio functions into new parent class `radio.h`
* moved radio statistics into the inverter - each inverter has now seperate statistics which can be accessed by click on the footer in `/live`
* fix compiler warnings #1191
* fix ePaper logo during night time #1151
## 0.7.60 - 2023-09-27
* fixed typos in changelog #1172
* fixed MqTT manual clientId storage #1174

85
src/app.cpp

@ -34,12 +34,12 @@ void app::setup() {
DBGPRINTLN(F("false"));
if(mConfig->nrf.enabled) {
mNrfRadio.setup(&mNrfStat, mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs, mConfig->nrf.pinSclk, mConfig->nrf.pinMosi, mConfig->nrf.pinMiso);
mNrfRadio.setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs, mConfig->nrf.pinSclk, mConfig->nrf.pinMosi, mConfig->nrf.pinMiso);
mNrfRadio.enableDebug();
}
#if defined(ESP32)
if(mConfig->cmt.enabled) {
mCmtRadio.setup(&mCmtStat, mConfig->cmt.pinSclk, mConfig->cmt.pinSdio, mConfig->cmt.pinCsb, mConfig->cmt.pinFcsb, false);
mCmtRadio.setup(mConfig->cmt.pinSclk, mConfig->cmt.pinSdio, mConfig->cmt.pinCsb, mConfig->cmt.pinFcsb, false);
mCmtRadio.enableDebug();
}
#endif
@ -59,24 +59,27 @@ void app::setup() {
#endif
#endif /* defined(ETHERNET) */
mSys.setup(&mTimestamp);
mSys.addInverters(&mConfig->inst);
if (mConfig->nrf.enabled) {
mPayload.setup(this, &mSys, &mNrfRadio, &mNrfStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
mPayload.enableSerialDebug(mConfig->serial.debug);
mPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2));
mSys.setup(&mTimestamp, &mConfig->inst);
for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
mSys.addInverter(i, [this](Inverter<> *iv) {
if((IV_MI == iv->ivGen) || (IV_HM == iv->ivGen))
iv->radio = &mNrfRadio;
#if defined(ESP32)
else if((IV_HMS == iv->ivGen) || (IV_HMT == iv->ivGen))
iv->radio = &mCmtRadio;
#endif
});
}
mMiPayload.setup(this, &mSys, &mNrfRadio, &mNrfStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
mPayload.setup(this, &mSys, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
mPayload.enableSerialDebug(mConfig->serial.debug);
mPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2));
if (mConfig->nrf.enabled) {
mMiPayload.setup(this, &mSys, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
mMiPayload.enableSerialDebug(mConfig->serial.debug);
mMiPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2));
}
#if defined(ESP32)
mHmsPayload.setup(this, &mSys, &mCmtRadio, &mCmtStat, 5, &mTimestamp);
mHmsPayload.enableSerialDebug(mConfig->serial.debug);
mHmsPayload.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2));
#endif
if(mConfig->nrf.enabled) {
if (!mNrfRadio.isChipConnected())
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
@ -90,9 +93,6 @@ void app::setup() {
mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1));
mPayload.addAlarmListener([this](Inverter<> *iv) { mMqtt.alarmEvent(iv); });
mMiPayload.addAlarmListener([this](Inverter<> *iv) { mMqtt.alarmEvent(iv); });
#if defined(ESP32)
mHmsPayload.addAlarmListener([this](Inverter<> *iv) { mMqtt.alarmEvent(iv); });
#endif
}
#endif
setupLed();
@ -123,11 +123,11 @@ void app::setup() {
//-----------------------------------------------------------------------------
void app::loop(void) {
ah::Scheduler::loop();
bool processPayload = false;
if (mNrfRadio.loop() && mConfig->nrf.enabled) {
while (!mNrfRadio.mBufCtrl.empty()) {
packet_t *p = &mNrfRadio.mBufCtrl.front();
if (mConfig->serial.debug) {
DPRINT(DBG_INFO, F("RX "));
DBGPRINT(String(p->len));
@ -142,47 +142,48 @@ void app::loop(void) {
Inverter<> *iv = mSys.findInverter(&p->packet[1]);
if (NULL != iv) {
if (IV_HM == iv->ivGen)
mPayload.add(iv, p);
else
if (IV_MI == iv->ivGen)
mMiPayload.add(iv, p);
else
mPayload.add(iv, p);
}
mNrfRadio.mBufCtrl.pop();
yield();
}
mPayload.process(true);
processPayload = true;
mMiPayload.process(true);
}
#if defined(ESP32)
if (mCmtRadio.loop() && mConfig->cmt.enabled) {
while (!mCmtRadio.mBufCtrl.empty()) {
hmsPacket_t *p = &mCmtRadio.mBufCtrl.front();
packet_t *p = &mCmtRadio.mBufCtrl.front();
if (mConfig->serial.debug) {
DPRINT(DBG_INFO, F("RX "));
DBGPRINT(String(p->data[0]));
DBGPRINT(String(p->len));
DBGPRINT(F(", "));
DBGPRINT(String(p->rssi));
DBGPRINT(F("dBm | "));
ah::dumpBuf(&p->data[1], p->data[0]);
ah::dumpBuf(p->packet, p->len);
}
mCmtStat.frmCnt++;
Inverter<> *iv = mSys.findInverter(&p->data[2]);
Inverter<> *iv = mSys.findInverter(&p->packet[1]);
if(NULL != iv) {
if((iv->ivGen == IV_HMS) || (iv->ivGen == IV_HMT))
mHmsPayload.add(iv, p);
mPayload.add(iv, p);
}
mCmtRadio.mBufCtrl.pop();
yield();
}
mHmsPayload.process(false); //true
processPayload = true;
}
#endif
if(processPayload)
mPayload.process(true);
mPayload.loop();
mMiPayload.loop();
#if defined(ESP32)
mHmsPayload.loop();
#endif
if (mMqttEnabled && mNetworkConnected)
mMqtt.loop();
@ -195,10 +196,6 @@ void app::onNetwork(bool gotIp) {
ah::Scheduler::resetTicker();
regularTickers(); //reinstall regular tickers
every(std::bind(&app::tickSend, this), mConfig->nrf.sendInterval, "tSend");
#if defined(ESP32)
if(mConfig->cmt.enabled)
everySec(std::bind(&CmtRadioType::tickSecond, &mCmtRadio), "tsCmt");
#endif
mMqttReconnect = true;
mSunrise = 0; // needs to be set to 0, to reinstall sunrise and ivComm tickers!
//once(std::bind(&app::tickNtpUpdate, this), 2, "ntp2");
@ -439,18 +436,10 @@ void app::tickSend(void) {
if (NULL != iv) {
if (iv->config->enabled) {
if(mConfig->nrf.enabled) {
if (iv->ivGen == IV_HM)
mPayload.ivSend(iv);
else if(iv->ivGen == IV_MI)
mMiPayload.ivSend(iv);
}
#if defined(ESP32)
if(mConfig->cmt.enabled) {
if((iv->ivGen == IV_HMS) || (iv->ivGen == IV_HMT))
mHmsPayload.ivSend(iv);
}
#endif
if((iv->ivGen == IV_MI) && mConfig->nrf.enabled)
mMiPayload.ivSend(iv);
else
mPayload.ivSend(iv);
}
#if defined(ESP32)

17
src/app.h

@ -16,7 +16,6 @@
#include "hm/hmSystem.h"
#include "hm/hmRadio.h"
#include "hms/hmsRadio.h"
#include "hms/hmsPayload.h"
#include "hm/hmPayload.h"
#include "hm/miPayload.h"
#include "publisher/pubMqtt.h"
@ -42,11 +41,10 @@
#define ACOS(x) (degrees(acos(x)))
typedef HmSystem<MAX_NUM_INVERTERS> HmSystemType;
typedef HmPayload<HmSystemType, HmRadio<>> PayloadType;
typedef MiPayload<HmSystemType, HmRadio<>> MiPayloadType;
typedef HmPayload<HmSystemType> PayloadType;
typedef MiPayload<HmSystemType> MiPayloadType;
#ifdef ESP32
typedef CmtRadio<esp32_3wSpi> CmtRadioType;
typedef HmsPayload<HmSystemType, CmtRadioType> HmsPayloadType;
#endif
typedef Web<HmSystemType> WebType;
typedef RestApi<HmSystemType> RestApiType;
@ -184,14 +182,10 @@ class app : public IApp, public ah::Scheduler {
void ivSendHighPrio(Inverter<> *iv) {
if(mIVCommunicationOn) { // only send commands if communication is enabled
if (iv->ivGen == IV_HM)
mPayload.ivSendHighPrio(iv);
else if (iv->ivGen == IV_MI)
if (iv->ivGen == IV_MI)
mMiPayload.ivSendHighPrio(iv);
#if defined(ESP32)
else if((iv->ivGen == IV_HMS) || (iv->ivGen == IV_HMT))
mHmsPayload.ivSendHighPrio(iv);
#endif
else
mPayload.ivSendHighPrio(iv);
}
}
@ -342,7 +336,6 @@ class app : public IApp, public ah::Scheduler {
#endif
#ifdef ESP32
CmtRadioType mCmtRadio;
HmsPayloadType mHmsPayload;
#endif
char mVersion[12];

2
src/defines.h

@ -13,7 +13,7 @@
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 7
#define VERSION_PATCH 60
#define VERSION_PATCH 61
//-------------------------------------
typedef struct {

21
src/hm/hmInverter.h

@ -17,6 +17,7 @@
#include <queue>
#include "../config/settings.h"
#include "radio.h"
/**
* For values which are of interest and not transmitted by the inverter can be
* calculated automatically.
@ -24,10 +25,6 @@
* automatically. Their result does not differ from original read values.
*/
// forward declaration of class
template <class REC_TYP=float>
class Inverter;
// prototypes
template<class T=float>
@ -88,7 +85,7 @@ class CommandAbstract {
};
virtual ~CommandAbstract() {};
const uint8_t getCmd() {
uint8_t getCmd() const {
return _Cmd;
}
@ -142,7 +139,7 @@ class Inverter {
uint8_t channels; // number of PV channels (1-4)
record_t<REC_TYP> recordMeas; // structure for measured values
record_t<REC_TYP> recordInfo; // structure for info values
record_t<REC_TYP> recordHwInfo; // structure for simple (hardware) info values
record_t<REC_TYP> recordHwInfo; // structure for simple (hardware) info values
record_t<REC_TYP> recordConfig; // structure for system config values
record_t<REC_TYP> recordAlarm; // structure for alarm values
bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
@ -152,7 +149,9 @@ class Inverter {
uint8_t alarmNxtWrPos; // indicates the position in array (rolling buffer)
uint16_t alarmCnt; // counts the total number of occurred alarms
uint16_t alarmLastId; // lastId which was received
int8_t rssi; // HMS and HMT inverters only
int8_t rssi; // RSSI
Radio *radio; // pointer to associated radio class
statistics_t radioStatistics; // information about transmitted, failed, ... packets
static uint32_t *timestamp; // system timestamp
@ -167,7 +166,6 @@ class Inverter {
mDevControlRequest = false;
devControlCmd = InitDataState;
initialized = false;
//lastAlarmMsg = "nothing";
alarmMesIndex = 0;
isConnected = false;
status = InverterStatus::OFF;
@ -362,9 +360,6 @@ class Inverter {
}
else if (rec->assign == AlarmDataAssignment) {
DPRINTLN(DBG_DEBUG, "add alarm");
//if (getPosByChFld(0, FLD_LAST_ALARM_CODE, rec) == pos){
// lastAlarmMsg = getAlarmStr(rec->record[pos]);
//}
}
else
DPRINTLN(DBG_WARN, F("add with unknown assignment"));
@ -497,8 +492,8 @@ class Inverter {
record_t<> *getRecordStruct(uint8_t cmd) {
switch (cmd) {
case RealTimeRunData_Debug: return &recordMeas; // 11 = 0x0b
case InverterDevInform_Simple: return &recordHwInfo; // 0 = 0x00
case InverterDevInform_All: return &recordInfo; // 1 = 0x01
case InverterDevInform_Simple: return &recordHwInfo; // 0 = 0x00
case InverterDevInform_All: return &recordInfo; // 1 = 0x01
case SystemConfigPara: return &recordConfig; // 5 = 0x05
case AlarmData: return &recordAlarm; // 17 = 0x11
default: break;

63
src/hm/hmPayload.h

@ -1,6 +1,6 @@
//-----------------------------------------------------------------------------
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#ifndef __HM_PAYLOAD_H__
@ -10,12 +10,16 @@
#include "../utils/crc.h"
#include "../config/config.h"
#include "hmRadio.h"
#if defined(ESP32)
#include "../hms/cmt2300a.h"
#endif
#include <Arduino.h>
#define HMS_TIMEOUT_SEC 30
typedef struct {
uint8_t txCmd;
uint8_t txId;
uint8_t invId;
uint32_t ts;
uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE];
int8_t rssi[MAX_PAYLOAD_ENTRIES];
@ -34,20 +38,19 @@ typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType;
typedef std::function<void(Inverter<> *)> alarmListenerType;
template<class HMSYSTEM, class HMRADIO>
template<class HMSYSTEM>
class HmPayload {
public:
HmPayload() {}
void setup(IApp *app, HMSYSTEM *sys, HMRADIO *radio, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) {
void setup(IApp *app, HMSYSTEM *sys, uint8_t maxRetransmits, uint32_t *timestamp) {
mApp = app;
mSys = sys;
mRadio = radio;
mStat = stat;
mMaxRetrans = maxRetransmits;
mTimestamp = timestamp;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
reset(i);
mIvCmd56Cnt[i] = 0;
}
mSerialDebug = false;
mHighPrioIv = NULL;
@ -69,7 +72,7 @@ class HmPayload {
void loop() {
if (NULL != mHighPrioIv) {
ivSend(mHighPrioIv, true);
ivSend(mHighPrioIv, true); // for e.g. devcontrol commands
mHighPrioIv = NULL;
}
}
@ -111,11 +114,11 @@ class HmPayload {
if (mSerialDebug)
DPRINT_IVID(DBG_INFO, iv->id);
if (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId) {
mStat->rxFailNoAnser++; // got nothing
iv->radioStatistics.rxFailNoAnser++; // got nothing
if (mSerialDebug)
DBGPRINTLN(F("enqueued cmd failed/timeout"));
} else {
mStat->rxFail++; // got fragments but not complete response
iv->radioStatistics.rxFail++; // got fragments but not complete response
if (mSerialDebug) {
DBGPRINT(F("no complete Payload received! (retransmits: "));
DBGPRINT(String(mPayload[iv->id].retransmits));
@ -141,23 +144,34 @@ class HmPayload {
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("Devcontrol request 0x"));
DBGPRINT(String(iv->devControlCmd, HEX));
DHEX(iv->devControlCmd);
DBGPRINT(F(" power limit "));
DBGPRINTLN(String(iv->powerLimit[0]));
}
iv->powerLimitAck = false;
mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false);
iv->radio->sendControlPacket(iv, iv->devControlCmd, iv->powerLimit, false);
mPayload[iv->id].txCmd = iv->devControlCmd;
//iv->clearCmdQueue();
//iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
} else {
#if defined(ESP32)
if((IV_HMS == iv->ivGen) || (IV_HMT == iv->ivGen)) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
if(((rec->ts + HMS_TIMEOUT_SEC) < *mTimestamp) && (mIvCmd56Cnt[iv->id] < 3)) {
iv->radio->switchFrequency(iv, HOY_BOOT_FREQ_KHZ, WORK_FREQ_KHZ);
mIvCmd56Cnt[iv->id]++;
return;
} else if(++mIvCmd56Cnt[iv->id] == 10)
mIvCmd56Cnt[iv->id] = 0;
}
#endif
uint8_t cmd = iv->getQueuedCmd();
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(cmd);
}
mRadio->prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmLastId, false);
iv->radio->prepareDevInformCmd(iv, cmd, mPayload[iv->id].ts, iv->alarmLastId, false);
mPayload[iv->id].txCmd = cmd;
}
}
@ -227,9 +241,6 @@ class HmPayload {
if (NULL == iv)
continue; // skip to next inverter
if (IV_HM != iv->ivGen) // only process HM inverters
continue; // skip to next inverter
if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (0 != mPayload[iv->id].txId)) {
// no processing needed if txId is not 0x95
mPayload[iv->id].complete = true;
@ -251,7 +262,7 @@ class HmPayload {
} else if(iv->devControlCmd == ActivePowerContr) {
DPRINT_IVID(DBG_INFO, iv->id);
DPRINTLN(DBG_INFO, F("retransmit power limit"));
mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true);
iv->radio->sendControlPacket(iv, iv->devControlCmd, iv->powerLimit, true);
} else {
if(false == mPayload[iv->id].gotFragment) {
DPRINT_IVID(DBG_WARN, iv->id);
@ -262,7 +273,7 @@ class HmPayload {
DBGPRINTLN(F("nothing received: complete retransmit"));
mPayload[iv->id].txCmd = iv->getQueuedCmd();
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd 0x") + String(mPayload[iv->id].txCmd, HEX));
mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
iv->radio->prepareDevInformCmd(iv, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
}
} else {
for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) {
@ -273,7 +284,7 @@ class HmPayload {
DBGPRINT(String(i + 1));
DBGPRINTLN(F(" missing: Request Retransmit"));
}
mRadio->sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true);
iv->radio->sendCmdPacket(iv, TX_REQ_INFO, (SINGLE_FRAME + i), true);
break; // only request retransmit one frame per loop
}
yield();
@ -290,12 +301,13 @@ class HmPayload {
mPayload[iv->id].retransmits++;
mPayload[iv->id].txCmd = iv->getQueuedCmd();
if (mSerialDebug) {
DPRINTLN(DBG_WARN, F("CRC Error: Request Complete Retransmit"));
DPRINT_IVID(DBG_WARN, iv->id);
DBGPRINTLN(F("CRC Error: Request Complete Retransmit"));
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(mPayload[iv->id].txCmd);
}
mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmLastId, true);
iv->radio->prepareDevInformCmd(iv, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmLastId, true);
}
} else { // payload complete
if (mSerialDebug) {
@ -345,7 +357,7 @@ class HmPayload {
DPRINTLN(DBG_ERROR, F("record is NULL!"));
} else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) {
if (mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES))
mStat->rxSuccess++;
iv->radioStatistics.rxSuccess++;
rec->ts = mPayload[iv->id].ts;
for (uint8_t i = 0; i < rec->length; i++) {
@ -375,8 +387,8 @@ class HmPayload {
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(cmd);
}
mStat->rxSuccess++;
mRadio->prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmLastId, false);
iv->radioStatistics.rxSuccess++;
iv->radio->prepareDevInformCmd(iv, cmd, mPayload[iv->id].ts, iv->alarmLastId, false);
mPayload[iv->id].txCmd = cmd;
*/
mHighPrioIv = iv;
@ -388,7 +400,7 @@ class HmPayload {
DBGPRINT(String(rec->pyldLen));
DBGPRINTLN(F(" bytes"));
}
mStat->rxFail++;
iv->radioStatistics.rxFail++;
}
iv->setQueuedCmdFinished();
@ -461,11 +473,10 @@ class HmPayload {
IApp *mApp;
HMSYSTEM *mSys;
HMRADIO *mRadio;
statistics_t *mStat;
uint8_t mMaxRetrans;
uint32_t *mTimestamp;
invPayload_t mPayload[MAX_NUM_INVERTERS];
uint8_t mIvCmd56Cnt[MAX_NUM_INVERTERS];
bool mSerialDebug;
Inverter<> *mHighPrioIv;

123
src/hm/hmRadio.h

@ -3,32 +3,25 @@
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#ifndef __RADIO_H__
#define __RADIO_H__
#ifndef __HM_RADIO_H__
#define __HM_RADIO_H__
#include "../utils/dbg.h"
#include <RF24.h>
#include "../utils/crc.h"
#include "../config/config.h"
#include "SPI.h"
#include "radio.h"
#define SPI_SPEED 1000000
#define RF_CHANNELS 5
#define TX_REQ_INFO 0x15
#define TX_REQ_DEVCONTROL 0x51
#define ALL_FRAMES 0x80
#define SINGLE_FRAME 0x81
const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"};
//-----------------------------------------------------------------------------
// HM Radio class
//-----------------------------------------------------------------------------
template <uint8_t IRQ_PIN = DEF_NRF_IRQ_PIN, uint8_t CE_PIN = DEF_NRF_CE_PIN, uint8_t CS_PIN = DEF_NRF_CS_PIN, uint8_t AMP_PWR = RF24_PA_LOW, uint8_t SCLK_PIN = DEF_NRF_SCLK_PIN, uint8_t MOSI_PIN = DEF_NRF_MOSI_PIN, uint8_t MISO_PIN = DEF_NRF_MISO_PIN>
class HmRadio {
template <uint8_t IRQ_PIN = DEF_NRF_IRQ_PIN, uint8_t CE_PIN = DEF_NRF_CE_PIN, uint8_t CS_PIN = DEF_NRF_CS_PIN, uint8_t AMP_PWR = RF24_PA_LOW, uint8_t SCLK_PIN = DEF_NRF_SCLK_PIN, uint8_t MOSI_PIN = DEF_NRF_MOSI_PIN, uint8_t MISO_PIN = DEF_NRF_MISO_PIN, uint32_t DTU_SN = 0x81001765>
class HmRadio : public Radio {
public:
HmRadio() : mNrf24(CE_PIN, CS_PIN, SPI_SPEED) {
if(mSerialDebug) {
@ -40,6 +33,7 @@ class HmRadio {
DBGPRINT(String(SPI_SPEED));
DBGPRINTLN(F(")"));
}
mDtuSn = DTU_SN;
// Depending on the program, the module can work on 2403, 2423, 2440, 2461 or 2475MHz.
// Channel List 2403, 2423, 2440, 2461, 2475MHz
@ -58,28 +52,11 @@ class HmRadio {
}
~HmRadio() {}
void setup(statistics_t *stat, uint8_t ampPwr = RF24_PA_LOW, uint8_t irq = IRQ_PIN, uint8_t ce = CE_PIN, uint8_t cs = CS_PIN, uint8_t sclk = SCLK_PIN, uint8_t mosi = MOSI_PIN, uint8_t miso = MISO_PIN) {
void setup(uint8_t ampPwr = RF24_PA_LOW, uint8_t irq = IRQ_PIN, uint8_t ce = CE_PIN, uint8_t cs = CS_PIN, uint8_t sclk = SCLK_PIN, uint8_t mosi = MOSI_PIN, uint8_t miso = MISO_PIN) {
DPRINTLN(DBG_VERBOSE, F("hmRadio.h:setup"));
pinMode(irq, INPUT_PULLUP);
mStat = stat;
uint32_t dtuSn = 0x87654321;
uint32_t chipID = 0; // will be filled with last 3 bytes of MAC
#ifdef ESP32
uint64_t MAC = ESP.getEfuseMac();
chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF);
#else
chipID = ESP.getChipId();
#endif
if(chipID) {
dtuSn = 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal
for(int i = 0; i < 7; i++) {
dtuSn |= (chipID % 10) << (i * 4);
chipID /= 10;
}
}
// change the byte order of the DTU serial number and append the required 0x01 at the end
DTU_RADIO_ID = ((uint64_t)(((dtuSn >> 24) & 0xFF) | ((dtuSn >> 8) & 0xFF00) | ((dtuSn << 8) & 0xFF0000) | ((dtuSn << 24) & 0xFF000000)) << 8) | 0x01;
generateDtuSn();
#ifdef ESP32
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
@ -104,7 +81,7 @@ class HmRadio {
mNrf24.enableDynamicPayloads();
mNrf24.setCRCLength(RF24_CRC_16);
mNrf24.setAddressWidth(5);
mNrf24.openReadingPipe(1, reinterpret_cast<uint8_t*>(&DTU_RADIO_ID));
mNrf24.openReadingPipe(1, reinterpret_cast<uint8_t*>(&mDtuSn));
// enable all receiving interrupts
mNrf24.maskIRQ(false, false, false);
@ -117,7 +94,7 @@ class HmRadio {
DPRINTLN(DBG_INFO, F("Radio Config:"));
mNrf24.printPrettyDetails();
DPRINT(DBG_INFO, F("DTU_SN: 0x"));
DBGPRINTLN(String(DTU_RADIO_ID, HEX));
DBGPRINTLN(String(mDtuSn, HEX));
} else
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
}
@ -157,22 +134,15 @@ class HmRadio {
return true;
}
void handleIntr(void) {
mIrqRcvd = true;
}
bool isChipConnected(void) {
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:isChipConnected"));
return mNrf24.isChipConnected();
}
void enableDebug() {
mSerialDebug = true;
}
void sendControlPacket(uint64_t invId, uint8_t cmd, uint16_t *data, bool isRetransmit, bool isNoMI = true, bool is4chMI = false) {
void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit, bool isNoMI = true, bool is4chMI = false) {
DPRINT(DBG_INFO, F("sendControlPacket cmd: 0x"));
DBGHEXLN(cmd);
initPacket(invId, TX_REQ_DEVCONTROL, SINGLE_FRAME);
initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME);
uint8_t cnt = 10;
if (isNoMI) {
mTxBuf[cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor
@ -216,28 +186,7 @@ class HmRadio {
}
cnt++;
}
sendPacket(invId, cnt, isRetransmit, isNoMI);
}
void prepareDevInformCmd(uint64_t invId, uint8_t cmd, uint32_t ts, uint16_t alarmMesId, bool isRetransmit, uint8_t reqfld=TX_REQ_INFO) { // might not be necessary to add additional arg.
if(mSerialDebug) {
DPRINT(DBG_DEBUG, F("prepareDevInformCmd 0x"));
DPRINTLN(DBG_DEBUG,String(cmd, HEX));
}
initPacket(invId, reqfld, ALL_FRAMES);
mTxBuf[10] = cmd; // cid
mTxBuf[11] = 0x00;
CP_U32_LittleEndian(&mTxBuf[12], ts);
if (cmd == AlarmData ) { //cmd == RealTimeRunData_Debug ||
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
mTxBuf[19] = (alarmMesId ) & 0xff;
}
sendPacket(invId, 24, isRetransmit);
}
void sendCmdPacket(uint64_t invId, uint8_t mid, uint8_t pid, bool isRetransmit, bool appendCrc16=true) {
initPacket(invId, mid, pid);
sendPacket(invId, 10, isRetransmit, appendCrc16);
sendPacket(iv, cnt, isRetransmit, isNoMI);
}
uint8_t getDataRate(void) {
@ -251,7 +200,6 @@ class HmRadio {
}
std::queue<packet_t> mBufCtrl;
bool mSerialDebug;
private:
bool getReceived(void) {
@ -283,34 +231,8 @@ class HmRadio {
return isLastPackage;
}
void initPacket(uint64_t invId, uint8_t mid, uint8_t pid) {
if(mSerialDebug) {
DPRINT(DBG_VERBOSE, F("initPacket, mid: "));
DPRINT(DBG_VERBOSE, String(mid, HEX));
DPRINT(DBG_VERBOSE,F(" pid: "));
DPRINTLN(DBG_VERBOSE,String(pid, HEX));
}
memset(mTxBuf, 0, MAX_RF_PAYLOAD_SIZE);
mTxBuf[0] = mid; // message id
CP_U32_BigEndian(&mTxBuf[1], (invId >> 8));
CP_U32_BigEndian(&mTxBuf[5], (DTU_RADIO_ID >> 8));
mTxBuf[9] = pid;
}
void sendPacket(uint64_t invId, uint8_t len, bool isRetransmit, bool appendCrc16=true) {
//DPRINTLN(DBG_VERBOSE, F("hmRadio.h:sendPacket"));
//DPRINTLN(DBG_VERBOSE, "sent packet: #" + String(mStat->txCnt));
// append crc's
if (appendCrc16 && (len > 10)) {
// crc control data
uint16_t crc = ah::crc16(&mTxBuf[10], len - 10);
mTxBuf[len++] = (crc >> 8) & 0xff;
mTxBuf[len++] = (crc ) & 0xff;
}
// crc over all
mTxBuf[len] = ah::crc8(mTxBuf, len);
len++;
void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) {
updateCrcs(len, appendCrc16);
// set TX and RX channels
mTxChIdx = (mTxChIdx + 1) % RF_CHANNELS;
@ -327,17 +249,18 @@ class HmRadio {
mNrf24.stopListening();
mNrf24.setChannel(mRfChLst[mTxChIdx]);
mNrf24.openWritingPipe(reinterpret_cast<uint8_t*>(&invId));
mNrf24.openWritingPipe(reinterpret_cast<uint8_t*>(&iv->radioId.u64));
mNrf24.startWrite(mTxBuf, len, false); // false = request ACK response
if(isRetransmit)
mStat->retransmits++;
iv->radioStatistics.retransmits++;
else
mStat->txCnt++;
iv->radioStatistics.txCnt++;
}
volatile bool mIrqRcvd;
uint64_t DTU_RADIO_ID;
uint64_t getIvId(Inverter<> *iv) {
return iv->radioId.u64;
}
uint8_t mRfChLst[RF_CHANNELS];
uint8_t mTxChIdx;
@ -345,8 +268,6 @@ class HmRadio {
SPIClass* mSpi;
RF24 mNrf24;
uint8_t mTxBuf[MAX_RF_PAYLOAD_SIZE];
statistics_t *mStat;
};
#endif /*__RADIO_H__*/
#endif /*__HM_RADIO_H__*/

104
src/hm/hmSystem.h

@ -7,70 +7,45 @@
#define __HM_SYSTEM_H__
#include "hmInverter.h"
#include <functional>
template <uint8_t MAX_INVERTER=3, class INVERTERTYPE=Inverter<float>>
class HmSystem {
public:
HmSystem() {}
void setup(uint32_t *timestamp) {
void setup(uint32_t *timestamp, cfgInst_t *config) {
mInverter[0].timestamp = timestamp;
mNumInv = 0;
}
void addInverters(cfgInst_t *config) {
mInverter[0].generalConfig = config;
Inverter<> *iv;
for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
iv = addInverter(&config->iv[i]);
if (0ULL != config->iv[i].serial.u64) {
if (NULL != iv) {
DPRINT(DBG_INFO, "added inverter ");
if(iv->config->serial.b[5] == 0x11) {
if((iv->config->serial.b[4] & 0x0f) == 0x04)
DBGPRINT("HMS");
else
DBGPRINT("HM");
} else if(iv->config->serial.b[5] == 0x13)
DBGPRINT("HMT");
else
DBGPRINT(((iv->config->serial.b[4] & 0x03) == 0x01) ? " (2nd Gen) " : " (3rd Gen) ");
DBGPRINTLN(String(iv->config->serial.u64, HEX));
if((iv->config->serial.b[5] == 0x10) && ((iv->config->serial.b[4] & 0x03) == 0x01))
DPRINTLN(DBG_WARN, F("MI Inverter are not fully supported now!!!"));
}
}
}
mNumInv = 0;
}
INVERTERTYPE *addInverter(cfgIv_t *config) {
void addInverter(uint8_t id, std::function<void(Inverter<> *iv)> cb) {
DPRINTLN(DBG_VERBOSE, F("hmSystem.h:addInverter"));
if(MAX_INVERTER <= mNumInv) {
DPRINT(DBG_WARN, F("max number of inverters reached!"));
return NULL;
return;
}
INVERTERTYPE *p = &mInverter[mNumInv];
p->id = mNumInv;
p->config = config;
DPRINT(DBG_VERBOSE, "SERIAL: " + String(p->config->serial.b[5], HEX));
DPRINTLN(DBG_VERBOSE, " " + String(p->config->serial.b[4], HEX));
if((p->config->serial.b[5] == 0x11) || (p->config->serial.b[5] == 0x10)) {
switch(p->config->serial.b[4]) {
INVERTERTYPE *iv = &mInverter[mNumInv];
iv->id = mNumInv;
iv->config = &mInverter[0].generalConfig->iv[id];
DPRINT(DBG_VERBOSE, "SERIAL: " + String(iv->config->serial.b[5], HEX));
DPRINTLN(DBG_VERBOSE, " " + String(iv->config->serial.b[4], HEX));
if((iv->config->serial.b[5] == 0x11) || (iv->config->serial.b[5] == 0x10)) {
switch(iv->config->serial.b[4]) {
case 0x24: // HMS-500
case 0x22:
case 0x21: p->type = INV_TYPE_1CH;
case 0x21: iv->type = INV_TYPE_1CH;
break;
case 0x44: // HMS-1000
case 0x42:
case 0x41: p->type = INV_TYPE_2CH;
case 0x41: iv->type = INV_TYPE_2CH;
break;
case 0x64: // HMS-2000
case 0x62:
case 0x61: p->type = INV_TYPE_4CH;
case 0x61: iv->type = INV_TYPE_4CH;
break;
default:
@ -78,26 +53,47 @@ class HmSystem {
break;
}
if(p->config->serial.b[5] == 0x11) {
if((p->config->serial.b[4] & 0x0f) == 0x04)
p->ivGen = IV_HMS;
if(iv->config->serial.b[5] == 0x11) {
if((iv->config->serial.b[4] & 0x0f) == 0x04)
iv->ivGen = IV_HMS;
else
p->ivGen = IV_HM;
iv->ivGen = IV_HM;
}
else if((p->config->serial.b[4] & 0x03) == 0x02) // MI 3rd Gen -> same as HM
p->ivGen = IV_HM;
else if((iv->config->serial.b[4] & 0x03) == 0x02) // MI 3rd Gen -> same as HM
iv->ivGen = IV_HM;
else // MI 2nd Gen
p->ivGen = IV_MI;
} else if(p->config->serial.b[5] == 0x13) {
p->ivGen = IV_HMT;
p->type = INV_TYPE_6CH;
} else if(p->config->serial.u64 != 0ULL)
iv->ivGen = IV_MI;
} else if(iv->config->serial.b[5] == 0x13) {
iv->ivGen = IV_HMT;
iv->type = INV_TYPE_6CH;
} else if(iv->config->serial.u64 != 0ULL) {
DPRINTLN(DBG_ERROR, F("inverter type can't be detected!"));
return;
} else
iv->ivGen = IV_UNKNOWN;
p->init();
iv->init();
mNumInv ++;
return p;
if(IV_UNKNOWN == iv->ivGen)
return; // serial is 0
DPRINT(DBG_INFO, "added inverter ");
if(iv->config->serial.b[5] == 0x11) {
if((iv->config->serial.b[4] & 0x0f) == 0x04)
DBGPRINT("HMS");
else
DBGPRINT("HM");
} else if(iv->config->serial.b[5] == 0x13)
DBGPRINT("HMT");
else
DBGPRINT(((iv->config->serial.b[4] & 0x03) == 0x01) ? " (2nd Gen) " : " (3rd Gen) ");
DBGPRINTLN(String(iv->config->serial.u64, HEX));
if((iv->config->serial.b[5] == 0x10) && ((iv->config->serial.b[4] & 0x03) == 0x01))
DPRINTLN(DBG_WARN, F("MI Inverter are not fully supported now!!!"));
cb(iv);
}
INVERTERTYPE *findInverter(uint8_t buf[]) {

65
src/hm/miPayload.h

@ -1,12 +1,10 @@
//-----------------------------------------------------------------------------
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#ifndef __MI_PAYLOAD_H__
#define __MI_PAYLOAD_H__
//#include "hmInverter.h"
#include "../utils/dbg.h"
#include "../utils/crc.h"
#include "../config/config.h"
@ -35,16 +33,14 @@ typedef struct {
typedef std::function<void(uint8_t, Inverter<> *)> miPayloadListenerType;
template<class HMSYSTEM, class HMRADIO>
template<class HMSYSTEM>
class MiPayload {
public:
MiPayload() {}
void setup(IApp *app, HMSYSTEM *sys, HMRADIO *radio, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) {
void setup(IApp *app, HMSYSTEM *sys, uint8_t maxRetransmits, uint32_t *timestamp) {
mApp = app;
mSys = sys;
mRadio = radio;
mStat = stat;
mMaxRetrans = maxRetransmits;
mTimestamp = timestamp;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
@ -53,7 +49,7 @@ class MiPayload {
}
mSerialDebug = false;
mHighPrioIv = NULL;
mCbMiPayload = NULL;
mCbPayload = NULL;
}
void enableSerialDebug(bool enable) {
@ -61,7 +57,7 @@ class MiPayload {
}
void addPayloadListener(miPayloadListenerType cb) {
mCbMiPayload = cb;
mCbPayload = cb;
}
void addAlarmListener(alarmListenerType cb) {
@ -89,11 +85,11 @@ class MiPayload {
if (mSerialDebug)
DPRINT_IVID(DBG_INFO, iv->id);
if (!mPayload[iv->id].gotFragment) {
mStat->rxFailNoAnser++; // got nothing
iv->radioStatistics.rxFailNoAnser++; // got nothing
if (mSerialDebug)
DBGPRINTLN(F("enqueued cmd failed/timeout"));
} else {
mStat->rxFail++; // got "fragments" (part of the required messages)
iv->radioStatistics.rxFail++; // got "fragments" (part of the required messages)
// but no complete set of responses
if (mSerialDebug) {
DBGPRINT(F("no complete Payload received! (retransmits: "));
@ -112,7 +108,7 @@ class MiPayload {
mPayload[iv->id].requested = true;
yield();
if (mSerialDebug){
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("Requesting Inv SN "));
DBGPRINTLN(String(iv->config->serial.u64, HEX));
@ -127,7 +123,7 @@ class MiPayload {
DBGPRINTLN(String(iv->powerLimit[0]));
}
iv->powerLimitAck = false;
mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false, false, iv->type == INV_TYPE_4CH);
iv->radio->sendControlPacket(iv, iv->devControlCmd, iv->powerLimit, false, false, iv->type == INV_TYPE_4CH);
mPayload[iv->id].txCmd = iv->devControlCmd;
mPayload[iv->id].limitrequested = true;
@ -153,7 +149,7 @@ class MiPayload {
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(cmd);
}
mRadio->sendCmdPacket(iv->radioId.u64, cmd, cmd2, false, false);
iv->radio->sendCmdPacket(iv, cmd, cmd2, false, false);
mPayload[iv->id].txCmd = cmd;
if (iv->type == INV_TYPE_1CH || iv->type == INV_TYPE_2CH) {
@ -262,7 +258,7 @@ class MiPayload {
DPRINTLN(DBG_ERROR, F("record is NULL!"));
} else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) {
if (mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES))
mStat->rxSuccess++;
iv->radioStatistics.rxSuccess++;
rec->ts = mPayload[iv->id].ts;
for (uint8_t i = 0; i < rec->length; i++) {
@ -284,7 +280,7 @@ class MiPayload {
}
} else {
DPRINTLN(DBG_ERROR, F("plausibility check failed, expected ") + String(rec->pyldLen) + F(" bytes"));
mStat->rxFail++;
iv->radioStatistics.rxFail++;
}
iv->setQueuedCmdFinished();
@ -330,7 +326,7 @@ class MiPayload {
} else if(iv->devControlCmd == ActivePowerContr) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("retransmit power limit"));
mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true, false);
iv->radio->sendControlPacket(iv, iv->devControlCmd, iv->powerLimit, true, false);
} else {
uint8_t cmd = mPayload[iv->id].txCmd;
if (mPayload[iv->id].retransmits < mMaxRetrans) {
@ -343,10 +339,10 @@ class MiPayload {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("retransmit on failed first request"));
mPayload[iv->id].rxTmo = true;
mRadio->sendCmdPacket(iv->radioId.u64, cmd, cmd, true, false);
iv->radio->sendCmdPacket(iv, cmd, cmd, true, false);
} else if ( cmd == 0x0f ) {
//hard/firmware request
mRadio->sendCmdPacket(iv->radioId.u64, 0x0f, 0x00, true, false);
iv->radio->sendCmdPacket(iv, 0x0f, 0x00, true, false);
mPayload[id].multi_parts = 0;
} else {
bool change = false;
@ -384,7 +380,7 @@ class MiPayload {
DBGPRINT(F(" 0x"));
DBGHEXLN(cmd);
mPayload[id].multi_parts = 0;
mRadio->sendCmdPacket(iv->radioId.u64, cmd, cmd, true, false);
iv->radio->sendCmdPacket(iv, cmd, cmd, true, false);
yield();
}
} else {
@ -404,7 +400,7 @@ class MiPayload {
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(mPayload[iv->id].txCmd);
}
mRadio->sendCmdPacket(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].txCmd, false, false);
iv->radio->sendCmdPacket(iv, mPayload[iv->id].txCmd, mPayload[iv->id].txCmd, false, false);
} else {
mPayload[iv->id].rxTmo = true;
}
@ -420,9 +416,9 @@ class MiPayload {
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(cmd);
}
mStat->rxSuccess++;
//mRadio->prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
mRadio->prepareDevInformCmd(iv->radioId.u64, iv->getType(),
iv->radioStatistics.rxSuccess++;
//iv->radio->prepareDevInformCmd(iv, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
iv->radio->prepareDevInformCmd(iv, iv->getType(),
iv->getNextTxChanIndex(), cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
mPayload[iv->id].txCmd = cmd; */
if (mHighPrioIv == NULL)
@ -438,8 +434,8 @@ class MiPayload {
private:
void notify(uint8_t val, Inverter<> *iv) {
if(NULL != mCbMiPayload)
(mCbMiPayload)(val, iv);
if(NULL != mCbPayload)
(mCbPayload)(val, iv);
}
void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t stschan = CH1) {
@ -510,14 +506,11 @@ class MiPayload {
}
/*if(AlarmData == mPayload[iv->id].txCmd) {
uint8_t i = 0;
uint16_t code;
uint32_t start, end;
while(1) {
code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end);
if(0 == code)
if(0 == iv->parseAlarmLog(i++, payload, payloadLen))
break;
if (NULL != mCbAlarm)
(mCbAlarm)(code, start, end);
(mCbAlarm)(iv);
yield();
}
}*/
@ -600,7 +593,7 @@ class MiPayload {
iv->isProducing();
iv->setQueuedCmdFinished();
mStat->rxSuccess++;
iv->radioStatistics.rxSuccess++;
yield();
notify(RealTimeRunData_Debug, iv);
}
@ -756,7 +749,7 @@ const byteAssign_t InfoAssignment[] = {
mPayload[iv->id].complete = true;
mPayload[iv->id].rxTmo = true;
mPayload[iv->id].requested= false;
mStat->rxSuccess++;
iv->radioStatistics.rxSuccess++;
}
}
@ -789,16 +782,14 @@ const byteAssign_t InfoAssignment[] = {
IApp *mApp;
HMSYSTEM *mSys;
HMRADIO *mRadio;
statistics_t *mStat;
uint8_t mMaxRetrans;
uint32_t *mTimestamp;
miPayload_t mPayload[MAX_NUM_INVERTERS];
bool mSerialDebug;
Inverter<> *mHighPrioIv;
alarmListenerType mCbAlarm;
payloadListenerType mCbMiPayload;
payloadListenerType mCbPayload;
};
#endif /*__MI_PAYLOAD_H__*/

102
src/hm/radio.h

@ -0,0 +1,102 @@
//-----------------------------------------------------------------------------
// 2023 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#ifndef __RADIO_H__
#define __RADIO_H__
#define TX_REQ_INFO 0x15
#define TX_REQ_DEVCONTROL 0x51
#define ALL_FRAMES 0x80
#define SINGLE_FRAME 0x81
#include "../utils/dbg.h"
#include "../utils/crc.h"
// forward declaration of class
template <class REC_TYP=float>
class Inverter;
// abstract radio interface
class Radio {
public:
virtual void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit, bool isNoMI = true, bool is4chMI = false) = 0;
virtual bool switchFrequency(Inverter<> *iv, uint32_t fromkHz, uint32_t tokHz) { return true; }
void handleIntr(void) {
mIrqRcvd = true;
}
void enableDebug() {
mSerialDebug = true;
}
void sendCmdPacket(Inverter<> *iv, uint8_t mid, uint8_t pid, bool isRetransmit, bool appendCrc16=true) {
initPacket(getIvId(iv), mid, pid);
sendPacket(iv, 10, isRetransmit, appendCrc16);
}
void prepareDevInformCmd(Inverter<> *iv, uint8_t cmd, uint32_t ts, uint16_t alarmMesId, bool isRetransmit, uint8_t reqfld=TX_REQ_INFO) { // might not be necessary to add additional arg.
if(mSerialDebug) {
DPRINT(DBG_DEBUG, F("prepareDevInformCmd 0x"));
DPRINTLN(DBG_DEBUG,String(cmd, HEX));
}
initPacket(getIvId(iv), reqfld, ALL_FRAMES);
mTxBuf[10] = cmd;
CP_U32_LittleEndian(&mTxBuf[12], ts);
if (cmd == AlarmData ) { //cmd == RealTimeRunData_Debug ||
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
mTxBuf[19] = (alarmMesId ) & 0xff;
}
sendPacket(iv, 24, isRetransmit);
}
protected:
virtual void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) = 0;
virtual uint64_t getIvId(Inverter<> *iv) = 0;
void initPacket(uint64_t ivId, uint8_t mid, uint8_t pid) {
mTxBuf[0] = mid;
CP_U32_BigEndian(&mTxBuf[1], ivId >> 8);
CP_U32_LittleEndian(&mTxBuf[5], mDtuSn);
mTxBuf[9] = pid;
memset(&mTxBuf[10], 0x00, (MAX_RF_PAYLOAD_SIZE-10));
}
void updateCrcs(uint8_t len, bool appendCrc16=true) {
// append crc's
if (appendCrc16 && (len > 10)) {
// crc control data
uint16_t crc = ah::crc16(&mTxBuf[10], len - 10);
mTxBuf[len++] = (crc >> 8) & 0xff;
mTxBuf[len++] = (crc ) & 0xff;
}
// crc over all
mTxBuf[len] = ah::crc8(mTxBuf, len);
len++;
}
void generateDtuSn(void) {
uint32_t chipID = 0;
#ifdef ESP32
uint64_t MAC = ESP.getEfuseMac();
chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF);
#else
chipID = ESP.getChipId();
#endif
mDtuSn = 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal
for(int i = 0; i < 7; i++) {
mDtuSn |= (chipID % 10) << (i * 4);
chipID /= 10;
}
}
uint32_t mDtuSn;
volatile bool mIrqRcvd;
bool mSerialDebug;
uint8_t mTxBuf[MAX_RF_PAYLOAD_SIZE];
};
#endif /*__RADIO_H__*/

4
src/hms/cmt2300a.h

@ -239,7 +239,7 @@ class Cmt2300a {
return CMT_SUCCESS;
}
uint8_t getRx(uint8_t buf[], uint8_t len, int8_t *rssi) {
uint8_t getRx(uint8_t buf[], uint8_t *rxLen, uint8_t maxlen, int8_t *rssi) {
if(mTxPending)
return CMT_ERR_TX_PENDING;
@ -250,7 +250,7 @@ class Cmt2300a {
if(!cmtSwitchStatus(CMT2300A_GO_STBY, CMT2300A_STA_STBY))
return CMT_ERR_SWITCH_STATE;
mSpi.readFifo(buf, len);
mSpi.readFifo(buf, rxLen, maxlen);
*rssi = mSpi.readReg(CMT2300A_CUS_RSSI_DBM) - 128;
if(!cmtSwitchStatus(CMT2300A_GO_SLEEP, CMT2300A_STA_SLEEP))

9
src/hms/esp32_3wSpi.h

@ -141,7 +141,7 @@ class esp32_3wSpi {
SPI_PARAM_UNLOCK();
}
void readFifo(uint8_t buf[], uint8_t len) {
void readFifo(uint8_t buf[], uint8_t *len, uint8_t maxlen) {
if(!mInitialized)
return;
uint8_t rx_data;
@ -154,10 +154,13 @@ class esp32_3wSpi {
};
SPI_PARAM_LOCK();
for(uint8_t i = 0; i < len; i++) {
for(uint8_t i = 0; i < maxlen; i++) {
ESP_ERROR_CHECK(spi_device_polling_transmit(spi_fifo, &t));
delayMicroseconds(4); // > 4 us
buf[i] = rx_data;
if(0 == i)
*len = rx_data;
else
buf[i-1] = rx_data;
}
SPI_PARAM_UNLOCK();
}

415
src/hms/hmsPayload.h

@ -1,415 +0,0 @@
//-----------------------------------------------------------------------------
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#ifndef __HMS_PAYLOAD_H__
#define __HMS_PAYLOAD_H__
#include "../utils/dbg.h"
#include "../utils/crc.h"
#include "../config/config.h"
#include <Arduino.h>
#define HMS_TIMEOUT_SEC 30 // 30s * 1000
typedef struct {
uint8_t txCmd;
uint8_t txId;
//uint8_t invId;
uint32_t ts;
uint8_t data[MAX_PAYLOAD_ENTRIES][MAX_RF_PAYLOAD_SIZE];
int8_t rssi[MAX_PAYLOAD_ENTRIES];
uint8_t len[MAX_PAYLOAD_ENTRIES];
bool complete;
uint8_t maxPackId;
bool lastFound;
uint8_t retransmits;
bool requested;
bool gotFragment;
} hmsPayload_t;
typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType;
typedef std::function<void(Inverter<> *)> alarmListenerType;
template<class HMSYSTEM, class RADIO>
class HmsPayload {
public:
HmsPayload() {}
void setup(IApp *app, HMSYSTEM *sys, RADIO *radio, statistics_t *stat, uint8_t maxRetransmits, uint32_t *timestamp) {
mApp = app;
mSys = sys;
mRadio = radio;
mStat = stat;
mMaxRetrans = maxRetransmits;
mTimestamp = timestamp;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
reset(i);
mIvCmd56Cnt[i] = 0;
}
mSerialDebug = false;
mHighPrioIv = NULL;
mCbAlarm = NULL;
mCbPayload = NULL;
//mLastRx = 0;
}
void enableSerialDebug(bool enable) {
mSerialDebug = enable;
}
void addPayloadListener(payloadListenerType cb) {
mCbPayload = cb;
}
void addAlarmListener(alarmListenerType cb) {
mCbAlarm = cb;
}
void loop() {
if(NULL != mHighPrioIv) {
ivSend(mHighPrioIv, true);
mHighPrioIv = NULL;
}
}
void ivSendHighPrio(Inverter<> *iv) {
mHighPrioIv = iv;
}
void ivSend(Inverter<> *iv, bool highPrio = false) {
if ((IV_HMS != iv->ivGen) && (IV_HMT != iv->ivGen)) // only process HMS inverters
return;
if(!highPrio) {
if (mPayload[iv->id].requested) {
if (!mPayload[iv->id].complete)
process(false); // no retransmit
if (!mPayload[iv->id].complete) {
if (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId)
mStat->rxFailNoAnser++; // got nothing
else
mStat->rxFail++; // got fragments but not complete response
iv->setQueuedCmdFinished(); // command failed
if (mSerialDebug)
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
/*if (mSerialDebug) {
DPRINT(DBG_INFO, F("(#"));
DBGPRINT(String(iv->id));
DBGPRINT(F(") no Payload received! (retransmits: "));
DBGPRINT(String(mPayload[iv->id].retransmits));
DBGPRINTLN(F(")"));
}*/
}
}
}
reset(iv->id);
mPayload[iv->id].requested = true;
yield();
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("Requesting Inv SN "));
DBGPRINTLN(String(iv->config->serial.u64, HEX));
}
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
if (iv->getDevControlRequest()) {
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("Devcontrol request 0x"));
DBGPRINT(String(iv->devControlCmd, HEX));
DBGPRINT(F(" power limit "));
DBGPRINTLN(String(iv->powerLimit[0]));
}
iv->powerLimitAck = false;
mRadio->sendControlPacket(&iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false);
mPayload[iv->id].txCmd = iv->devControlCmd;
//iv->clearCmdQueue();
//iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
} else if(((rec->ts + HMS_TIMEOUT_SEC) < *mTimestamp) && (mIvCmd56Cnt[iv->id] < 3)) {
mRadio->switchFrequency(&iv->radioId.u64, HOY_BOOT_FREQ_KHZ, WORK_FREQ_KHZ);
mIvCmd56Cnt[iv->id]++;
} else {
if(++mIvCmd56Cnt[iv->id] == 10)
mIvCmd56Cnt[iv->id] = 0;
uint8_t cmd = iv->getQueuedCmd();
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(cmd);
mRadio->prepareDevInformCmd(&iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmLastId, false);
mPayload[iv->id].txCmd = cmd;
}
}
void add(Inverter<> *iv, hmsPacket_t *p) {
if (p->data[1] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command
mPayload[iv->id].txId = p->data[1];
DPRINTLN(DBG_DEBUG, F("Response from info request received"));
uint8_t *pid = &p->data[10];
if (*pid == 0x00) {
DPRINT(DBG_DEBUG, F("fragment number zero received and ignored"));
} else {
DPRINTLN(DBG_DEBUG, "PID: 0x" + String(*pid, HEX));
if ((*pid & 0x7F) < MAX_PAYLOAD_ENTRIES) {
memcpy(mPayload[iv->id].data[(*pid & 0x7F) - 1], &p->data[11], p->data[0] - 11);
mPayload[iv->id].len[(*pid & 0x7F) - 1] = p->data[0] -11;
mPayload[iv->id].gotFragment = true;
mPayload[iv->id].rssi[(*pid & 0x7F) - 1] = p->rssi;
}
if ((*pid & ALL_FRAMES) == ALL_FRAMES) {
// Last packet
if (((*pid & 0x7f) > mPayload[iv->id].maxPackId) || (MAX_PAYLOAD_ENTRIES == mPayload[iv->id].maxPackId)) {
mPayload[iv->id].maxPackId = (*pid & 0x7f);
if (*pid > 0x81)
mPayload[iv->id].lastFound = true;
}
}
}
} else if (p->data[1] == (TX_REQ_DEVCONTROL + ALL_FRAMES)) { // response from dev control command
DPRINTLN(DBG_DEBUG, F("Response from devcontrol request received"));
mPayload[iv->id].txId = p->data[1];
iv->clearDevControlRequest();
if ((p->data[13] == ActivePowerContr) && (p->data[14] == 0x00)) {
bool ok = true;
if((p->data[11] == 0x00) && (p->data[12] == 0x00)) {
mApp->setMqttPowerLimitAck(iv);
iv->powerLimitAck = true;
} else
ok = false;
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F(" has "));
if(!ok) DBGPRINT(F("not "));
DBGPRINT(F("accepted power limit set point "));
DBGPRINT(String(iv->powerLimit[0]));
DBGPRINT(F(" with PowerLimitControl "));
DBGPRINTLN(String(iv->powerLimit[1]));
iv->clearCmdQueue();
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
if(mHighPrioIv == NULL) // do it immediately if possible
mHighPrioIv = iv;
}
iv->devControlCmd = Init;
}
}
void process(bool retransmit) {
for (uint8_t id = 0; id < mSys->getNumInverters(); id++) {
Inverter<> *iv = mSys->getInverterByPos(id);
if (NULL == iv)
continue; // skip to next inverter
if ((IV_HMS != iv->ivGen) && (IV_HMT != iv->ivGen)) // only process HMS inverters
continue; // skip to next inverter
if ((mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (0 != mPayload[iv->id].txId)) {
// no processing needed if txId is not 0x95
mPayload[iv->id].complete = true;
continue; // skip to next inverter
}
if (!mPayload[iv->id].complete) {
bool crcPass, pyldComplete;
crcPass = build(iv->id, &pyldComplete);
if (!crcPass && !pyldComplete) { // payload not complete
if ((mPayload[iv->id].requested) && (retransmit)) {
if (mPayload[iv->id].retransmits < mMaxRetrans) {
mPayload[iv->id].retransmits++;
if (iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm) {
// This is required to prevent retransmissions without answer.
DPRINTLN(DBG_INFO, F("Prevent retransmit on Restart / CleanState_LockAndAlarm..."));
mPayload[iv->id].retransmits = mMaxRetrans;
} else if(iv->devControlCmd == ActivePowerContr) {
DPRINTLN(DBG_INFO, F("retransmit power limit"));
mRadio->sendControlPacket(&iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true);
} else {
if(false == mPayload[iv->id].gotFragment) {
//DPRINTLN(DBG_WARN, F("nothing received: Request Complete Retransmit"));
//mPayload[iv->id].txCmd = iv->getQueuedCmd();
//DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd 0x") + String(mPayload[iv->id].txCmd, HEX));
//mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmLastId, true);
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("nothing received"));
mPayload[iv->id].retransmits = mMaxRetrans;
} else {
for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) {
if (mPayload[iv->id].len[i] == 0) {
DPRINT(DBG_WARN, F("Frame "));
DBGPRINT(String(i + 1));
DBGPRINTLN(F(" missing: Request Retransmit"));
//mRadio->sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true);
break; // only request retransmit one frame per loop
}
yield();
}
}
}
}
}
} /*else if(!crcPass && pyldComplete) { // crc error on complete Payload
if (mPayload[iv->id].retransmits < mMaxRetrans) {
mPayload[iv->id].retransmits++;
DPRINTLN(DBG_WARN, F("CRC Error: Request Complete Retransmit"));
mPayload[iv->id].txCmd = iv->getQueuedCmd();
DPRINT(DBG_INFO, F("(#"));
DBGPRINT(String(iv->id));
DBGPRINT(F(") prepareDevInformCmd 0x"));
DBGPRINTLN(String(mPayload[iv->id].txCmd, HEX));
mRadio->prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmLastId, true);
}
}*/ else { // payload complete
DPRINT(DBG_INFO, F("procPyld: cmd: 0x"));
DBGPRINTLN(String(mPayload[iv->id].txCmd, HEX));
//DPRINT(DBG_DEBUG, F("procPyld: txid: 0x"));
//DBGPRINTLN(String(mPayload[iv->id].txId, HEX));
DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId));
record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
mPayload[iv->id].complete = true;
uint8_t payload[150];
uint8_t payloadLen = 0;
memset(payload, 0, 150);
int8_t rssi = -127;
for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i++) {
if((mPayload[iv->id].len[i] + payloadLen) > 150) {
DPRINTLN(DBG_ERROR, F("payload buffer to small!"));
break;
}
memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i]));
payloadLen += (mPayload[iv->id].len[i]);
// get worst RSSI
if(mPayload[iv->id].rssi[i] > rssi)
rssi = mPayload[iv->id].rssi[i];
yield();
}
payloadLen -= 2;
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("Payload ("));
DBGPRINT(String(payloadLen));
DBGPRINT(F("): "));
ah::dumpBuf(payload, payloadLen);
}
if (NULL == rec) {
DPRINTLN(DBG_ERROR, F("record is NULL!"));
} else if ((rec->pyldLen == payloadLen) || (0 == rec->pyldLen)) {
if (mPayload[iv->id].txId == (TX_REQ_INFO + ALL_FRAMES))
mStat->rxSuccess++;
rec->ts = mPayload[iv->id].ts;
for (uint8_t i = 0; i < rec->length; i++) {
iv->addValue(i, payload, rec);
yield();
}
iv->rssi = rssi;
iv->doCalculations();
notify(mPayload[iv->id].txCmd, iv);
if(AlarmData == mPayload[iv->id].txCmd) {
uint8_t i = 0;
uint32_t start, end;
while(1) {
if(0 == iv->parseAlarmLog(i++, payload, payloadLen))
break;
if (NULL != mCbAlarm)
(mCbAlarm)(iv);
yield();
}
}
} else {
DPRINT(DBG_ERROR, F("plausibility check failed, expected "));
DBGPRINT(String(rec->pyldLen));
DBGPRINTLN(F(" bytes"));
mStat->rxFail++;
}
iv->setQueuedCmdFinished();
}
}
yield();
}
}
private:
void notify(uint8_t val, Inverter<> *iv) {
if(NULL != mCbPayload)
(mCbPayload)(val, iv);
}
bool build(uint8_t id, bool *complete) {
DPRINTLN(DBG_VERBOSE, F("build"));
uint16_t crc = 0xffff, crcRcv = 0x0000;
if (mPayload[id].maxPackId > MAX_PAYLOAD_ENTRIES)
mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES;
// check if all fragments are there
*complete = true;
for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) {
if(mPayload[id].len[i] == 0)
*complete = false;
}
if(!*complete)
return false;
for (uint8_t i = 0; i < mPayload[id].maxPackId; i++) {
if (mPayload[id].len[i] > 0) {
if (i == (mPayload[id].maxPackId - 1)) {
crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i] - 1, crc);
crcRcv = (mPayload[id].data[i][mPayload[id].len[i] - 2] << 8) | (mPayload[id].data[i][mPayload[id].len[i] - 1]);
} else
crc = ah::crc16(mPayload[id].data[i], mPayload[id].len[i], crc);
}
yield();
}
return (crc == crcRcv) ? true : false;
}
void reset(uint8_t id) {
//DPRINT(DBG_INFO, "resetPayload: id: ");
//DBGPRINTLN(String(id));
memset(&mPayload[id], 0, sizeof(hmsPayload_t));
mPayload[id].txCmd = 0;
mPayload[id].gotFragment = false;
//mPayload[id].retransmits = 0;
mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES;
mPayload[id].lastFound = false;
mPayload[id].complete = false;
mPayload[id].requested = false;
mPayload[id].ts = *mTimestamp;
}
IApp *mApp;
HMSYSTEM *mSys;
RADIO *mRadio;
statistics_t *mStat;
uint8_t mMaxRetrans;
uint32_t *mTimestamp;
//uint32_t mLastRx;
hmsPayload_t mPayload[MAX_NUM_INVERTERS];
uint8_t mIvCmd56Cnt[MAX_NUM_INVERTERS];
bool mSerialDebug;
Inverter<> *mHighPrioIv;
alarmListenerType mCbAlarm;
payloadListenerType mCbPayload;
};
#endif /*__HMS_PAYLOAD_H__*/

105
src/hms/hmsRadio.h

@ -6,21 +6,11 @@
#ifndef __HMS_RADIO_H__
#define __HMS_RADIO_H__
#include "../utils/dbg.h"
#include "cmt2300a.h"
typedef struct {
int8_t rssi;
uint8_t data[28];
} hmsPacket_t;
#define U32_B3(val) ((uint8_t)((val >> 24) & 0xff))
#define U32_B2(val) ((uint8_t)((val >> 16) & 0xff))
#define U32_B1(val) ((uint8_t)((val >> 8) & 0xff))
#define U32_B0(val) ((uint8_t)((val ) & 0xff))
#include "../hm/radio.h"
template<class SPI, uint32_t DTU_SN = 0x81001765>
class CmtRadio {
class CmtRadio : public Radio {
typedef SPI SpiType;
typedef Cmt2300a<SpiType> CmtType;
public:
@ -29,9 +19,8 @@ class CmtRadio {
mCmtAvail = false;
}
void setup(statistics_t *stat, uint8_t pinSclk, uint8_t pinSdio, uint8_t pinCsb, uint8_t pinFcsb, bool genDtuSn = true) {
void setup(uint8_t pinSclk, uint8_t pinSdio, uint8_t pinCsb, uint8_t pinFcsb, bool genDtuSn = true) {
mCmt.setup(pinSclk, pinSdio, pinCsb, pinFcsb);
mStat = stat;
reset(genDtuSn);
}
@ -54,25 +43,14 @@ class CmtRadio {
return false;
}
void tickSecond() {
}
void handleIntr(void) {
mIrqRcvd = true;
}
void enableDebug() {
mSerialDebug = true;
}
bool isConnected() {
return mCmtAvail;
}
void sendControlPacket(const uint64_t *ivId, uint8_t cmd, uint16_t *data, bool isRetransmit) {
void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit, bool isNoMI = true, bool is4chMI = false) {
DPRINT(DBG_INFO, F("sendControlPacket cmd: 0x"));
DBGHEXLN(cmd);
initPacket(ivId, TX_REQ_DEVCONTROL, SINGLE_FRAME);
initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME);
uint8_t cnt = 10;
mTxBuf[cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor
@ -84,10 +62,10 @@ class CmtRadio {
mTxBuf[cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling
}
sendPacket(cnt, isRetransmit);
sendPacket(iv, cnt, isRetransmit);
}
bool switchFrequency(const uint64_t *ivId, uint32_t fromkHz, uint32_t tokHz) {
bool switchFrequency(Inverter<> *iv, uint32_t fromkHz, uint32_t tokHz) {
uint8_t fromCh = mCmt.freq2Chan(fromkHz);
uint8_t toCh = mCmt.freq2Chan(tokHz);
@ -95,30 +73,16 @@ class CmtRadio {
return false;
mCmt.switchChannel(fromCh);
sendSwitchChCmd(ivId, toCh);
sendSwitchChCmd(iv, toCh);
mCmt.switchChannel(toCh);
return true;
}
void prepareDevInformCmd(const uint64_t *ivId, uint8_t cmd, uint32_t ts, uint16_t alarmMesId, bool isRetransmit, uint8_t reqfld=TX_REQ_INFO) { // might not be necessary to add additional arg.
initPacket(ivId, reqfld, ALL_FRAMES);
mTxBuf[10] = cmd;
CP_U32_LittleEndian(&mTxBuf[12], ts);
if (cmd == AlarmData ) { //cmd == RealTimeRunData_Debug ||
mTxBuf[18] = (alarmMesId >> 8) & 0xff;
mTxBuf[19] = (alarmMesId ) & 0xff;
}
sendPacket(24, isRetransmit);
}
std::queue<packet_t> mBufCtrl;
void sendPacket(uint8_t len, bool isRetransmit) {
if (len > 14) {
uint16_t crc = ah::crc16(&mTxBuf[10], len - 10);
mTxBuf[len++] = (crc >> 8) & 0xff;
mTxBuf[len++] = (crc ) & 0xff;
}
mTxBuf[len] = ah::crc8(mTxBuf, len);
len++;
private:
void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) {
updateCrcs(len, appendCrc16);
if(mSerialDebug) {
DPRINT(DBG_INFO, F("TX "));
@ -136,14 +100,15 @@ class CmtRadio {
}
if(isRetransmit)
mStat->retransmits++;
iv->radioStatistics.retransmits++;
else
mStat->txCnt++;
iv->radioStatistics.txCnt++;
}
std::queue<hmsPacket_t> mBufCtrl;
uint64_t getIvId(Inverter<> *iv) {
return iv->radioId.u64;
}
private:
inline void reset(bool genDtuSn) {
if(genDtuSn)
generateDtuSn();
@ -160,7 +125,7 @@ class CmtRadio {
mRqstGetRx = false;
}
inline void sendSwitchChCmd(const uint64_t *ivId, uint8_t ch) {
inline void sendSwitchChCmd(Inverter<> *iv, uint8_t ch) {
/** ch:
* 0x00: 860.00 MHz
* 0x01: 860.25 MHz
@ -170,51 +135,25 @@ class CmtRadio {
* ...
* 0x28: 870.00 MHz
* */
initPacket(ivId, 0x56, 0x02);
initPacket(iv->radioId.u64, 0x56, 0x02);
mTxBuf[10] = 0x15;
mTxBuf[11] = 0x21;
mTxBuf[12] = ch;
mTxBuf[13] = 0x14;
sendPacket(14, false);
sendPacket(iv, 14, false);
mRqstGetRx = true;
}
void initPacket(const uint64_t *ivId, uint8_t mid, uint8_t pid) {
mTxBuf[0] = mid;
CP_U32_BigEndian(&mTxBuf[1], (*ivId) >> 8);
CP_U32_LittleEndian(&mTxBuf[5], mDtuSn);
mTxBuf[9] = pid;
memset(&mTxBuf[10], 0x00, 17);
}
inline void generateDtuSn(void) {
uint32_t chipID = 0;
#ifdef ESP32
uint64_t MAC = ESP.getEfuseMac();
chipID = ((MAC >> 8) & 0xFF0000) | ((MAC >> 24) & 0xFF00) | ((MAC >> 40) & 0xFF);
#endif
mDtuSn = 0x80000000; // the first digit is an 8 for DTU production year 2022, the rest is filled with the ESP chipID in decimal
for(int i = 0; i < 7; i++) {
mDtuSn |= (chipID % 10) << (i * 4);
chipID /= 10;
}
}
inline void getRx(void) {
hmsPacket_t p;
uint8_t status = mCmt.getRx(p.data, 28, &p.rssi);
packet_t p;
uint8_t status = mCmt.getRx(p.packet, &p.len, 28, &p.rssi);
if(CMT_SUCCESS == status)
mBufCtrl.push(p);
}
CmtType mCmt;
uint32_t mDtuSn;
uint8_t mTxBuf[27];
bool mSerialDebug;
bool mIrqRcvd;
bool mRqstGetRx;
bool mCmtAvail;
statistics_t *mStat;
};
#endif /*__HMS_RADIO_H__*/

2
src/plugins/Display/Display.h

@ -135,7 +135,7 @@ class Display {
}
#if defined(ESP32)
else if (mCfg->type == 10) {
mEpaper.loop(totalPower, totalYieldDay, totalYieldTotal, nrprod);
mEpaper.loop(((allOff) ? 0.0 : totalPower), totalYieldDay, totalYieldTotal, nrprod);
mRefreshCycle++;
}

12
src/utils/dbg.h

@ -148,7 +148,7 @@
#define PVERBLN(str)
#endif
#define DPRINT(level, str) ({\
#define DPRINT(level, str) do {\
switch(level) {\
case DBG_ERROR: PERR(str); break; \
case DBG_WARN: PWARN(str); break; \
@ -156,13 +156,13 @@
case DBG_DEBUG: PDBG(str); break; \
default: PVERB(str); break; \
}\
})
} while (0)
#define DPRINT_IVID(level, id) ({\
#define DPRINT_IVID(level, id) do {\
DPRINT(level, F("(#")); DBGPRINT(String(id)); DBGPRINT(F(") "));\
})
} while (0)
#define DPRINTLN(level, str) ({\
#define DPRINTLN(level, str) do {\
switch(level) {\
case DBG_ERROR: PERRLN(str); break; \
case DBG_WARN: PWARNLN(str); break; \
@ -170,7 +170,7 @@
case DBG_DEBUG: PDBGLN(str); break; \
default: PVERBLN(str); break; \
}\
})
} while (0)
/*class ahoyLog {

8
src/utils/helper.h

@ -20,21 +20,21 @@ static Timezone gTimezone(CEST, CET);
#define CHECK_MASK(a,b) ((a & b) == b)
#define CP_U32_LittleEndian(buf, v) ({ \
#define CP_U32_LittleEndian(buf, v) do { \
uint8_t *b = buf; \
b[0] = ((v >> 24) & 0xff); \
b[1] = ((v >> 16) & 0xff); \
b[2] = ((v >> 8) & 0xff); \
b[3] = ((v ) & 0xff); \
})
} while (0)
#define CP_U32_BigEndian(buf, v) ({ \
#define CP_U32_BigEndian(buf, v) do { \
uint8_t *b = buf; \
b[3] = ((v >> 24) & 0xff); \
b[2] = ((v >> 16) & 0xff); \
b[1] = ((v >> 8) & 0xff); \
b[0] = ((v ) & 0xff); \
})
} while (0)
namespace ah {
void ip2Arr(uint8_t ip[], const char *ipStr);

31
src/web/RestApi.h

@ -107,6 +107,8 @@ class RestApi {
getIvAlarms(root, request->url().substring(20).toInt());
else if(path.substring(0, 17) == "inverter/version/")
getIvVersion(root, request->url().substring(22).toInt());
else if(path.substring(0, 19) == "inverter/radiostat/")
getIvStatistis(root, request->url().substring(24).toInt());
else
getNotFound(root, F("http://") + request->host() + F("/api/"));
}
@ -245,7 +247,6 @@ class RestApi {
getRadioCmtInfo(obj.createNestedObject(F("radioCmt")));
#endif
getMqttInfo(obj.createNestedObject(F("mqtt")));
getStatistics(obj.createNestedArray(F("statistics")));
#if defined(ESP32)
obj[F("chip_revision")] = ESP.getChipRevision();
@ -310,23 +311,19 @@ class RestApi {
obj[F("html")] = F("reboot. Autoreload after 10 seconds");
}
void getStatistics(JsonArray arr) {
statistics_t *stat;
#if defined(ESP32)
for(uint8_t i = 0; i < 2; i++) {
stat = (0 == i) ? mApp->getNrfStatistics() : mApp->getCmtStatistics();
#else
{
stat = mApp->getNrfStatistics();
#endif
JsonObject obj = arr.createNestedObject();
obj[F("rx_success")] = stat->rxSuccess;
obj[F("rx_fail")] = stat->rxFail;
obj[F("rx_fail_answer")] = stat->rxFailNoAnser;
obj[F("frame_cnt")] = stat->frmCnt;
obj[F("tx_cnt")] = stat->txCnt;
obj[F("retransmits")] = stat->retransmits;
void getIvStatistis(JsonObject obj, uint8_t id) {
Inverter<> *iv = mSys->getInverterByPos(id);
if(NULL == iv) {
obj[F("error")] = F("inverter not found!");
return;
}
obj[F("name")] = String(iv->config->name);
obj[F("rx_success")] = iv->radioStatistics.rxSuccess;
obj[F("rx_fail")] = iv->radioStatistics.rxFail;
obj[F("rx_fail_answer")] = iv->radioStatistics.rxFailNoAnser;
obj[F("frame_cnt")] = iv->radioStatistics.frmCnt;
obj[F("tx_cnt")] = iv->radioStatistics.txCnt;
obj[F("retransmits")] = iv->radioStatistics.retransmits;
}
void getInverterList(JsonObject obj) {

9
src/web/html/api.js

@ -172,6 +172,15 @@ function getAjax(url, ptr, method="GET", json=null) {
* CREATE DOM FUNCTIONS
*/
function tr(val1, val2) {
if(typeof val2 == "number")
val2 = String(val2);
return ml("tr", {}, [
ml("th", {style: "width: 50%"}, val1),
ml("td", {}, val2)
]);
}
function des(val) {
e = document.createElement('p');
e.classList.add("subdes");

22
src/web/html/system.html

@ -48,26 +48,6 @@
return ml("div", {class: "head p-2 mt-3"}, ml("div", {class: "row"}, ml("div", {class: "col a-c"}, text)))
}
function tr(val1, val2) {
if(typeof val2 == "number")
val2 = String(val2);
return ml("tr", {}, [
ml("th", {style: "width: 50%"}, val1),
ml("td", {}, val2)
]);
}
function parseStat(stat) {
return [
tr("TX count", stat.tx_cnt),
tr("RX success", stat.rx_success),
tr("RX fail", stat.rx_fail),
tr("RX no answer", stat.rx_fail_answer),
tr("RX fragments", stat.frame_cnt),
tr("TX retransmits", stat.retransmits)
];
}
function parseRadio(obj) {
const pa = ["MIN (recommended)", "LOW", "HIGH", "MAX"];
const dr = ["1 M", "2 M", "250 k"]
@ -78,7 +58,6 @@
tr("NRF24 Power Level", pa[obj.radioNrf.power_level]),
tr("NRF24 Data Rate", dr[obj.radioNrf.dataRate] + "bps")
];
Array.prototype.push.apply(lines, parseStat(obj.statistics[0]));
} else
lines = [tr("NRF24L01", badge(false, "not enabled"))];
@ -92,7 +71,6 @@
/*IF_ESP32*/
if(obj.radioCmt.en) {
cmt = [tr("CMT2300A", badge(obj.radioCmt.isconnected, ((obj.radioCmt.isconnected) ? "" : "not ") + "connected"))];
Array.prototype.push.apply(cmt, parseStat(obj.statistics[1]));
} else
cmt = [tr("CMT2300A", badge(false, "not enabled"))];

59
src/web/html/visualization.html

@ -178,20 +178,20 @@
]);
}
function tsInfo(ts, gen, rssi) {
function tsInfo(obj) {
var ageInfo = "Last received data requested at: ";
if(ts > 0) {
var date = new Date(ts * 1000);
if(obj.ts_last_success > 0) {
var date = new Date(obj.ts_last_success * 1000);
ageInfo += toIsoDateStr(date);
}
else
ageInfo += "nothing received";
if(rssi > -127) {
if(gen < 2)
ageInfo += " (RSSI: " + ((rssi == -64) ? ">=" : "<") + " -64dBm)";
if(obj.rssi > -127) {
if(obj.generation < 2)
ageInfo += " (RSSI: " + ((obj.rssi == -64) ? ">=" : "<") + " -64dBm)";
else
ageInfo += " (RSSI: " + rssi + "dBm)";
ageInfo += " (RSSI: " + obj.rssi + "dBm)";
}
return ml("div", {class: "mb-5"}, [
@ -199,7 +199,9 @@
ml("div", {class: "col"}, "")
),
ml("div", {class: "row p-2 ts-bg mx-2"},
ml("div", {class: "col mx-2"}, ageInfo)
ml("div", { class: "pointer col mx-2", onclick: function() {
getAjax("/api/inverter/radiostat/" + obj.id, parseIvRadioStats);
}}, ageInfo)
)
]);
}
@ -219,7 +221,7 @@
ml("div", {}, [
ivHead(obj),
ml("div", {class: "row mb-2"}, chn),
tsInfo(obj.ts_last_success, obj.generation, obj.rssi)
tsInfo(obj)
])
);
@ -280,31 +282,30 @@
var html = ml("table", {class: "table"}, [
ml("tbody", {}, [
ml("tr", {}, [
ml("th", {}, "Model"),
ml("td", {}, model)
]),
ml("tr", {}, [
ml("th", {}, "Firmware Version / Build"),
ml("td", {}, String(obj.fw_ver) + " (build: " + String(obj.fw_date) + " " + String(obj.fw_time) + ")")
]),
ml("tr", {}, [
ml("th", {}, "Hardware Version / Build"),
ml("td", {}, (obj.hw_ver/100).toFixed(2) + " (build: " + String(obj.prod_cw) + "/" + String(obj.prod_year) + ")")
]),
ml("tr", {}, [
ml("th", {}, "Hardware Number"),
ml("td", {}, obj.part_num.toString(16))
]),
ml("tr", {}, [
ml("th", {}, "Bootloader Version"),
ml("td", {}, (obj.boot_ver/100).toFixed(2))
])
tr("Model", model),
tr("Firmware Version / Build", String(obj.fw_ver) + " (build: " + String(obj.fw_date) + " " + String(obj.fw_time) + ")"),
tr("Hardware Version / Build", (obj.hw_ver/100).toFixed(2) + " (build: " + String(obj.prod_cw) + "/" + String(obj.prod_year) + ")"),
tr("Hardware Number", obj.part_num.toString(16)),
tr("Bootloader Version", (obj.boot_ver/100).toFixed(2))
])
]);
modal("Info for inverter " + obj.name, ml("div", {}, html));
}
function parseIvRadioStats(obj) {
var html = ml("table", {class: "table"}, [
ml("tbody", {}, [
tr("TX count", obj.tx_cnt),
tr("RX success", obj.rx_success),
tr("RX fail", obj.rx_fail),
tr("RX no answer", obj.rx_fail_answer),
tr("RX fragments", obj.frame_cnt),
tr("TX retransmits", obj.retransmits)
])
]);
modal("Radio statistics for inverter " + obj.name, ml("div", {}, html));
}
function parse(obj) {
if(null != obj) {
parseGeneric(obj["generic"]);

2
src/web/web.h

@ -183,8 +183,8 @@ class Web {
mUploadFp.write(data, len);
if (final) {
mUploadFp.close();
char pwd[PWD_LEN];
#if !defined(ETHERNET)
char pwd[PWD_LEN];
strncpy(pwd, mConfig->sys.stationPwd, PWD_LEN); // backup WiFi PWD
#endif
if (!mApp->readSettings("/tmp.json")) {

Loading…
Cancel
Save