diff --git a/src/CHANGES.md b/src/CHANGES.md index 83917be9f..95fcae663 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -3,6 +3,8 @@ ## 0.8.28 - 2023-12-23 * fix bug heuristic * add version information to clipboard once 'copy' was clicked +* add get loss rate @rejoe2 +* improve communication @rejoe2 ## 0.8.27 - 2023-12-18 * fix set power limit #1276 diff --git a/src/hm/CommQueue.h b/src/hm/CommQueue.h index 37ffda62a..bb815a7e8 100644 --- a/src/hm/CommQueue.h +++ b/src/hm/CommQueue.h @@ -94,8 +94,8 @@ class CommQueue { mQueue[mRdPtr].attempts--; } - void incrAttempt(void) { - mQueue[mRdPtr].attempts++; + void incrAttempt(uint8_t attempts = 1) { + mQueue[mRdPtr].attempts += attempts; } void inc(uint8_t *ptr) { diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 232549227..818b396dd 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -14,7 +14,7 @@ #define MI_TIMEOUT 250 // timeout for MI type requests #define FRSTMSG_TIMEOUT 150 // how long to wait for first msg to be received #define DEFAULT_TIMEOUT 500 // timeout for regular requests -#define SINGLEFR_TIMEOUT 65 // timeout for single frame requests +#define SINGLEFR_TIMEOUT 100 // timeout for single frame requests #define MAX_BUFFER 250 typedef std::function *)> payloadListenerType; @@ -59,7 +59,7 @@ class Communication : public CommQueue<> { mLastEmptyQueueMillis = millis(); mPrintSequenceDuration = true; - uint16_t timeout = (q->iv->ivGen == IV_MI) ? MI_TIMEOUT : ((q->iv->mGotFragment && q->iv->mGotLastMsg) || mIsRetransmit) ? SINGLEFR_TIMEOUT : DEFAULT_TIMEOUT; + uint16_t timeout = (q->iv->ivGen == IV_MI) ? MI_TIMEOUT : (((q->iv->mGotFragment && q->iv->mGotLastMsg) || mIsRetransmit) ? SINGLEFR_TIMEOUT : ((q->cmd != AlarmData) ? DEFAULT_TIMEOUT : (1.5 * DEFAULT_TIMEOUT))); uint16_t timeout_min = (q->iv->ivGen == IV_MI) ? MI_TIMEOUT : ((q->iv->mGotFragment || mIsRetransmit)) ? SINGLEFR_TIMEOUT : FRSTMSG_TIMEOUT; /*if(mDebugState != mState) { @@ -85,6 +85,8 @@ class Communication : public CommQueue<> { mIsRetransmit = false; if(NULL == q->iv->radio) cmdDone(false); // can't communicate while radio is not defined! + q->iv->mCmd = q->cmd; + q->iv->mIsSingleframeReq = false; mState = States::START; break; @@ -159,8 +161,9 @@ class Communication : public CommQueue<> { closeRequest(q, false); break; } - mIsRetransmit = false; mFirstTry = false; // for correct reset + if((IV_MI != q->iv->ivGen) || (0 == q->attempts)) + mIsRetransmit = false; while(!q->iv->radio->mBufCtrl.empty()) { packet_t *p = &q->iv->radio->mBufCtrl.front(); @@ -168,6 +171,7 @@ class Communication : public CommQueue<> { if(validateIvSerial(&p->packet[1], q->iv)) { q->iv->radioStatistics.frmCnt++; + q->iv->mDtuRxCnt++; if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command if(parseFrame(p)) @@ -197,15 +201,25 @@ class Communication : public CommQueue<> { if(q->iv->ivGen != IV_MI) { mState = States::CHECK_PACKAGE; } else { + bool fastNext = true; if(q->iv->miMultiParts < 6) { mState = States::WAIT; + if((millis() > mWaitTimeout && mIsRetransmit) || !mIsRetransmit) { + miRepeatRequest(q); + return; + } } else { + mHeu.evalTxChQuality(q->iv, true, (4 - q->attempts), q->iv->curFrmCnt); if(((q->cmd == 0x39) && (q->iv->type == INV_TYPE_4CH)) || ((q->cmd == MI_REQ_CH2) && (q->iv->type == INV_TYPE_2CH)) || ((q->cmd == MI_REQ_CH1) && (q->iv->type == INV_TYPE_1CH))) { miComplete(q->iv); + fastNext = false; } - closeRequest(q, true); + if(fastNext) + miNextRequest(q->iv->type == INV_TYPE_4CH ? MI_REQ_4CH : MI_REQ_CH1, q); + else + closeRequest(q, true); } } @@ -247,6 +261,8 @@ class Communication : public CommQueue<> { DBGPRINT(String(q->attempts)); DBGPRINTLN(F(" attempts left)")); } + if (!mIsRetransmit) + q->iv->mIsSingleframeReq = true; sendRetransmit(q, (framnr-1)); mIsRetransmit = true; mlastTO_min = timeout_min; @@ -290,7 +306,9 @@ class Communication : public CommQueue<> { else ah::dumpBuf(p->packet, p->len); } else { - DBGPRINT(F("| ")); + DBGPRINT(F("| 0x")); + DHEX(p->packet[0]); + DBGPRINT(F(" ")); DBGHEXLN(p->packet[9]); } } @@ -331,8 +349,11 @@ class Communication : public CommQueue<> { return false; // CRC8 is wrong, frame invalid } - if((*frameId & ALL_FRAMES) == ALL_FRAMES) + if((*frameId & ALL_FRAMES) == ALL_FRAMES) { mMaxFrameId = (*frameId & 0x7f); + if(mMaxFrameId > 8) // large payloads, e.g. AlarmData + incrAttempt(mMaxFrameId - 6); + } frame_t *f = &mLocalBuf[(*frameId & 0x7f) - 1]; memcpy(f->buf, &p->packet[10], p->len-11); @@ -373,7 +394,7 @@ class Communication : public CommQueue<> { accepted = false; DPRINT_IVID(DBG_INFO, q->iv->id); - DBGPRINT(F(" has ")); + DBGPRINT(F("has ")); if(!accepted) DBGPRINT(F("not ")); DBGPRINT(F("accepted power limit set point ")); DBGPRINT(String(q->iv->powerLimit[0])); @@ -446,8 +467,14 @@ class Communication : public CommQueue<> { record_t<> *rec = q->iv->getRecordStruct(q->cmd); if(NULL == rec) { - DPRINTLN(DBG_ERROR, F("record is NULL!")); - closeRequest(q, false); + if(GetLossRate == q->cmd) { + q->iv->parseGetLossRate(mPayload, len); + //closeRequest(q, true); //@lumapu: Activating would crash most esp's! + return; + } else { + DPRINTLN(DBG_ERROR, F("record is NULL!")); + closeRequest(q, false); + } return; } if((rec->pyldLen != len) && (0 != rec->pyldLen)) { @@ -688,6 +715,7 @@ class Communication : public CommQueue<> { miStsConsolidate(q, datachan, rec, p->packet[23], p->packet[24]); if (p->packet[0] < (0x39 + ALL_FRAMES) ) { + mHeu.evalTxChQuality(q->iv, true, (4 - q->attempts), 1); miNextRequest((p->packet[0] - ALL_FRAMES + 1), q); } else { q->iv->miMultiParts = 7; // indicate we are ready @@ -696,6 +724,7 @@ class Communication : public CommQueue<> { } else if((p->packet[0] == (MI_REQ_CH1 + ALL_FRAMES)) && (q->iv->type == INV_TYPE_2CH)) { //addImportant(q->iv, MI_REQ_CH2); miNextRequest(MI_REQ_CH2, q); + mHeu.evalTxChQuality(q->iv, true, (4 - q->attempts), q->iv->curFrmCnt); //use also miMultiParts here for better statistics? //mHeu.setGotFragment(q->iv); } else { // first data msg for 1ch, 2nd for 2ch @@ -732,6 +761,24 @@ class Communication : public CommQueue<> { //mState = States::WAIT; } + void miRepeatRequest(const queue_s *q) { + setAttempt(); // if function is called, we got something, and we necessarily need more transmissions for MI types... + if(*mSerialDebug) { + DPRINT_IVID(DBG_WARN, q->iv->id); + DBGPRINT(F("resend request (")); + DBGPRINT(String(q->attempts)); + DBGPRINT(F(" attempts left): 0x")); + DBGHEXLN(q->cmd); + } + + q->iv->radio->sendCmdPacket(q->iv, q->cmd, 0x00, true); + + mWaitTimeout = millis() + MI_TIMEOUT; + mWaitTimeout_min = mWaitTimeout; + //mState = States::WAIT; + mIsRetransmit = false; + } + void miStsConsolidate(const queue_s *q, uint8_t stschan, record_t<> *rec, uint8_t uState, uint8_t uEnum, uint8_t lState = 0, uint8_t lEnum = 0) { //uint8_t status = (p->packet[11] << 8) + p->packet[12]; uint16_t statusMi = 3; // regular status for MI, change to 1 later? diff --git a/src/hm/hmDefines.h b/src/hm/hmDefines.h index c00f4940b..552592895 100644 --- a/src/hm/hmDefines.h +++ b/src/hm/hmDefines.h @@ -139,6 +139,8 @@ const byteAssign_t AlarmDataAssignment[] = { #define HMALARMDATA_PAYLOAD_LEN 0 // 0: means check is off #define ALARM_LOG_ENTRY_SIZE 12 +#define HMGETLOSSRATE_PAYLOAD_LEN 4 +#define AHOY_GET_LOSS_INTERVAL 10 //------------------------------------- // HM300, HM350, HM400 diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 66496340b..2ae60e107 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -133,12 +133,20 @@ class Inverter { bool mGotFragment; // shows if inverter has sent at least one fragment uint8_t curFrmCnt; // count received frames in current loop bool mGotLastMsg; // shows if inverter has already finished transmission cycle + uint8_t mCmd; // holds the command to send + bool mIsSingleframeReq; // indicates this is a missing single frame request Radio *radio; // pointer to associated radio class statistics_t radioStatistics; // information about transmitted, failed, ... packets HeuristicInv heuristics; // heuristic information / logic uint8_t curCmtFreq; // current used CMT frequency, used to check if freq. was changed during runtime bool commEnabled; // 'pause night communication' sets this field to false + uint16_t mIvRxCnt; // last iv rx frames (from GetLossRate) + uint16_t mIvTxCnt; // last iv tx frames (from GetLossRate) + uint16_t mDtuRxCnt; // cur dtu rx frames (since last GetLossRate) + uint16_t mDtuTxCnt; // cur dtu tx frames (since last getLoassRate) + uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debu + static uint32_t *timestamp; // system timestamp static cfgInst_t *generalConfig; // general inverter configuration from setup @@ -159,8 +167,14 @@ class Inverter { rssi = -127; miMultiParts = 0; mGotLastMsg = false; + mCmd = InitDataState; + mIsSingleframeReq = false; radio = NULL; commEnabled = true; + mIvRxCnt = 0; + mIvTxCnt = 0; + mDtuRxCnt = 0; + mDtuTxCnt = 0; memset(&radioStatistics, 0, sizeof(statistics_t)); memset(heuristics.txRfQuality, -6, 5); @@ -174,6 +188,7 @@ class Inverter { cb(devControlCmd, true); mDevControlRequest = false; } else if (IV_MI != ivGen) { + mGetLossInterval++; if((alarmLastId != alarmMesIndex) && (alarmMesIndex != 0)) cb(AlarmData, false); // get last alarms else if(0 == getFwVersion()) @@ -185,8 +200,12 @@ class Inverter { else if(InitDataState != devControlCmd) { cb(devControlCmd, false); // custom command which was received by API devControlCmd = InitDataState; + mGetLossInterval = 1; } else if((0 == mGridLen) && generalConfig->readGrid) { // read grid profile cb(GridOnProFilePara, false); + } else if (mGetLossInterval > AHOY_GET_LOSS_INTERVAL) { // get loss rate + mGetLossInterval = 1; + cb(GetLossRate, false); } else cb(RealTimeRunData_Debug, false); // get live data } else { @@ -574,6 +593,29 @@ class Inverter { memset(mLastYD, 0, sizeof(float) * 6); } + bool parseGetLossRate(uint8_t pyld[], uint8_t len) { + if (len == HMGETLOSSRATE_PAYLOAD_LEN) { + uint16_t rxCnt = (pyld[0] << 8) + pyld[1]; + uint16_t txCnt = (pyld[2] << 8) + pyld[3]; + + if (mIvRxCnt || mIvTxCnt) { // there was successful GetLossRate in the past + DPRINT_IVID(DBG_INFO, id); + DBGPRINTLN("Inv loss: " + + String (mDtuTxCnt - (rxCnt - mIvRxCnt)) + " of " + + String (mDtuTxCnt) + ", DTU loss: " + + String (txCnt - mIvTxCnt - mDtuRxCnt) + " of " + + String (txCnt - mIvTxCnt)); + } + + mIvRxCnt = rxCnt; + mIvTxCnt = txCnt; + mDtuRxCnt = 0; // start new interval + mDtuTxCnt = 0; // start new interval + return true; + } + return false; + } + uint16_t parseAlarmLog(uint8_t id, uint8_t pyld[], uint8_t len) { uint8_t startOff = 2 + id * ALARM_LOG_ENTRY_SIZE; if((startOff + ALARM_LOG_ENTRY_SIZE) > len) diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index dd83544fa..6db39d7f6 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -119,9 +119,11 @@ class HmRadio : public Radio { if(NULL == mLastIv) // prevent reading on NULL object! return; - uint32_t startMicros = micros(); - uint32_t loopMillis = millis(); - while ((millis() - loopMillis) < 400) { + uint32_t startMicros = micros(); + uint32_t loopMillis = millis(); + uint32_t outerLoopTimeout = (mLastIv->mIsSingleframeReq) ? 100 : ((mLastIv->mCmd != AlarmData) ? 400 : 600); + + while ((millis() - loopMillis) < outerLoopTimeout) { while ((micros() - startMicros) < 5110) { // listen (4088us or?) 5110us to each channel if (mIrqRcvd) { mIrqRcvd = false; @@ -304,8 +306,14 @@ class HmRadio : public Radio { ah::dumpBuf(mTxBuf, len, 1, 4); else ah::dumpBuf(mTxBuf, len); - } else + } else { + DBGPRINT(F("0x")); + DHEX(mTxBuf[0]); + DBGPRINT(F(" 0x")); + DHEX(mTxBuf[10]); + DBGPRINT(F(" ")); DBGHEXLN(mTxBuf[9]); + } } mNrf24->stopListening(); @@ -315,6 +323,7 @@ class HmRadio : public Radio { mMillis = millis(); mLastIv = iv; + iv->mDtuTxCnt++; } uint64_t getIvId(Inverter<> *iv) { diff --git a/src/hms/hmsRadio.h b/src/hms/hmsRadio.h index 402000857..80a2ecd92 100644 --- a/src/hms/hmsRadio.h +++ b/src/hms/hmsRadio.h @@ -95,8 +95,13 @@ class CmtRadio : public Radio { ah::dumpBuf(mTxBuf, len, 1, 4); else ah::dumpBuf(mTxBuf, len); - } else + } else { + DBGPRINT(F("0x")); + DHEX(mTxBuf[0]); + DBGPRINT(F(" 0x")); + DHEX(mTxBuf[10]); DBGHEXLN(mTxBuf[9]); + } } uint8_t status = mCmt.tx(mTxBuf, len); @@ -107,6 +112,7 @@ class CmtRadio : public Radio { if(CMT_ERR_RX_IN_FIFO == status) mIrqRcvd = true; } + iv->mDtuTxCnt++; } uint64_t getIvId(Inverter<> *iv) { diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 0ac6d3069..3600e22c0 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -127,6 +127,11 @@ class PubMqttIvData { void stateSend() { record_t<> *rec = mIv->getRecordStruct(mCmd); + if(rec == NULL) { + if (mCmd != GetLossRate) + DPRINT(DBG_WARN, "unknown record to publish!"); + return; + } uint32_t lastTs = mIv->getLastTs(rec); bool pubData = (lastTs > 0); if (mCmd == RealTimeRunData_Debug) diff --git a/src/web/html/serial.html b/src/web/html/serial.html index 966ffecd6..ef7aa4c3e 100644 --- a/src/web/html/serial.html +++ b/src/web/html/serial.html @@ -48,6 +48,8 @@ exeOnce = false; setTimeOffset(); } + version = obj.version; + build = obj.build; } function setTimeOffset() {