From 03b295b6b80063945b57776b00f1a65154bb85f3 Mon Sep 17 00:00:00 2001 From: rejoe2 Date: Thu, 28 Sep 2023 09:59:02 +0200 Subject: [PATCH] MI command review Payload now will be build according to xlsx, but note: this seems not to work at least for the MI-600 used for testing... - non-persistent relative will treated as short percentage messages (may need review, if multiplying by 10 is needed!) - non-persistent absolute will treated same as "as is" in DTUSimMI, so always "10%" will be filled as percentage (this may not work for all type of MI) - persistent will be handled as DRED command, allowing only a few presets. So use absolute values, these will be translated as follows: -- 0x55AA Boot without DRM restrictions (value higher than double of inverter max power (IMP)) -- 0xA5A5 DRM0 shutdown (not supported yet) -- 0x5A5A DRM5 power limit 0% (values lower than 25% of IMP) -- 0xAA55 DRM6 power limit 50% (values between 25% and 75% of IMP) -- 0x5A55 DRM8 unlimited power operation (values between 75% and 100% of IMP) --- src/hm/hmInverter.h | 4 +- src/hm/hmRadio.h | 66 +++++++++++++++++------ src/hm/miPayload.h | 124 ++++++++++++++++++++++++++++++-------------- 3 files changed, 136 insertions(+), 58 deletions(-) diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 54355da5..7ee8da5e 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -142,7 +142,7 @@ class Inverter { uint8_t channels; // number of PV channels (1-4) record_t recordMeas; // structure for measured values record_t recordInfo; // structure for info values - record_t recordHwInfo; // structure for simple (hardware) info values + record_t recordHwInfo; // structure for simple (hardware) info values record_t recordConfig; // structure for system config values record_t 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 +152,7 @@ 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; // accurate for HMS and HMT inverters only static uint32_t *timestamp; // system timestamp diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index 870ef457..e20444b0 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -169,7 +169,7 @@ class HmRadio { mSerialDebug = true; } - void sendControlPacket(uint64_t invId, uint8_t cmd, uint16_t *data, bool isRetransmit, bool isNoMI = true, bool is4chMI = false) { + void sendControlPacket(uint64_t invId, uint8_t cmd, uint16_t *data, bool isRetransmit, bool isNoMI = true, uint16_t powerMax = 0) { DPRINT(DBG_INFO, F("sendControlPacket cmd: 0x")); DBGHEXLN(cmd); initPacket(invId, TX_REQ_DEVCONTROL, SINGLE_FRAME); @@ -185,6 +185,7 @@ class HmRadio { } } else { //MI 2nd gen. specific switch (cmd) { + case Restart: case TurnOn: //mTxBuf[0] = 0x50; mTxBuf[9] = 0x55; @@ -195,21 +196,54 @@ class HmRadio { mTxBuf[10] = 0x55; break; case ActivePowerContr: - mTxBuf[9] = 0x5a; - mTxBuf[10] = 0x5a; - //Testing only! Original NRF24_DTUMIesp.ino code #L612-L613: - //UsrData[0]=0x5A;UsrData[1]=0x5A;UsrData[2]=100;//0x0a;// 10% limit - //UsrData[3]=((Limit*10) >> 8) & 0xFF; UsrData[4]= (Limit*10) & 0xFF; //WR needs 1 dec= zB 100.1 W - if (is4chMI) { - mTxBuf[cnt++] = 100; //10% limit, seems to be necessary to send sth. at all, but for MI-1500 this has no effect - //works (if ever!) only for absulute power limits! - mTxBuf[cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit - mTxBuf[cnt++] = ((data[0] * 10) ) & 0xff; // power limit - } else { - mTxBuf[cnt++] = data[0]*10; // power limit + if (data[1]<256) { // non persistent + mTxBuf[9] = 0x5a; + mTxBuf[10] = 0x5a; + //Testing only! Original NRF24_DTUMIesp.ino code #L612-L613: + //UsrData[0]=0x5A;UsrData[1]=0x5A;UsrData[2]=100;//0x0a;// 10% limit + //UsrData[3]=((Limit*10) >> 8) & 0xFF; UsrData[4]= (Limit*10) & 0xFF; //WR needs 1 dec= zB 100.1 W + if (!data[1]) { // AbsolutNonPersistent + mTxBuf[++cnt] = 100; //10% limit, seems to be necessary to send sth. at all, but for MI-1500 this has no effect + //works (if ever!) only for absulute power limits! + mTxBuf[++cnt] = ((data[0] * 10) >> 8) & 0xff; // power limit in W + mTxBuf[++cnt] = ((data[0] * 10) ) & 0xff; // power limit in W + } else if (powerMax) { //relative, but 4ch-MI (if ever) only accepts absolute values + mTxBuf[++cnt] = data[0]; // simple power limit in %, might be necessary to multiply by 10? + mTxBuf[++cnt] = ((data[0] * 10 * powerMax) >> 8) & 0xff; // power limit + mTxBuf[++cnt] = ((data[0] * 10 * powerMax) ) & 0xff; // power limit + } else { // might work for 1/2ch MI (if ever) + mTxBuf[++cnt] = data[0]; // simple power limit in %, might be necessary to multiply by 10? + } + } else { // persistent power limit needs to be translated in DRED command (?) + /* DRED instruction + Order Function + 0x55AA Boot without DRM restrictions + 0xA5A5 DRM0 shutdown + 0x5A5A DRM5 power limit 0% + 0xAA55 DRM6 power limit 50% + 0x5A55 DRM8 unlimited power operation + */ + mTxBuf[0] = 0x50; + + if (data[1] == 256UL) { // AbsolutPersistent + if (data[0] == 0 && !powerMax) { + mTxBuf[9] = 0xa5; + mTxBuf[10] = 0xa5; + } else if (data[0] == 0 || !powerMax || data[0] < powerMax/4 ) { + mTxBuf[9] = 0x5a; + mTxBuf[10] = 0x5a; + } else if (data[0] <= powerMax/4*3) { + mTxBuf[9] = 0xaa; + mTxBuf[10] = 0x55; + } else if (data[0] <= powerMax) { + mTxBuf[9] = 0x5a; + mTxBuf[10] = 0x55; + } else if (data[0] > powerMax*2) { + mTxBuf[9] = 0x55; + mTxBuf[10] = 0xaa; + } + } } - - break; default: return; @@ -254,7 +288,7 @@ class HmRadio { bool mSerialDebug; private: - bool getReceived(void) { + bool getReceived(void) { bool tx_ok, tx_fail, rx_ready; mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // resets the IRQ pin to HIGH diff --git a/src/hm/miPayload.h b/src/hm/miPayload.h index 716ea019..800cc88c 100644 --- a/src/hm/miPayload.h +++ b/src/hm/miPayload.h @@ -26,6 +26,7 @@ typedef struct { uint8_t invId; uint8_t retransmits; bool gotFragment; + bool gotGPF; uint8_t rtrRes; // for limiting resets uint8_t multi_parts; // for quality bool rxTmo; @@ -49,7 +50,8 @@ class MiPayload { mTimestamp = timestamp; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { reset(i, false, true); - mPayload[i].limitrequested = true; + mPayload[i].limitrequested = false; + mPayload[i].gotGPF = false; } mSerialDebug = false; mHighPrioIv = NULL; @@ -124,10 +126,12 @@ class MiPayload { DBGPRINT(F("Devcontrol request 0x")); DHEX(iv->devControlCmd); DBGPRINT(F(" power limit ")); - DBGPRINTLN(String(iv->powerLimit[0])); + DBGPRINT(String(iv->powerLimit[0])); + DBGPRINT(F(" with PowerLimitControl ")); + DBGPRINTLN(String(iv->powerLimit[1])); } iv->powerLimitAck = false; - mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false, false, iv->type == INV_TYPE_4CH); + mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false, false, iv->powerLimit[1] == RelativNonPersistent ? 0 : iv->getMaxPower()); // might be dependent on IV-Type as well, iv->type == INV_TYPE_4CH? mPayload[iv->id].txCmd = iv->devControlCmd; mPayload[iv->id].limitrequested = true; @@ -136,17 +140,18 @@ class MiPayload { uint8_t cmd = iv->getQueuedCmd(); uint8_t cmd2 = cmd; if ( cmd == SystemConfigPara ) { //0x05 for HM-types - if (!mPayload[iv->id].limitrequested) { // only do once at startup + if (mPayload[iv->id].gotGPF) { iv->setQueuedCmdFinished(); cmd = iv->getQueuedCmd(); - } else { - mPayload[iv->id].limitrequested = false; } } - if (cmd == 0x01 || cmd == SystemConfigPara ) { //0x1 and 0x05 for HM-types - cmd2 = cmd == SystemConfigPara ? 0x01 : 0x00; //perhaps we can only try to get second frame? - cmd = 0x0f; // for MI, these seem to make part of polling the device software and hardware version number command + if (cmd == 0x01) { //0x1 for HM-types + cmd2 = 0x00; + cmd = 0x0f; // for MI, these seem to make part of polling the device software and hardware version number command + } else if (cmd == SystemConfigPara ) { // 0x05 for HM-types + cmd2 = 0x00; + cmd = 0x10; // legacy GPF request } if (mSerialDebug) { DPRINT_IVID(DBG_INFO, iv->id); @@ -177,21 +182,24 @@ class MiPayload { else if (p->packet[0] == (0x11 + SINGLE_FRAME)) { // 0x92; MI status response to 0x11 miStsDecode(iv, p, CH2); - } - else if ( p->packet[0] == 0x09 + ALL_FRAMES || + } else if ( p->packet[0] == 0x09 + ALL_FRAMES || p->packet[0] == 0x11 + ALL_FRAMES || ( p->packet[0] >= (0x36 + ALL_FRAMES) && p->packet[0] < (0x39 + SINGLE_FRAME) && mPayload[iv->id].txCmd != 0x0f) ) { // small MI or MI 1500 data responses to 0x09, 0x11, 0x36, 0x37, 0x38 and 0x39 mPayload[iv->id].txId = p->packet[0]; miDataDecode(iv,p); - } - else if (p->packet[0] == ( 0x0f + ALL_FRAMES)) { + } else if (p->packet[0] == ( 0x0f + ALL_FRAMES)) { // MI response from get hardware information request miHwDecode(iv, p); mPayload[iv->id].txId = p->packet[0]; + } else if (p->packet[0] == ( 0x10 + ALL_FRAMES)) { + // MI response from get Grid Profile information request + miGPFDecode(iv, p); + mPayload[iv->id].txId = p->packet[0]; + } else if ( p->packet[0] == (TX_REQ_INFO + ALL_FRAMES) // response from get information command || (p->packet[0] == 0xB6 && mPayload[iv->id].txCmd != 0x36)) { // strange short response from MI-1500 3rd gen; might be misleading! // atm, we just do nothing else than print out what we got... @@ -211,11 +219,13 @@ class MiPayload { } } else if (p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES ) // response from dev control command || p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES -1)) { // response from DRED instruction - DPRINT_IVID(DBG_DEBUG, iv->id); - DBGPRINTLN(F("Response from devcontrol request received")); - + if (mSerialDebug) { + DPRINT_IVID(DBG_DEBUG, iv->id); + DBGPRINTLN(F("Response from devcontrol request received")); + } mPayload[iv->id].txId = p->packet[0]; iv->clearDevControlRequest(); + mStat->rxSuccess++; if ((p->packet[9] == 0x5a) && (p->packet[10] == 0x5a)) { mApp->setMqttPowerLimitAck(iv); @@ -228,7 +238,8 @@ class MiPayload { DBGPRINTLN(String(iv->powerLimit[1])); } iv->clearCmdQueue(); - iv->enqueCommand(SystemConfigPara); // read back power limit + //does not work for MI + //iv->enqueCommand(SystemConfigPara); // read back power limit } iv->devControlCmd = Init; } else { // some other response; copied from hmPayload:process; might not be correct to do that here!!! @@ -309,7 +320,8 @@ class MiPayload { (mPayload[iv->id].txId != (0x88)) && (mPayload[iv->id].txId != (0x92)) && (mPayload[iv->id].txId != 0 && - mPayload[iv->id].txCmd != 0x0f)) { + mPayload[iv->id].txCmd != 0x0f + && !iv->getDevControlRequest())) { // no processing needed if txId is not one of 0x95, 0x88, 0x89, 0x91, 0x92 or response to 0x36ff mPayload[iv->id].complete = true; mPayload[iv->id].rxTmo = true; @@ -330,7 +342,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); + mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, true, false, iv->getMaxPower()); } else { uint8_t cmd = mPayload[iv->id].txCmd; if (mPayload[iv->id].retransmits < mMaxRetrans) { @@ -339,6 +351,7 @@ class MiPayload { DPRINT_IVID(DBG_INFO, iv->id); DBGPRINTLN(F("nothing received")); mPayload[iv->id].retransmits = mMaxRetrans; + mPayload[iv->id].requested = false; //close failed request } else if( !mPayload[iv->id].gotFragment && !mPayload[iv->id].rxTmo ) { DPRINT_IVID(DBG_INFO, iv->id); DBGPRINTLN(F("retransmit on failed first request")); @@ -475,17 +488,29 @@ class MiPayload { } uint16_t prntsts = statusMi == 3 ? 1 : statusMi; + bool stsok = true; if ( statusMi != mPayload[iv->id].sts[stschan] ) { //sth.'s changed? iv->alarmCnt = 1; // minimum... - if ((iv->type != INV_TYPE_1CH) && ((statusMi != 3) //sth is or was wrong! - || ((mPayload[iv->id].sts[stschan] && statusMi == 3) && (mPayload[iv->id].sts[stschan] != 3))) + //sth is or was wrong? + if ( (iv->type != INV_TYPE_1CH) && ( (statusMi != 3) + || ((mPayload[iv->id].sts[stschan]) && (statusMi == 3) && (mPayload[iv->id].sts[stschan] != 3))) ) { - iv->lastAlarm[stschan] = alarm_t(prntsts, mPayload[iv->id].ts,mPayload[iv->id].ts); + iv->lastAlarm[stschan] = alarm_t(prntsts, mPayload[iv->id].ts,0); iv->alarmCnt = iv->type == INV_TYPE_2CH ? 3 : 5; - iv->alarmLastId = iv->alarmMesIndex; } + iv->alarmLastId = prntsts; //iv->alarmMesIndex; mPayload[iv->id].sts[stschan] = statusMi; + stsok = false; + if (iv->alarmCnt > 1) { //more than one channel + for (uint8_t ch = 0; ch < (iv->alarmCnt); ++ch) { //start with 1 + if (mPayload[iv->id].sts[ch] == 3) { + stsok = true; + break; + } + } + } + if (mSerialDebug) { DPRINT(DBG_WARN, F("New state on CH")); DBGPRINT(String(stschan)); DBGPRINT(F(" (")); @@ -494,9 +519,11 @@ class MiPayload { } } - if ( !mPayload[iv->id].sts[0] || prntsts < mPayload[iv->id].sts[0] ) { - mPayload[iv->id].sts[0] = prntsts; + //if ( !mPayload[iv->id].sts[0] || prntsts < mPayload[iv->id].sts[0] ) { + //mPayload[iv->id].sts[0] = prntsts; + if (!stsok) { iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, prntsts); + iv->lastAlarm[0] = alarm_t(prntsts, mPayload[iv->id].ts, 0); } if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]) { @@ -506,21 +533,7 @@ class MiPayload { DBGPRINT(F("alarm ID incremented to ")); DBGPRINTLN(String(iv->alarmMesIndex)); } - iv->lastAlarm[0] = alarm_t(prntsts, mPayload[iv->id].ts, mPayload[iv->id].ts); } - /*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) - break; - if (NULL != mCbAlarm) - (mCbAlarm)(code, start, end); - yield(); - } - }*/ } void miDataDecode(Inverter<> *iv, packet_t *p) { @@ -758,6 +771,37 @@ const byteAssign_t InfoAssignment[] = { mPayload[iv->id].requested= false; mStat->rxSuccess++; } + if (mHighPrioIv == NULL) + mHighPrioIv = iv; + } + + void miGPFDecode(Inverter<> *iv, packet_t *p ) { + mPayload[iv->id].gotFragment = true; + mPayload[iv->id].gotGPF = true; + record_t<> *rec = iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure + rec->ts = mPayload[iv->id].ts; + iv->setValue(2, rec, (uint32_t) (((p->packet[10] << 8) | p->packet[11]))); //FLD_GRID_PROFILE_CODE + iv->setValue(3, rec, (uint32_t) (((p->packet[12] << 8) | p->packet[13]))); //FLD_GRID_PROFILE_VERSION + iv->setQueuedCmdFinished(); + mStat->rxSuccess++; + +/* according to xlsx (different start byte -1!) + Polling Grid-connected Protection Parameter File Command - Receipt + byte[10] ST1 indicates the status of the grid-connected protection file. ST1=1 indicates the default grid-connected protection file, ST=2 indicates that the grid-connected protection file is configured and normal, ST=3 indicates that the grid-connected protection file cannot be recognized, ST=4 indicates that the grid-connected protection file is damaged + byte[11] byte[12] CountryStd variable indicates the national standard code of the grid-connected protection file + byte[13] byte[14] Version indicates the version of the grid-connected protection file + byte[15] byte[16] +*/ + if(mSerialDebug) { + DPRINT(DBG_INFO,F("ST1 ")); + DBGPRINTLN(String(p->packet[9])); + DPRINT(DBG_INFO,F("CountryStd ")); + DBGPRINTLN(String((p->packet[10] << 8) + p->packet[11])); + DPRINT(DBG_INFO,F("Version ")); + DBGPRINTLN(String((p->packet[12] << 8) + p->packet[13])); + } + if (mHighPrioIv == NULL) + mHighPrioIv = iv; } void reset(uint8_t id, bool setTxTmo = true, bool clrSts = false) { @@ -777,8 +821,8 @@ const byteAssign_t InfoAssignment[] = { mPayload[id].txCmd = 0; mPayload[id].requested = false; mPayload[id].ts = *mTimestamp; - mPayload[id].sts[0] = 0; if (clrSts) { // only clear channel states at startup + mPayload[id].sts[0] = 0; mPayload[id].sts[CH1] = 0; mPayload[id].sts[CH2] = 0; mPayload[id].sts[CH3] = 0;