mirror of https://github.com/lumapu/ahoy.git
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.
433 lines
18 KiB
433 lines
18 KiB
//-----------------------------------------------------------------------------
|
|
// 2023 Ahoy, https://github.com/lumpapu/ahoy
|
|
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#ifndef __COMMUNICATION_H__
|
|
#define __COMMUNICATION_H__
|
|
|
|
#include "CommQueue.h"
|
|
#include <Arduino.h>
|
|
#include "../utils/crc.h"
|
|
|
|
typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType;
|
|
typedef std::function<void(Inverter<> *)> alarmListenerType;
|
|
|
|
class Communication : public CommQueue<> {
|
|
public:
|
|
void setup(uint32_t *timestamp) {
|
|
mTimestamp = timestamp;
|
|
}
|
|
|
|
void addImportant(Inverter<> *iv, uint8_t cmd, bool delOnPop = true) {
|
|
mState = States::RESET; // cancel current operation
|
|
CommQueue::addImportant(iv, cmd, delOnPop);
|
|
}
|
|
|
|
void addPayloadListener(payloadListenerType cb) {
|
|
mCbPayload = cb;
|
|
}
|
|
|
|
void addAlarmListener(alarmListenerType cb) {
|
|
mCbAlarm = cb;
|
|
}
|
|
|
|
void loop() {
|
|
get([this](bool valid, const queue_s *q) {
|
|
if(!valid)
|
|
return; // empty
|
|
|
|
switch(mState) {
|
|
case States::RESET:
|
|
if(millis() < mWaitTimeout)
|
|
return;
|
|
mMaxFrameId = 0;
|
|
for(uint8_t i = 0; i < MAX_PAYLOAD_ENTRIES; i++) {
|
|
mLocalBuf[i].len = 0;
|
|
}
|
|
mState = States::START;
|
|
break;
|
|
|
|
case States::START:
|
|
setTs(mTimestamp);
|
|
if(q->isDevControl) {
|
|
if(ActivePowerContr == q->cmd)
|
|
q->iv->powerLimitAck = false;
|
|
q->iv->radio->sendControlPacket(q->iv, q->cmd, q->iv->powerLimit, false);
|
|
} else
|
|
q->iv->radio->prepareDevInformCmd(q->iv, q->cmd, q->ts, q->iv->alarmLastId, false);
|
|
q->iv->radioStatistics.txCnt++;
|
|
mWaitTimeout = millis() + 500;
|
|
setAttempt();
|
|
mState = States::WAIT;
|
|
break;
|
|
|
|
case States::WAIT:
|
|
if(millis() < mWaitTimeout)
|
|
return;
|
|
mState = States::CHECK_FRAMES;
|
|
break;
|
|
|
|
case States::CHECK_FRAMES: {
|
|
if(!q->iv->radio->get()) { // radio buffer empty
|
|
cmdDone();
|
|
DPRINT_IVID(DBG_INFO, q->iv->id);
|
|
DBGPRINT(F("request timeout: "));
|
|
DBGPRINT(String(millis() - mWaitTimeout + 500));
|
|
DBGPRINTLN(F("ms"));
|
|
|
|
q->iv->radioStatistics.rxFailNoAnser++; // got nothing
|
|
if((IV_HMS == q->iv->ivGen) || (IV_HMT == q->iv->ivGen)) {
|
|
q->iv->radio->switchFrequency(q->iv, HOY_BOOT_FREQ_KHZ, WORK_FREQ_KHZ);
|
|
mWaitTimeout = millis() + 1000;
|
|
}
|
|
mState = States::RESET;
|
|
break;
|
|
}
|
|
|
|
States nextState = States::RESET;
|
|
while(!q->iv->radio->mBufCtrl.empty()) {
|
|
packet_t *p = &q->iv->radio->mBufCtrl.front();
|
|
|
|
DPRINT_IVID(DBG_INFO, q->iv->id);
|
|
DBGPRINT(F("RX "));
|
|
if(p->millis < 100)
|
|
DBGPRINT(F("0"));
|
|
DBGPRINT(String(p->millis));
|
|
DBGPRINT(F("ms "));
|
|
DBGPRINT(String(p->len));
|
|
if((IV_HM == q->iv->ivGen) || (IV_MI == q->iv->ivGen)) {
|
|
DBGPRINT(F(" CH"));
|
|
if(3 == p->ch)
|
|
DBGPRINT(F("0"));
|
|
DBGPRINT(String(p->ch));
|
|
}
|
|
DBGPRINT(F(", "));
|
|
DBGPRINT(String(p->rssi));
|
|
DBGPRINT(F("dBm | "));
|
|
ah::dumpBuf(p->packet, p->len);
|
|
|
|
if(checkIvSerial(&p->packet[1], q->iv)) {
|
|
q->iv->radioStatistics.frmCnt++;
|
|
|
|
if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command
|
|
parseFrame(p);
|
|
nextState = States::CHECK_PACKAGE;
|
|
} else if (p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES)) { // response from dev control command
|
|
parseDevCtrl(p, q);
|
|
cmdDone(true); // remove done request
|
|
} else if(IV_MI == q->iv->ivGen) {
|
|
parseMiFrame(p, q);
|
|
}
|
|
} else
|
|
DPRINTLN(DBG_WARN, F("Inverter serial does not match"));
|
|
|
|
q->iv->radio->mBufCtrl.pop();
|
|
yield();
|
|
}
|
|
mState = nextState;
|
|
}
|
|
break;
|
|
|
|
case States::CHECK_PACKAGE:
|
|
if(0 == mMaxFrameId) {
|
|
setAttempt();
|
|
|
|
DPRINT_IVID(DBG_WARN, q->iv->id);
|
|
DBGPRINT(F("frame missing: request retransmit ("));
|
|
DBGPRINT(String(q->attempts));
|
|
DBGPRINTLN(F(" attempts left)"));
|
|
|
|
uint8_t i = 0;
|
|
while(i < MAX_PAYLOAD_ENTRIES) {
|
|
if(mLocalBuf[i++].len == 0)
|
|
break;
|
|
}
|
|
|
|
if(q->attempts) {
|
|
q->iv->radio->sendCmdPacket(q->iv, TX_REQ_INFO, (ALL_FRAMES + i), true);
|
|
q->iv->radioStatistics.retransmits++;
|
|
mWaitTimeout = millis() + 500;
|
|
mState = States::WAIT;
|
|
} else {
|
|
add(q, true);
|
|
cmdDone(q);
|
|
mState = States::RESET;
|
|
}
|
|
return;
|
|
}
|
|
|
|
for(uint8_t i = 0; i < mMaxFrameId; i++) {
|
|
if(mLocalBuf[i].len == 0) {
|
|
setAttempt();
|
|
|
|
DPRINT_IVID(DBG_WARN, q->iv->id);
|
|
DBGPRINT(F("frame "));
|
|
DBGPRINT(String(i + 1));
|
|
DBGPRINT(F(" missing: request retransmit ("));
|
|
DBGPRINT(String(q->attempts));
|
|
DBGPRINTLN(F(" attempts left)"));
|
|
|
|
if(q->attempts) {
|
|
q->iv->radio->sendCmdPacket(q->iv, TX_REQ_INFO, (ALL_FRAMES + i), true);
|
|
q->iv->radioStatistics.retransmits++;
|
|
mWaitTimeout = millis() + 500;
|
|
mState = States::WAIT;
|
|
} else {
|
|
add(q, true);
|
|
cmdDone(q);
|
|
mState = States::RESET;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
compilePayload(q);
|
|
|
|
if(NULL != mCbPayload)
|
|
(mCbPayload)(q->cmd, q->iv);
|
|
|
|
cmdDone(true); // remove done request
|
|
mState = States::RESET; // everything ok, next request
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
private:
|
|
inline bool checkIvSerial(uint8_t buf[], Inverter<> *iv) {
|
|
uint8_t tmp[4];
|
|
CP_U32_BigEndian(tmp, iv->radioId.u64 >> 8);
|
|
for(uint8_t i = 0; i < 4; i++) {
|
|
if(tmp[i] != buf[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline bool checkFrameCrc(uint8_t buf[], uint8_t len) {
|
|
return (ah::crc8(buf, len - 1) == buf[len-1]);
|
|
}
|
|
|
|
inline void parseFrame(packet_t *p) {
|
|
uint8_t *frameId = &p->packet[9];
|
|
if(0x00 == *frameId) {
|
|
DPRINTLN(DBG_WARN, F("invalid frameId 0x00"));
|
|
return; // skip current packet
|
|
}
|
|
if((*frameId & 0x7f) > MAX_PAYLOAD_ENTRIES) {
|
|
DPRINTLN(DBG_WARN, F("local buffer to small for payload fragments"));
|
|
return; // local storage is to small for id
|
|
}
|
|
|
|
if(!checkFrameCrc(p->packet, p->len)) {
|
|
DPRINTLN(DBG_WARN, F("frame CRC is wrong"));
|
|
return; // CRC8 is wrong, frame invalid
|
|
}
|
|
|
|
if((*frameId & ALL_FRAMES) == ALL_FRAMES)
|
|
mMaxFrameId = (*frameId & 0x7f);
|
|
|
|
frame_t *f = &mLocalBuf[(*frameId & 0x7f) - 1];
|
|
memcpy(f->buf, &p->packet[10], p->len-11);
|
|
f->len = p->len - 11;
|
|
f->rssi = p->rssi;
|
|
}
|
|
|
|
inline void parseMiFrame(packet_t *p, const queue_s *q) {
|
|
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))
|
|
&& (q->cmd != 0x0f))) {
|
|
// small MI or MI 1500 data responses to 0x09, 0x11, 0x36, 0x37, 0x38 and 0x39
|
|
//mPayload[iv->id].txId = p->packet[0];
|
|
miDataDecode(p, q);
|
|
|
|
}
|
|
}
|
|
|
|
inline void miDataDecode(packet_t *p, const queue_s *q) {
|
|
record_t<> *rec = q->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
|
|
q->iv->setValue(q->iv->getPosByChFld(datachan, FLD_UDC, rec), rec, (float)((p->packet[9] << 8) + p->packet[10])/10);
|
|
|
|
q->iv->setValue(q->iv->getPosByChFld(datachan, FLD_IDC, rec), rec, (float)((p->packet[11] << 8) + p->packet[12])/10);
|
|
|
|
q->iv->setValue(q->iv->getPosByChFld(0, FLD_UAC, rec), rec, (float)((p->packet[13] << 8) + p->packet[14])/10);
|
|
|
|
q->iv->setValue(q->iv->getPosByChFld(0, FLD_F, rec), rec, (float) ((p->packet[15] << 8) + p->packet[16])/100);
|
|
q->iv->setValue(q->iv->getPosByChFld(datachan, FLD_PDC, rec), rec, (float)((p->packet[17] << 8) + p->packet[18])/10);
|
|
|
|
q->iv->setValue(q->iv->getPosByChFld(datachan, FLD_YD, rec), rec, (float)((p->packet[19] << 8) + p->packet[20])/1);
|
|
|
|
q->iv->setValue(q->iv->getPosByChFld(0, FLD_T, rec), rec, (float) ((int16_t)(p->packet[21] << 8) + p->packet[22])/10);
|
|
q->iv->setValue(q->iv->getPosByChFld(0, FLD_IRR, rec), rec, (float) (calcIrradiation(q->iv, datachan)));
|
|
//mPayload[q->iv->id].rssi[(datachan-1)] = p->rssi;
|
|
|
|
/*if ( datachan < 3 ) {
|
|
mPayload[q->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) ) {
|
|
addImportant(q->iv, (q->cmd + 1));
|
|
//mPayload[iv->id].txCmd++;
|
|
//mPayload[iv->id].retransmits = 0; // reserve retransmissions for each response
|
|
//mPayload[iv->id].complete = false;
|
|
} else {
|
|
//miComplete(iv);
|
|
}
|
|
} else if((p->packet[0] == (MI_REQ_CH1 + ALL_FRAMES)) && q->iv->type == INV_TYPE_2CH ) {
|
|
addImportant(q->iv, MI_REQ_CH2);
|
|
}
|
|
}
|
|
|
|
inline void parseDevCtrl(packet_t *p, const queue_s *q) {
|
|
if((p->packet[12] != ActivePowerContr) || (p->packet[13] != 0x00))
|
|
return;
|
|
bool accepted = true;
|
|
if((p->packet[10] == 0x00) && (p->packet[11] == 0x00))
|
|
q->iv->powerLimitAck = true;
|
|
else
|
|
accepted = false;
|
|
|
|
DPRINT_IVID(DBG_INFO, q->iv->id);
|
|
DBGPRINT(F(" has "));
|
|
if(!accepted) DBGPRINT(F("not "));
|
|
DBGPRINT(F("accepted power limit set point "));
|
|
DBGPRINT(String(q->iv->powerLimit[0]));
|
|
DBGPRINT(F(" with PowerLimitControl "));
|
|
DBGPRINTLN(String(q->iv->powerLimit[1]));
|
|
q->iv->actPowerLimit = 0xffff; // unknown, readback current value
|
|
}
|
|
|
|
inline void compilePayload(const queue_s *q) {
|
|
uint16_t crc = 0xffff, crcRcv = 0x0000;
|
|
for(uint8_t i = 0; i < mMaxFrameId; i++) {
|
|
if(i == (mMaxFrameId - 1)) {
|
|
crc = ah::crc16(mLocalBuf[i].buf, mLocalBuf[i].len - 2, crc);
|
|
crcRcv = (mLocalBuf[i].buf[mLocalBuf[i].len-2] << 8);
|
|
crcRcv |= mLocalBuf[i].buf[mLocalBuf[i].len-1];
|
|
} else
|
|
crc = ah::crc16(mLocalBuf[i].buf, mLocalBuf[i].len, crc);
|
|
}
|
|
|
|
if(crc != crcRcv) {
|
|
DPRINT_IVID(DBG_WARN, q->iv->id);
|
|
DBGPRINT(F("CRC Error "));
|
|
if(q->attempts == 0) {
|
|
DBGPRINTLN(F("-> Fail"));
|
|
q->iv->radioStatistics.rxFail++; // got fragments but not complete response
|
|
cmdDone();
|
|
} else
|
|
DBGPRINTLN(F("-> complete retransmit"));
|
|
mState = States::RESET;
|
|
return;
|
|
}
|
|
|
|
/*DPRINT_IVID(DBG_INFO, q->iv->id);
|
|
DBGPRINT(F("procPyld: cmd: 0x"));
|
|
DBGHEXLN(q->cmd);*/
|
|
|
|
memset(mPayload, 0, 150);
|
|
int8_t rssi = -127;
|
|
uint8_t len = 0;
|
|
|
|
for(uint8_t i = 0; i < mMaxFrameId; i++) {
|
|
if(mLocalBuf[i].len + len > 150) {
|
|
DPRINTLN(DBG_ERROR, F("payload buffer to small!"));
|
|
return;
|
|
}
|
|
memcpy(&mPayload[len], mLocalBuf[i].buf, mLocalBuf[i].len);
|
|
len += mLocalBuf[i].len;
|
|
// get worst RSSI
|
|
if(mLocalBuf[i].rssi > rssi)
|
|
rssi = mLocalBuf[i].rssi;
|
|
}
|
|
|
|
len -= 2;
|
|
|
|
DPRINT_IVID(DBG_INFO, q->iv->id);
|
|
DBGPRINT(F("Payload ("));
|
|
DBGPRINT(String(len));
|
|
DBGPRINT(F("): "));
|
|
ah::dumpBuf(mPayload, len);
|
|
|
|
record_t<> *rec = q->iv->getRecordStruct(q->cmd);
|
|
if(NULL == rec) {
|
|
DPRINTLN(DBG_ERROR, F("record is NULL!"));
|
|
return;
|
|
}
|
|
if((rec->pyldLen != len) && (0 != rec->pyldLen)) {
|
|
DPRINT(DBG_ERROR, F("plausibility check failed, expected "));
|
|
DBGPRINT(String(rec->pyldLen));
|
|
DBGPRINTLN(F(" bytes"));
|
|
q->iv->radioStatistics.rxFail++;
|
|
return;
|
|
}
|
|
|
|
q->iv->radioStatistics.rxSuccess++;
|
|
|
|
rec->ts = q->ts;
|
|
for (uint8_t i = 0; i < rec->length; i++) {
|
|
q->iv->addValue(i, mPayload, rec);
|
|
}
|
|
|
|
q->iv->rssi = rssi;
|
|
q->iv->doCalculations();
|
|
|
|
if(AlarmData == q->cmd) {
|
|
uint8_t i = 0;
|
|
while(1) {
|
|
if(0 == q->iv->parseAlarmLog(i++, mPayload, len))
|
|
break;
|
|
if (NULL != mCbAlarm)
|
|
(mCbAlarm)(q->iv);
|
|
yield();
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
enum class States : uint8_t {
|
|
RESET, START, WAIT, CHECK_FRAMES, CHECK_PACKAGE
|
|
};
|
|
|
|
typedef struct {
|
|
uint8_t buf[MAX_RF_PAYLOAD_SIZE];
|
|
uint8_t len;
|
|
int8_t rssi;
|
|
} frame_t;
|
|
|
|
private:
|
|
States mState = States::RESET;
|
|
uint32_t *mTimestamp;
|
|
uint32_t mWaitTimeout = 0;
|
|
std::array<frame_t, MAX_PAYLOAD_ENTRIES> mLocalBuf;
|
|
uint8_t mMaxFrameId;
|
|
uint8_t mPayload[150];
|
|
payloadListenerType mCbPayload = NULL;
|
|
alarmListenerType mCbAlarm = NULL;
|
|
};
|
|
|
|
#endif /*__COMMUNICATION_H__*/
|
|
|