You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

844 lines
42 KiB

//-----------------------------------------------------------------------------
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
#ifndef __MI_PAYLOAD_H__
#define __MI_PAYLOAD_H__
#include "../utils/dbg.h"
#include "../utils/crc.h"
#include "../config/config.h"
#include <Arduino.h>
#define MI_REQ_CH1 0x09
#define MI_REQ_CH2 0x11
#define MI_REQ_4CH 0x36
typedef struct {
uint32_t ts;
bool requested;
bool limitrequested;
uint8_t txCmd;
uint8_t len[MAX_PAYLOAD_ENTRIES];
int8_t rssi[4];
bool complete;
bool dataAB[3];
bool stsAB[3];
uint16_t sts[5];
uint8_t txId;
uint8_t invId;
uint8_t retransmits;
bool gotFragment;
bool gotGPF;
uint8_t rtrRes; // for limiting resets
uint8_t multi_parts; // for quality
bool rxTmo;
} miPayload_t;
typedef std::function<void(uint8_t, Inverter<> *)> miPayloadListenerType;
template<class HMSYSTEM>
class MiPayload {
public:
MiPayload() {}
void setup(IApp *app, HMSYSTEM *sys, uint32_t *timestamp) {
mApp = app;
mSys = sys;
mMaxRetrans = 5;
mTimestamp = timestamp;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
reset(i, false, true);
mPayload[i].limitrequested = false;
mPayload[i].gotGPF = false;
}
mSerialDebug = false;
mHighPrioIv = NULL;
mCbPayload = NULL;
}
void enableSerialDebug(bool enable) {
mSerialDebug = enable;
}
void addPayloadListener(miPayloadListenerType cb) {
mCbPayload = cb;
}
void addAlarmListener(alarmListenerType cb) {
mCbAlarm = cb;
}
void loop() {
if (NULL != mHighPrioIv) {
ivSend(mHighPrioIv, true); // for e.g. devcontrol commands
mHighPrioIv = NULL;
}
}
void ivSendHighPrio(Inverter<> *iv) {
mHighPrioIv = iv;
}
void ivSend(Inverter<> *iv, bool highPrio = false) {
if(!highPrio) {
if (mPayload[iv->id].requested) {
if (!mPayload[iv->id].complete)
process(false); // no retransmit
if (!mPayload[iv->id].complete && mPayload[iv->id].rxTmo) {
if (mSerialDebug)
DPRINT_IVID(DBG_INFO, iv->id);
if (!mPayload[iv->id].gotFragment) {
iv->radioStatistics.rxFailNoAnser++; // got nothing
if (mSerialDebug)
DBGPRINTLN(F("enqueued cmd failed/timeout"));
} else {
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: "));
DBGPRINT(String(mPayload[iv->id].retransmits));
DBGPRINTLN(F(")"));
}
}
mPayload[iv->id].rxTmo = true;
mPayload[iv->id].complete = true;
iv->setQueuedCmdFinished(); // command failed
}
}
}
reset(iv->id, !iv->isAvailable());
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));
}
if (iv->getDevControlRequest()) {
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("Devcontrol request 0x"));
DHEX(iv->devControlCmd);
DBGPRINT(F(" power limit "));
DBGPRINT(String(iv->powerLimit[0]));
DBGPRINT(F(" with PowerLimitControl "));
DBGPRINTLN(String(iv->powerLimit[1]));
}
iv->powerLimitAck = false;
iv->radio->sendControlPacket(iv, iv->devControlCmd, iv->powerLimit, false, false, (iv->powerLimit[1] == RelativNonPersistent) ? 0 : iv->getMaxPower());
mPayload[iv->id].txCmd = iv->devControlCmd;
mPayload[iv->id].limitrequested = true;
iv->clearCmdQueue();
} else {
uint8_t cmd = iv->getQueuedCmd();
uint8_t cmd2 = cmd;
if ( cmd == SystemConfigPara ) { //0x05 for HM-types
if (!mPayload[iv->id].gotGPF) {
iv->setQueuedCmdFinished();
cmd = iv->getQueuedCmd();
}
}
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);
DBGPRINT(F("legacy cmd 0x"));
DBGHEXLN(cmd);
}
iv->radio->sendCmdPacket(iv, cmd, cmd2, false, false);
mPayload[iv->id].txCmd = cmd;
if (iv->type == INV_TYPE_1CH || iv->type == INV_TYPE_2CH) {
mPayload[iv->id].dataAB[CH1] = false;
mPayload[iv->id].stsAB[CH1] = false;
mPayload[iv->id].dataAB[CH0] = false;
mPayload[iv->id].stsAB[CH0] = false;
if (iv->type == INV_TYPE_2CH) {
mPayload[iv->id].dataAB[CH2] = false;
mPayload[iv->id].stsAB[CH2] = false;
}
}
}
}
void add(Inverter<> *iv, packet_t *p) {
//DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX));
if (p->packet[0] == (0x88)) { // 0x88 is MI status response to 0x09
miStsDecode(iv, p);
}
else if (p->packet[0] == (MI_REQ_CH2 + SINGLE_FRAME)) { // 0x92; MI status response to 0x11
miStsDecode(iv, p, CH2);
} else if ( p->packet[0] == MI_REQ_CH1 + ALL_FRAMES ||
p->packet[0] == MI_REQ_CH2 + ALL_FRAMES ||
( p->packet[0] >= (MI_REQ_4CH + 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)) {
// 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 != MI_REQ_4CH)) { // strange short response from MI-1500 3rd gen; might be misleading!
// atm, we just do nothing else than print out what we got...
// for decoding see xls- Data collection instructions - #147ff
//mPayload[iv->id].txId = p->packet[0];
DPRINTLN(DBG_DEBUG, F("Response from info request received"));
uint8_t *pid = &p->packet[9];
if (*pid == 0x00) {
DPRINT(DBG_DEBUG, F("fragment number zero received"));
iv->setQueuedCmdFinished();
} else if (p->packet[9] == 0x81) { // might need some additional check, as this is only meant for short answers!
DPRINT_IVID(DBG_WARN, iv->id);
DBGPRINTLN(F("seems to use 3rd gen. protocol - switching ivGen!"));
iv->ivGen = IV_HM;
iv->setQueuedCmdFinished();
iv->clearCmdQueue();
}
} 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
#if DEBUG_LEVEL >= DBG_DEBUG
if (mSerialDebug) {
DPRINT_IVID(DBG_DEBUG, iv->id);
DBGPRINTLN(F("Response from devcontrol request received"));
}
#endif
mPayload[iv->id].txId = p->packet[0];
iv->clearDevControlRequest();
if ((p->packet[9] == 0x5a) && (p->packet[10] == 0x5a)) {
mApp->setMqttPowerLimitAck(iv);
iv->powerLimitAck = true;
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("has accepted power limit set point "));
DBGPRINT(String(iv->powerLimit[0]));
DBGPRINT(F(" with PowerLimitControl "));
DBGPRINTLN(String(iv->powerLimit[1]));
}
iv->clearCmdQueue();
//does not work for MI
//iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
}
iv->devControlCmd = Init;
} else { // some other response; copied from hmPayload:process; might not be correct to do that here!!!
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO,iv->id);
DBGPRINT(F("procPyld: cmd: 0x"));
DBGHEXLN(mPayload[iv->id].txCmd);
DPRINT_IVID(DBG_INFO,iv->id);
DBGPRINT(F("procPyld: txid: 0x"));
DBGHEXLN(mPayload[iv->id].txId);
}
record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
mPayload[iv->id].complete = true;
uint8_t payload[128];
uint8_t payloadLen = 0;
memset(payload, 0, 128);
payloadLen -= 2;
if (mSerialDebug) {
DPRINT(DBG_INFO, F("Payload ("));
DBGPRINT(String(payloadLen));
DBGPRINT("): ");
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))
iv->radioStatistics.rxSuccess++;
rec->ts = mPayload[iv->id].ts;
for (uint8_t i = 0; i < rec->length; i++) {
iv->addValue(i, payload, rec);
yield();
}
iv->doCalculations();
notify(mPayload[iv->id].txCmd, iv);
if(AlarmData == mPayload[iv->id].txCmd) {
uint8_t i = 0;
while(1) {
if(0 == iv->parseAlarmLog(i++, payload, payloadLen))
break;
if (NULL != mCbAlarm)
(mCbAlarm)(iv);
yield();
}
}
} else {
DPRINTLN(DBG_ERROR, F("plausibility check failed, expected ") + String(rec->pyldLen) + F(" bytes"));
iv->radioStatistics.rxFail++;
}
iv->setQueuedCmdFinished();
}
}
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_MI != iv->ivGen) // only process MI inverters
continue; // skip to next inverter
if ( !mPayload[iv->id].complete &&
(mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) &&
(mPayload[iv->id].txId < (MI_REQ_4CH + ALL_FRAMES)) &&
(mPayload[iv->id].txId > (0x39 + ALL_FRAMES)) &&
(mPayload[iv->id].txId != (MI_REQ_CH1 + ALL_FRAMES)) &&
(mPayload[iv->id].txId != (MI_REQ_CH2 + ALL_FRAMES)) &&
(mPayload[iv->id].txId != (0x88)) &&
(mPayload[iv->id].txId != (0x92)) &&
(mPayload[iv->id].txId != 0) &&
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;
continue; // skip to next inverter
}
if (!mPayload[iv->id].complete) {
bool gotAllMsgParts, pyldComplete, fastNext;
gotAllMsgParts = build(iv, &pyldComplete, &fastNext);
if (!gotAllMsgParts && !pyldComplete) { // payload not complete
if ((mPayload[iv->id].requested) && (retransmit)) {
if (iv->devControlCmd == Restart || iv->devControlCmd == CleanState_LockAndAlarm) {
// This is required to prevent retransmissions without answer.
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("Prevent retransmit on Restart / CleanState_LockAndAlarm..."));
mPayload[iv->id].retransmits = mMaxRetrans;
mPayload[iv->id].rxTmo = true;
} else if(iv->devControlCmd == ActivePowerContr) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("retransmit power limit"));
iv->radio->sendControlPacket(iv, iv->devControlCmd, iv->powerLimit, true, false, (iv->powerLimit[1] == RelativNonPersistent) ? 0 : iv->getMaxPower());
} else {
uint8_t cmd = mPayload[iv->id].txCmd;
if (mPayload[iv->id].retransmits < mMaxRetrans) {
mPayload[iv->id].retransmits++;
if( !mPayload[iv->id].gotFragment && mPayload[iv->id].rxTmo ) {
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"));
mPayload[iv->id].rxTmo = true;
iv->radio->sendCmdPacket(iv, cmd, cmd, true, false);
} else if ( cmd == 0x0f ) {
//hard/firmware request
iv->radio->sendCmdPacket(iv, 0x0f, 0x00, true, false);
mPayload[id].multi_parts = 0;
} else {
bool change = false;
if ( cmd >= MI_REQ_4CH && cmd < 0x39 ) { // MI-1500 Data command
if (cmd > MI_REQ_4CH && mPayload[iv->id].retransmits==1) // first request for the upper channels
change = true;
} else if ( cmd == MI_REQ_CH1 ) {//MI single or dual channel device
if ( mPayload[iv->id].dataAB[CH1] && iv->type == INV_TYPE_2CH ) {
if (!mPayload[iv->id].stsAB[CH1] && mPayload[iv->id].retransmits<2) {}
//first try to get missing sts for first channel a second time
else if (!mPayload[iv->id].stsAB[CH2] || !mPayload[iv->id].dataAB[CH2] ) {
cmd = MI_REQ_CH2;
change = true;
if (mPayload[iv->id].rtrRes < 3) //only get back to first channel twice
mPayload[iv->id].retransmits = 0; //reset counter
}
}
} else if ( cmd == MI_REQ_CH2) {
if ( mPayload[iv->id].dataAB[CH2] ) { // data + status ch2 are there?
if (mPayload[iv->id].stsAB[CH2] && (!mPayload[iv->id].stsAB[CH1] || !mPayload[iv->id].dataAB[CH1])) {
cmd = MI_REQ_CH1;
change = true;
}
}
}
DPRINT_IVID(DBG_INFO, iv->id);
if (change) {
DBGPRINT(F("next request is"));
mPayload[iv->id].txCmd = cmd;
mPayload[iv->id].rtrRes++;
} else {
DBGPRINT(F("sth."));
DBGPRINT(F(" missing: Request Retransmit"));
}
DBGPRINT(F(" 0x"));
DBGHEXLN(cmd);
mPayload[id].multi_parts = 0;
iv->radio->sendCmdPacket(iv, cmd, cmd, true, false);
yield();
}
} else {
mPayload[iv->id].rxTmo = true;
}
}
}
} else if(!gotAllMsgParts && pyldComplete) { // crc error on complete Payload
if (mPayload[iv->id].retransmits < mMaxRetrans) {
mPayload[iv->id].retransmits++;
mPayload[iv->id].txCmd = iv->getQueuedCmd();
mPayload[id].multi_parts = 0;
if (mSerialDebug) {
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);
}
iv->radio->sendCmdPacket(iv, mPayload[iv->id].txCmd, mPayload[iv->id].txCmd, false, false);
} else {
mPayload[iv->id].rxTmo = true;
}
} else {
if (!fastNext) {
mPayload[iv->id].rxTmo = true;
} else {
if (mHighPrioIv == NULL)
mHighPrioIv = iv;
}
}
} else {
mPayload[iv->id].rxTmo = true;
}
yield();
}
}
private:
void notify(uint8_t val, Inverter<> *iv) {
if(NULL != mCbPayload)
(mCbPayload)(val, iv);
}
void miStsDecode(Inverter<> *iv, packet_t *p, uint8_t stschan = CH1) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the record structure
rec->ts = mPayload[iv->id].ts;
mPayload[iv->id].gotFragment = true;
mPayload[iv->id].multi_parts += 3;
mPayload[iv->id].txId = p->packet[0];
miStsConsolidate(iv, stschan, rec, p->packet[10], p->packet[12], p->packet[9], p->packet[11]);
mPayload[iv->id].stsAB[stschan] = true;
if (mPayload[iv->id].stsAB[CH1] && mPayload[iv->id].stsAB[CH2])
mPayload[iv->id].stsAB[CH0] = true;
}
void miStsConsolidate(Inverter<> *iv, 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?
if ( uState == 2 ) {
statusMi = 5050 + stschan; //first approach, needs review!
if (lState)
statusMi += lState*10;
} else if ( uState > 3 ) {
statusMi = uState*1000 + uEnum*10;
if (lState)
statusMi += lState*100; //needs review, esp. for 4ch-8310 state!
//if (lEnum)
statusMi += lEnum;
if (uEnum < 6) {
statusMi += stschan;
}
if (statusMi == 8000)
statusMi = 8310; //trick?
}
uint16_t prntsts = statusMi == 3 ? 1 : statusMi;
bool stsok = true;
if ( statusMi != mPayload[iv->id].sts[stschan] ) { //sth.'s changed?
iv->alarmCnt = 1; // minimum...
//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,0);
iv->alarmCnt = iv->type == INV_TYPE_2CH ? 3 : 5;
}
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(" ("));
DBGPRINT(String(prntsts)); DBGPRINT(F("): "));
DBGPRINTLN(iv->getAlarmStr(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)]) {
iv->alarmMesIndex = rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]; // seems there's no status per channel in 3rd gen. models?!?
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("alarm ID incremented to "));
DBGPRINTLN(String(iv->alarmMesIndex));
}
}
}
void miDataDecode(Inverter<> *iv, packet_t *p) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); // choose the parser
rec->ts = mPayload[iv->id].ts;
mPayload[iv->id].gotFragment = true;
mPayload[iv->id].multi_parts += 4;
uint8_t datachan = ( p->packet[0] == (MI_REQ_CH1 + ALL_FRAMES) || p->packet[0] == (MI_REQ_4CH + ALL_FRAMES) ) ? CH1 :
( p->packet[0] == (MI_REQ_CH2 + ALL_FRAMES) || p->packet[0] == (0x37 + ALL_FRAMES) ) ? CH2 :
p->packet[0] == (0x38 + ALL_FRAMES) ? CH3 :
CH4;
// count in RF_communication_protocol.xlsx is with offset = -1
iv->setValue(iv->getPosByChFld(datachan, FLD_UDC, rec), rec, (float)((p->packet[9] << 8) + p->packet[10])/10);
yield();
iv->setValue(iv->getPosByChFld(datachan, FLD_IDC, rec), rec, (float)((p->packet[11] << 8) + p->packet[12])/10);
yield();
iv->setValue(iv->getPosByChFld(0, FLD_UAC, rec), rec, (float)((p->packet[13] << 8) + p->packet[14])/10);
yield();
iv->setValue(iv->getPosByChFld(0, FLD_F, rec), rec, (float) ((p->packet[15] << 8) + p->packet[16])/100);
iv->setValue(iv->getPosByChFld(datachan, FLD_PDC, rec), rec, (float)((p->packet[17] << 8) + p->packet[18])/10);
yield();
iv->setValue(iv->getPosByChFld(datachan, FLD_YD, rec), rec, (float)((p->packet[19] << 8) + p->packet[20])/1);
yield();
iv->setValue(iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[21] << 8) + p->packet[22])/10);
iv->setValue(iv->getPosByChFld(0, FLD_IRR, rec), rec, (float) (calcIrradiation(iv, datachan)));
mPayload[iv->id].rssi[(datachan-1)] = p->rssi;
if ( datachan < 3 ) {
mPayload[iv->id].dataAB[datachan] = true;
}
if ( !mPayload[iv->id].dataAB[CH0] && mPayload[iv->id].dataAB[CH1] && mPayload[iv->id].dataAB[CH2] ) {
mPayload[iv->id].dataAB[CH0] = true;
}
if (p->packet[0] >= (MI_REQ_4CH + ALL_FRAMES) ) {
/*For MI1500:
if (MI1500) {
STAT = (uint8_t)(p->packet[25] );
FCNT = (uint8_t)(p->packet[26]);
FCODE = (uint8_t)(p->packet[27]);
}*/
miStsConsolidate(iv, datachan, rec, p->packet[23], p->packet[24]);
if (p->packet[0] < (0x39 + ALL_FRAMES) ) {
mPayload[iv->id].txCmd++;
mPayload[iv->id].retransmits = 0; // reserve retransmissions for each response
mPayload[iv->id].complete = false;
} else {
miComplete(iv);
}
}
}
void miComplete(Inverter<> *iv) {
if ( mPayload[iv->id].complete )
return; //if we got second message as well in repreated attempt
mPayload[iv->id].complete = true;
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("got all msgs"));
}
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, calcYieldDayCh0(iv,0));
//preliminary AC calculation...
float ac_pow = 0;
for(uint8_t i = 1; i <= iv->channels; i++) {
if (mPayload[iv->id].sts[i] == 3) {
uint8_t pos = iv->getPosByChFld(i, FLD_PDC, rec);
ac_pow += iv->getValue(pos, rec);
}
}
ac_pow = (int) (ac_pow*9.5);
iv->setValue(iv->getPosByChFld(0, FLD_PAC, rec), rec, (float) ac_pow/10);
int8_t rssi = -127;
for (uint8_t i = 0; i < 4; i++) {
// get best RSSI
if(mPayload[iv->id].rssi[i] > rssi)
rssi = mPayload[iv->id].rssi[i];
yield();
}
iv->rssi = rssi;
iv->doCalculations();
// update status state-machine,
iv->isProducing();
iv->setQueuedCmdFinished();
iv->radioStatistics.rxSuccess++;
yield();
notify(RealTimeRunData_Debug, iv);
}
bool build(Inverter<> *iv, bool *complete, bool *fastNext ) {
DPRINTLN(DBG_VERBOSE, F("build"));
// check if all messages are there
*complete = mPayload[iv->id].complete;
*fastNext = false;
uint8_t txCmd = mPayload[iv->id].txCmd;
if(!*complete) {
DPRINTLN(DBG_VERBOSE, F("incomlete, txCmd is 0x") + String(txCmd, HEX));
//we got some delayed status msgs?!?
if ((txCmd == MI_REQ_CH1) || (txCmd == MI_REQ_CH2)) {
if (mPayload[iv->id].stsAB[CH0] && mPayload[iv->id].dataAB[CH0]) {
miComplete(iv);
return true;
}
return false;
}
if (txCmd >= MI_REQ_4CH && txCmd <= 0x39) {
return false;
}
if (txCmd == 0x0f) { //hw info request, at least hw part nr. and version have to be there...
bool gotRelevant = iv->getFwVersion()
&& iv->getChannelFieldValue(CH0, FLD_PART_NUM, iv->getRecordStruct(InverterDevInform_Simple));
if (gotRelevant)
*fastNext = true;
return gotRelevant;
}
}
//check if we want the next request to be executed faster
if (mPayload[iv->id].gotFragment && txCmd == 0x0f)
*fastNext = true;
return true;
}
void miHwDecode(Inverter<> *iv, packet_t *p ) {
record_t<> *rec = iv->getRecordStruct(InverterDevInform_All); // choose the record structure
rec->ts = mPayload[iv->id].ts;
mPayload[iv->id].gotFragment = true;
/*
Polling the device software and hardware version number command
start byte Command word routing address target address User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12]
0x7e 0x0f xx xx xx xx YY YY YY YY 0x00 CRC 0x7f
Command Receipt - First Frame
start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[17] byte[18] byte[19] byte[20] byte[21] byte[22] byte[23] byte[24] byte[25] byte[26] byte[27] byte[28]
0x7e 0x8f YY YY YY YY xx xx xx xx 0x00 USFWBuild_VER APPFWBuild_VER APPFWBuild_YYYY APPFWBuild_MMDD APPFWBuild_HHMM APPFW_PN HW_VER CRC 0x7f
Command Receipt - Second Frame
start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[17] byte[18] byte[19] byte[20] byte[21] byte[22] byte[23] byte[24] byte[25] byte[26] byte[27] byte[28]
0x7e 0x8f YY YY YY YY xx xx xx xx 0x01 HW_PN HW_FB_TLmValue HW_FB_ReSPRT HW_GridSamp_ResValule HW_ECapValue Matching_APPFW_PN CRC 0x7f
Command receipt - third frame
start byte Command word target address routing address Multi-frame marking User data User data User data User data User data User data User data User data check end byte
byte[0] byte[1] byte[2] byte[3] byte[4] byte[5] byte[6] byte[7] byte[8] byte[9] byte[10] byte[11] byte[12] byte[13] byte[14] byte[15] byte[16] byte[15] byte[16] byte[17] byte[18]
0x7e 0x8f YY YY YY YY xx xx xx xx 0x12 APPFW_MINVER HWInfoAddr PNInfoCRC_gusv PNInfoCRC_gusv CRC 0x7f
*/
/*
case InverterDevInform_All:
rec->length = (uint8_t)(HMINFO_LIST_LEN);
rec->assign = (byteAssign_t *)InfoAssignment;
rec->pyldLen = HMINFO_PAYLOAD_LEN;
break;
const byteAssign_t InfoAssignment[] = {
{ FLD_FW_VERSION, UNIT_NONE, CH0, 0, 2, 1 },
{ FLD_FW_BUILD_YEAR, UNIT_NONE, CH0, 2, 2, 1 },
{ FLD_FW_BUILD_MONTH_DAY, UNIT_NONE, CH0, 4, 2, 1 },
{ FLD_FW_BUILD_HOUR_MINUTE, UNIT_NONE, CH0, 6, 2, 1 },
{ FLD_BOOTLOADER_VER, UNIT_NONE, CH0, 8, 2, 1 }
};
*/
if ( p->packet[9] == 0x00 ) {//first frame
//FLD_FW_VERSION
for (uint8_t i = 0; i < 5; i++) {
iv->setValue(i, rec, (float) ((p->packet[(12+2*i)] << 8) + p->packet[(13+2*i)])/1);
}
iv->isConnected = true;
if(mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DPRINT(DBG_INFO,F("HW_VER is "));
DBGPRINTLN(String((p->packet[24] << 8) + p->packet[25]));
}
record_t<> *rec = iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure
rec->ts = mPayload[iv->id].ts;
iv->setValue(1, rec, (uint32_t) ((p->packet[24] << 8) + p->packet[25])/1);
mPayload[iv->id].multi_parts +=4;
} else if ( p->packet[9] == 0x01 || p->packet[9] == 0x10 ) {//second frame for MI, 3rd gen. answers in 0x10
DPRINT_IVID(DBG_INFO, iv->id);
if ( p->packet[9] == 0x01 ) {
DBGPRINTLN(F("got 2nd frame (hw info)"));
/* according to xlsx (different start byte -1!)
byte[11] to byte[14] HW_PN
byte[15] byte[16] HW_FB_TLmValue
byte[17] byte[18] HW_FB_ReSPRT
byte[19] byte[20] HW_GridSamp_ResValule
byte[21] byte[22] HW_ECapValue
byte[23] to byte[26] Matching_APPFW_PN*/
DPRINT(DBG_INFO,F("HW_PartNo "));
DBGPRINTLN(String((uint32_t) (((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13]));
record_t<> *rec = iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure
rec->ts = mPayload[iv->id].ts;
iv->setValue(0, rec, (uint32_t) ((((p->packet[10] << 8) | p->packet[11]) << 8 | p->packet[12]) << 8 | p->packet[13])/1);
if(mSerialDebug) {
DPRINT(DBG_INFO,F("HW_FB_TLmValue "));
DBGPRINTLN(String((p->packet[14] << 8) + p->packet[15]));
DPRINT(DBG_INFO,F("HW_FB_ReSPRT "));
DBGPRINTLN(String((p->packet[16] << 8) + p->packet[17]));
DPRINT(DBG_INFO,F("HW_GridSamp_ResValule "));
DBGPRINTLN(String((p->packet[18] << 8) + p->packet[19]));
DPRINT(DBG_INFO,F("HW_ECapValue "));
DBGPRINTLN(String((p->packet[20] << 8) + p->packet[21]));
DPRINT(DBG_INFO,F("Matching_APPFW_PN "));
DBGPRINTLN(String((uint32_t) (((p->packet[22] << 8) | p->packet[23]) << 8 | p->packet[24]) << 8 | p->packet[25]));
}
//notify(InverterDevInform_Simple, iv);
mPayload[iv->id].multi_parts +=2;
notify(InverterDevInform_All, iv);
} else {
DBGPRINTLN(F("3rd gen. inverter!"));
}
} else if ( p->packet[9] == 0x12 ) {//3rd frame
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("got 3rd frame (hw info)"));
/* according to xlsx (different start byte -1!)
byte[11] byte[12] APPFW_MINVER
byte[13] byte[14] HWInfoAddr
byte[15] byte[16] PNInfoCRC_gusv
byte[15] byte[16] PNInfoCRC_gusv (this really is double mentionned in xlsx...)
*/
if(mSerialDebug) {
DPRINT(DBG_INFO,F("APPFW_MINVER "));
DBGPRINTLN(String((p->packet[10] << 8) + p->packet[11]));
DPRINT(DBG_INFO,F("HWInfoAddr "));
DBGPRINTLN(String((p->packet[12] << 8) + p->packet[13]));
DPRINT(DBG_INFO,F("PNInfoCRC_gusv "));
DBGPRINTLN(String((p->packet[14] << 8) + p->packet[15]));
}
mPayload[iv->id].multi_parts++;
}
if (mPayload[iv->id].multi_parts > 5) {
iv->setQueuedCmdFinished();
mPayload[iv->id].complete = true;
mPayload[iv->id].rxTmo = true;
mPayload[iv->id].requested= false;
iv->radioStatistics.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();
iv->radioStatistics.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) {
memset(mPayload[id].len, 0, MAX_PAYLOAD_ENTRIES);
mPayload[id].gotFragment = false;
mPayload[id].rxTmo = setTxTmo;// design: don't start with complete retransmit
mPayload[id].rtrRes = 0;
mPayload[id].multi_parts = 0;
mPayload[id].retransmits = 0;
mPayload[id].complete = false;
mPayload[id].dataAB[CH0] = true; //required for 1CH and 2CH devices
mPayload[id].dataAB[CH1] = true; //required for 1CH and 2CH devices
mPayload[id].dataAB[CH2] = true; //only required for 2CH devices
mPayload[id].stsAB[CH0] = true; //required for 1CH and 2CH devices
mPayload[id].stsAB[CH1] = true; //required for 1CH and 2CH devices
mPayload[id].stsAB[CH2] = true; //only required for 2CH devices
mPayload[id].txCmd = 0;
mPayload[id].requested = false;
mPayload[id].ts = *mTimestamp;
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;
mPayload[id].sts[CH4] = 0;
}
}
IApp *mApp;
HMSYSTEM *mSys;
uint8_t mMaxRetrans;
uint32_t *mTimestamp;
miPayload_t mPayload[MAX_NUM_INVERTERS];
bool mSerialDebug;
Inverter<> *mHighPrioIv;
alarmListenerType mCbAlarm;
payloadListenerType mCbPayload;
};
#endif /*__MI_PAYLOAD_H__*/