Browse Source

Merge branch 'development03' into Zero-Export

pull/1155/head
DanielR92 2 years ago
parent
commit
34d23bf76f
  1. 13
      src/CHANGES.md
  2. 13
      src/app.cpp
  3. 12
      src/app.h
  4. 2
      src/appInterface.h
  5. 2
      src/defines.h
  6. 1
      src/hm/hmInverter.h
  7. 9
      src/hm/hmPayload.h
  8. 78
      src/hm/hmRadio.h
  9. 189
      src/hm/miPayload.h
  10. 16
      src/hm/radio.h
  11. 4
      src/hms/hmsRadio.h
  12. 19
      src/web/RestApi.h
  13. 2
      src/web/html/includes/nav.html
  14. 114
      src/web/html/serial.html
  15. 2
      src/web/html/setup.html
  16. 103
      src/web/html/visualization.html
  17. 5
      src/web/web.h

13
src/CHANGES.md

@ -1,5 +1,18 @@
# Development Changes # Development Changes
## 0.7.65 - 2023-10-02
* MI control command review #1197
## 0.7.64 - 2023-10-02
* moved active power control to modal in `live` view (per inverter) by click on current APC state
## 0.7.63 - 2023-10-01
* fix NRF24 communication #1200
## 0.7.62 - 2023-10-01
* fix communication to inverters #1198
* add timeout before payload is tried to process (necessary for HMS/HMT)
## 0.7.61 - 2023-10-01 ## 0.7.61 - 2023-10-01
* merged `hmPayload` and `hmsPayload` into single class * merged `hmPayload` and `hmsPayload` into single class
* merged generic radio functions into new parent class `radio.h` * merged generic radio functions into new parent class `radio.h`

13
src/app.cpp

@ -138,19 +138,19 @@ void app::loop(void) {
DBGPRINT(F("dBm | ")); DBGPRINT(F("dBm | "));
ah::dumpBuf(p->packet, p->len); ah::dumpBuf(p->packet, p->len);
} }
mNrfStat.frmCnt++;
Inverter<> *iv = mSys.findInverter(&p->packet[1]); Inverter<> *iv = mSys.findInverter(&p->packet[1]);
if (NULL != iv) { if (NULL != iv) {
iv->radioStatistics.frmCnt++;
if (IV_MI == iv->ivGen) if (IV_MI == iv->ivGen)
mMiPayload.add(iv, p); mMiPayload.add(iv, p);
else else
mPayload.add(iv, p); mPayload.add(iv, p);
} }
mNrfRadio.mBufCtrl.pop(); mNrfRadio.mBufCtrl.pop();
processPayload = true;
yield(); yield();
} }
processPayload = true;
mMiPayload.process(true); mMiPayload.process(true);
} }
#if defined(ESP32) #if defined(ESP32)
@ -165,17 +165,17 @@ void app::loop(void) {
DBGPRINT(F("dBm | ")); DBGPRINT(F("dBm | "));
ah::dumpBuf(p->packet, p->len); ah::dumpBuf(p->packet, p->len);
} }
mCmtStat.frmCnt++;
Inverter<> *iv = mSys.findInverter(&p->packet[1]); Inverter<> *iv = mSys.findInverter(&p->packet[1]);
if(NULL != iv) { if(NULL != iv) {
iv->radioStatistics.frmCnt++;
if((iv->ivGen == IV_HMS) || (iv->ivGen == IV_HMT)) if((iv->ivGen == IV_HMS) || (iv->ivGen == IV_HMT))
mPayload.add(iv, p); mPayload.add(iv, p);
} }
mCmtRadio.mBufCtrl.pop(); mCmtRadio.mBufCtrl.pop();
processPayload = true;
yield(); yield();
} }
processPayload = true;
} }
#endif #endif
@ -403,9 +403,9 @@ void app::tickSend(void) {
if(mConfig->nrf.enabled) { if(mConfig->nrf.enabled) {
if(!mNrfRadio.isChipConnected()) { if(!mNrfRadio.isChipConnected()) {
DPRINTLN(DBG_WARN, F("NRF24 not connected!")); DPRINTLN(DBG_WARN, F("NRF24 not connected!"));
return;
} }
} }
if (mIVCommunicationOn) { if (mIVCommunicationOn) {
if (!mNrfRadio.mBufCtrl.empty()) { if (!mNrfRadio.mBufCtrl.empty()) {
if (mConfig->serial.debug) { if (mConfig->serial.debug) {
@ -528,9 +528,6 @@ void app::resetSystem(void) {
mSaveReboot = false; mSaveReboot = false;
mNetworkConnected = false; mNetworkConnected = false;
memset(&mNrfStat, 0, sizeof(statistics_t));
memset(&mCmtStat, 0, sizeof(statistics_t));
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

12
src/app.h

@ -84,6 +84,7 @@ class app : public IApp, public ah::Scheduler {
#endif #endif
} }
} }
#ifdef ESP32 #ifdef ESP32
void handleHmsIntr(void) { void handleHmsIntr(void) {
mCmtRadio.handleIntr(); mCmtRadio.handleIntr();
@ -129,14 +130,6 @@ class app : public IApp, public ah::Scheduler {
return mSaveReboot; return mSaveReboot;
} }
statistics_t *getNrfStatistics() {
return &mNrfStat;
}
statistics_t *getCmtStatistics() {
return &mCmtStat;
}
#if !defined(ETHERNET) #if !defined(ETHERNET)
void scanAvailNetworks() { void scanAvailNetworks() {
mWifi.scanAvailNetworks(); mWifi.scanAvailNetworks();
@ -349,9 +342,6 @@ class app : public IApp, public ah::Scheduler {
bool mNetworkConnected; bool mNetworkConnected;
statistics_t mNrfStat;
statistics_t mCmtStat;
// mqtt // mqtt
PubMqttType mMqtt; PubMqttType mMqtt;
bool mMqttReconnect; bool mMqttReconnect;

2
src/appInterface.h

@ -32,8 +32,6 @@ class IApp {
virtual bool getShouldReboot() = 0; virtual bool getShouldReboot() = 0;
virtual void setRebootFlag() = 0; virtual void setRebootFlag() = 0;
virtual const char *getVersion() = 0; virtual const char *getVersion() = 0;
virtual statistics_t *getNrfStatistics() = 0;
virtual statistics_t *getCmtStatistics() = 0;
#if !defined(ETHERNET) #if !defined(ETHERNET)
virtual void scanAvailNetworks() = 0; virtual void scanAvailNetworks() = 0;

2
src/defines.h

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

1
src/hm/hmInverter.h

@ -173,6 +173,7 @@ class Inverter {
alarmCnt = 0; alarmCnt = 0;
alarmLastId = 0; alarmLastId = 0;
rssi = -127; rssi = -127;
memset(&radioStatistics, 0, sizeof(statistics_t));
} }
~Inverter() { ~Inverter() {

9
src/hm/hmPayload.h

@ -31,6 +31,7 @@ typedef struct {
bool requested; bool requested;
bool gotFragment; bool gotFragment;
bool rxTmo; bool rxTmo;
uint32_t sendMillis;
} invPayload_t; } invPayload_t;
@ -247,6 +248,11 @@ class HmPayload {
continue; // skip to next inverter continue; // skip to next inverter
} }
if((IV_HMS == iv->ivGen) || (IV_HMT == iv->ivGen)) {
if((mPayload[iv->id].sendMillis + 400) > millis())
return; // to fast, wait until packets are received!
}
if (!mPayload[iv->id].complete) { if (!mPayload[iv->id].complete) {
bool crcPass, pyldComplete, fastNext; bool crcPass, pyldComplete, fastNext;
@ -449,7 +455,7 @@ class HmPayload {
return false; return false;
//requests to cause the next request to be executed immediately //requests to cause the next request to be executed immediately
if (mPayload[iv->id].gotFragment && ((mPayload[iv->id].txCmd < 11) || (mPayload[iv->id].txCmd > 18))) { if (mPayload[iv->id].gotFragment && ((mPayload[iv->id].txCmd < RealTimeRunData_Debug) || (mPayload[iv->id].txCmd >= AlarmData))) {
*fastNext = true; *fastNext = true;
} }
@ -469,6 +475,7 @@ class HmPayload {
mPayload[id].requested = false; mPayload[id].requested = false;
mPayload[id].ts = *mTimestamp; mPayload[id].ts = *mTimestamp;
mPayload[id].rxTmo = setTxTmo; // design: don't start with complete retransmit mPayload[id].rxTmo = setTxTmo; // design: don't start with complete retransmit
mPayload[id].sendMillis = millis();
} }
IApp *mApp; IApp *mApp;

78
src/hm/hmRadio.h

@ -16,6 +16,11 @@
const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"}; const char* const rf24AmpPowerNames[] = {"MIN", "LOW", "HIGH", "MAX"};
#define TX_REQ_DREDCONTROL 0x50
#define DRED_A5 0xa5
#define DRED_5A 0x5a
#define DRED_AA 0xaa
#define DRED_55 0x55
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// HM Radio class // HM Radio class
@ -57,6 +62,7 @@ class HmRadio : public Radio {
pinMode(irq, INPUT_PULLUP); pinMode(irq, INPUT_PULLUP);
generateDtuSn(); generateDtuSn();
DTU_RADIO_ID = ((uint64_t)(((mDtuSn >> 24) & 0xFF) | ((mDtuSn >> 8) & 0xFF00) | ((mDtuSn << 8) & 0xFF0000) | ((mDtuSn << 24) & 0xFF000000)) << 8) | 0x01;
#ifdef ESP32 #ifdef ESP32
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 #if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
@ -81,7 +87,7 @@ class HmRadio : public Radio {
mNrf24.enableDynamicPayloads(); mNrf24.enableDynamicPayloads();
mNrf24.setCRCLength(RF24_CRC_16); mNrf24.setCRCLength(RF24_CRC_16);
mNrf24.setAddressWidth(5); mNrf24.setAddressWidth(5);
mNrf24.openReadingPipe(1, reinterpret_cast<uint8_t*>(&mDtuSn)); mNrf24.openReadingPipe(1, reinterpret_cast<uint8_t*>(&DTU_RADIO_ID));
// enable all receiving interrupts // enable all receiving interrupts
mNrf24.maskIRQ(false, false, false); mNrf24.maskIRQ(false, false, false);
@ -139,7 +145,7 @@ class HmRadio : public Radio {
return mNrf24.isChipConnected(); return mNrf24.isChipConnected();
} }
void sendControlPacket(Inverter<> *iv, 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, uint16_t powerMax = 0) {
DPRINT(DBG_INFO, F("sendControlPacket cmd: 0x")); DPRINT(DBG_INFO, F("sendControlPacket cmd: 0x"));
DBGHEXLN(cmd); DBGHEXLN(cmd);
initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME); initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME);
@ -155,31 +161,64 @@ class HmRadio : public Radio {
} }
} else { //MI 2nd gen. specific } else { //MI 2nd gen. specific
switch (cmd) { switch (cmd) {
case Restart:
case TurnOn: case TurnOn:
//mTxBuf[0] = 0x50; mTxBuf[9] = DRED_55;
mTxBuf[9] = 0x55; mTxBuf[10] = DRED_AA;
mTxBuf[10] = 0xaa;
break; break;
case TurnOff: case TurnOff:
mTxBuf[9] = 0xaa; mTxBuf[9] = DRED_AA;
mTxBuf[10] = 0x55; mTxBuf[10] = DRED_55;
break; break;
case ActivePowerContr: case ActivePowerContr:
mTxBuf[9] = 0x5a; if (data[1]<256) { // non persistent
mTxBuf[10] = 0x5a; mTxBuf[9] = DRED_5A;
mTxBuf[10] = DRED_5A;
//Testing only! Original NRF24_DTUMIesp.ino code #L612-L613: //Testing only! Original NRF24_DTUMIesp.ino code #L612-L613:
//UsrData[0]=0x5A;UsrData[1]=0x5A;UsrData[2]=100;//0x0a;// 10% limit //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 //UsrData[3]=((Limit*10) >> 8) & 0xFF; UsrData[4]= (Limit*10) & 0xFF; //WR needs 1 dec= zB 100.1 W
if (is4chMI) { 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 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! //works (if ever!) only for absulute power limits!
mTxBuf[cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit mTxBuf[++cnt] = ((data[0] * 10) >> 8) & 0xff; // power limit in W
mTxBuf[cnt++] = ((data[0] * 10) ) & 0xff; // power limit mTxBuf[++cnt] = ((data[0] * 10) ) & 0xff; // power limit in W
} else { } else if (powerMax) { //relative, but 4ch-MI (if ever) only accepts absolute values
mTxBuf[cnt++] = data[0]*10; // power limit 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] = TX_REQ_DREDCONTROL;
if (data[1] == 256UL) { // AbsolutPersistent
if (data[0] == 0 && !powerMax) {
mTxBuf[9] = DRED_A5;
mTxBuf[10] = DRED_A5;
} else if (data[0] == 0 || !powerMax || data[0] < powerMax/4 ) {
mTxBuf[9] = DRED_5A;
mTxBuf[10] = DRED_5A;
} else if (data[0] <= powerMax/4*3) {
mTxBuf[9] = DRED_AA;
mTxBuf[10] = DRED_55;
} else if (data[0] <= powerMax) {
mTxBuf[9] = DRED_5A;
mTxBuf[10] = DRED_55;
} else if (data[0] > powerMax*2) {
mTxBuf[9] = DRED_55;
mTxBuf[10] = DRED_AA;
}
}
} }
break; break;
default: default:
return; return;
@ -202,7 +241,7 @@ class HmRadio : public Radio {
std::queue<packet_t> mBufCtrl; std::queue<packet_t> mBufCtrl;
private: private:
bool getReceived(void) { inline bool getReceived(void) {
bool tx_ok, tx_fail, rx_ready; bool tx_ok, tx_fail, rx_ready;
mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // resets the IRQ pin to HIGH mNrf24.whatHappened(tx_ok, tx_fail, rx_ready); // resets the IRQ pin to HIGH
@ -232,7 +271,7 @@ class HmRadio : public Radio {
} }
void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) { void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) {
updateCrcs(len, appendCrc16); updateCrcs(&len, appendCrc16);
// set TX and RX channels // set TX and RX channels
mTxChIdx = (mTxChIdx + 1) % RF_CHANNELS; mTxChIdx = (mTxChIdx + 1) % RF_CHANNELS;
@ -262,6 +301,7 @@ class HmRadio : public Radio {
return iv->radioId.u64; return iv->radioId.u64;
} }
uint64_t DTU_RADIO_ID;
uint8_t mRfChLst[RF_CHANNELS]; uint8_t mRfChLst[RF_CHANNELS];
uint8_t mTxChIdx; uint8_t mTxChIdx;
uint8_t mRxChIdx; uint8_t mRxChIdx;

189
src/hm/miPayload.h

@ -10,20 +10,26 @@
#include "../config/config.h" #include "../config/config.h"
#include <Arduino.h> #include <Arduino.h>
#define MI_REQ_CH1 0x09
#define MI_REQ_CH2 0x11
#define MI_REQ_4CH 0x36
typedef struct { typedef struct {
uint32_t ts; uint32_t ts;
bool requested; bool requested;
bool limitrequested; bool limitrequested;
uint8_t txCmd; uint8_t txCmd;
uint8_t len[MAX_PAYLOAD_ENTRIES]; uint8_t len[MAX_PAYLOAD_ENTRIES];
int8_t rssi[4];
bool complete; bool complete;
bool dataAB[3]; bool dataAB[3];
bool stsAB[3]; bool stsAB[3];
uint16_t sts[6]; uint16_t sts[5];
uint8_t txId; uint8_t txId;
uint8_t invId; uint8_t invId;
uint8_t retransmits; uint8_t retransmits;
bool gotFragment; bool gotFragment;
bool gotGPF;
uint8_t rtrRes; // for limiting resets uint8_t rtrRes; // for limiting resets
uint8_t multi_parts; // for quality uint8_t multi_parts; // for quality
bool rxTmo; bool rxTmo;
@ -45,7 +51,8 @@ class MiPayload {
mTimestamp = timestamp; mTimestamp = timestamp;
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
reset(i, false, true); reset(i, false, true);
mPayload[i].limitrequested = true; mPayload[i].limitrequested = false;
mPayload[i].gotGPF = false;
} }
mSerialDebug = false; mSerialDebug = false;
mHighPrioIv = NULL; mHighPrioIv = NULL;
@ -120,10 +127,12 @@ class MiPayload {
DBGPRINT(F("Devcontrol request 0x")); DBGPRINT(F("Devcontrol request 0x"));
DHEX(iv->devControlCmd); DHEX(iv->devControlCmd);
DBGPRINT(F(" power limit ")); 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; iv->powerLimitAck = false;
iv->radio->sendControlPacket(iv, iv->devControlCmd, iv->powerLimit, false, false, iv->type == INV_TYPE_4CH); 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].txCmd = iv->devControlCmd;
mPayload[iv->id].limitrequested = true; mPayload[iv->id].limitrequested = true;
@ -132,21 +141,22 @@ class MiPayload {
uint8_t cmd = iv->getQueuedCmd(); uint8_t cmd = iv->getQueuedCmd();
uint8_t cmd2 = cmd; uint8_t cmd2 = cmd;
if ( cmd == SystemConfigPara ) { //0x05 for HM-types if ( cmd == SystemConfigPara ) { //0x05 for HM-types
if (!mPayload[iv->id].limitrequested) { // only do once at startup if (!mPayload[iv->id].gotGPF) {
iv->setQueuedCmdFinished(); iv->setQueuedCmdFinished();
cmd = iv->getQueuedCmd(); cmd = iv->getQueuedCmd();
} else {
mPayload[iv->id].limitrequested = false;
} }
} }
if (cmd == 0x01 || cmd == SystemConfigPara ) { //0x1 and 0x05 for HM-types if (cmd == 0x01) { //0x1 for HM-types
cmd2 = cmd == SystemConfigPara ? 0x01 : 0x00; //perhaps we can only try to get second frame? cmd2 = 0x00;
cmd = 0x0f; // for MI, these seem to make part of polling the device software and hardware version number command 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) { if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id); DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("prepareDevInformCmd 0x")); DBGPRINT(F("legacy cmd 0x"));
DBGHEXLN(cmd); DBGHEXLN(cmd);
} }
iv->radio->sendCmdPacket(iv, cmd, cmd2, false, false); iv->radio->sendCmdPacket(iv, cmd, cmd2, false, false);
@ -167,29 +177,32 @@ class MiPayload {
void add(Inverter<> *iv, packet_t *p) { void add(Inverter<> *iv, packet_t *p) {
//DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX)); //DPRINTLN(DBG_INFO, F("MI got data [0]=") + String(p->packet[0], HEX));
if (p->packet[0] == (0x08 + ALL_FRAMES)) { // 0x88; MI status response to 0x09 if (p->packet[0] == (0x88)) { // 0x88 is MI status response to 0x09
miStsDecode(iv, p); miStsDecode(iv, p);
} }
else if (p->packet[0] == (0x11 + SINGLE_FRAME)) { // 0x92; MI status response to 0x11 else if (p->packet[0] == (MI_REQ_CH2 + SINGLE_FRAME)) { // 0x92; MI status response to 0x11
miStsDecode(iv, p, CH2); miStsDecode(iv, p, CH2);
}
else if ( p->packet[0] == 0x09 + ALL_FRAMES || } else if ( p->packet[0] == MI_REQ_CH1 + ALL_FRAMES ||
p->packet[0] == 0x11 + ALL_FRAMES || p->packet[0] == MI_REQ_CH2 + ALL_FRAMES ||
( p->packet[0] >= (0x36 + ALL_FRAMES) && p->packet[0] < (0x39 + SINGLE_FRAME) ( 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].txCmd != 0x0f) ) { // small MI or MI 1500 data responses to 0x09, 0x11, 0x36, 0x37, 0x38 and 0x39
mPayload[iv->id].txId = p->packet[0]; mPayload[iv->id].txId = p->packet[0];
miDataDecode(iv,p); 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 // MI response from get hardware information request
miHwDecode(iv, p); miHwDecode(iv, p);
mPayload[iv->id].txId = p->packet[0]; 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 } 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! || (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... // atm, we just do nothing else than print out what we got...
// for decoding see xls- Data collection instructions - #147ff // for decoding see xls- Data collection instructions - #147ff
//mPayload[iv->id].txId = p->packet[0]; //mPayload[iv->id].txId = p->packet[0];
@ -207,9 +220,12 @@ class MiPayload {
} }
} else if (p->packet[0] == (TX_REQ_DEVCONTROL + ALL_FRAMES ) // response from dev control command } 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 || 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); DPRINT_IVID(DBG_DEBUG, iv->id);
DBGPRINTLN(F("Response from devcontrol request received")); DBGPRINTLN(F("Response from devcontrol request received"));
}
#endif
mPayload[iv->id].txId = p->packet[0]; mPayload[iv->id].txId = p->packet[0];
iv->clearDevControlRequest(); iv->clearDevControlRequest();
@ -224,7 +240,8 @@ class MiPayload {
DBGPRINTLN(String(iv->powerLimit[1])); DBGPRINTLN(String(iv->powerLimit[1]));
} }
iv->clearCmdQueue(); iv->clearCmdQueue();
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit //does not work for MI
//iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
} }
iv->devControlCmd = Init; iv->devControlCmd = Init;
} else { // some other response; copied from hmPayload:process; might not be correct to do that here!!! } else { // some other response; copied from hmPayload:process; might not be correct to do that here!!!
@ -298,14 +315,15 @@ class MiPayload {
if ( !mPayload[iv->id].complete && if ( !mPayload[iv->id].complete &&
(mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) && (mPayload[iv->id].txId != (TX_REQ_INFO + ALL_FRAMES)) &&
(mPayload[iv->id].txId < (0x36 + ALL_FRAMES)) && (mPayload[iv->id].txId < (MI_REQ_4CH + ALL_FRAMES)) &&
(mPayload[iv->id].txId > (0x39 + ALL_FRAMES)) && (mPayload[iv->id].txId > (0x39 + ALL_FRAMES)) &&
(mPayload[iv->id].txId != (0x09 + ALL_FRAMES)) && (mPayload[iv->id].txId != (MI_REQ_CH1 + ALL_FRAMES)) &&
(mPayload[iv->id].txId != (0x11 + ALL_FRAMES)) && (mPayload[iv->id].txId != (MI_REQ_CH2 + ALL_FRAMES)) &&
(mPayload[iv->id].txId != (0x88)) && (mPayload[iv->id].txId != (0x88)) &&
(mPayload[iv->id].txId != (0x92)) && (mPayload[iv->id].txId != (0x92)) &&
(mPayload[iv->id].txId != 0 && (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 // 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].complete = true;
mPayload[iv->id].rxTmo = true; mPayload[iv->id].rxTmo = true;
@ -326,7 +344,7 @@ class MiPayload {
} else if(iv->devControlCmd == ActivePowerContr) { } else if(iv->devControlCmd == ActivePowerContr) {
DPRINT_IVID(DBG_INFO, iv->id); DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("retransmit power limit")); DBGPRINTLN(F("retransmit power limit"));
iv->radio->sendControlPacket(iv, iv->devControlCmd, iv->powerLimit, true, false); iv->radio->sendControlPacket(iv, iv->devControlCmd, iv->powerLimit, true, false, (iv->powerLimit[1] == RelativNonPersistent) ? 0 : iv->getMaxPower());
} else { } else {
uint8_t cmd = mPayload[iv->id].txCmd; uint8_t cmd = mPayload[iv->id].txCmd;
if (mPayload[iv->id].retransmits < mMaxRetrans) { if (mPayload[iv->id].retransmits < mMaxRetrans) {
@ -335,6 +353,7 @@ class MiPayload {
DPRINT_IVID(DBG_INFO, iv->id); DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("nothing received")); DBGPRINTLN(F("nothing received"));
mPayload[iv->id].retransmits = mMaxRetrans; mPayload[iv->id].retransmits = mMaxRetrans;
mPayload[iv->id].requested = false; //close failed request
} else if( !mPayload[iv->id].gotFragment && !mPayload[iv->id].rxTmo ) { } else if( !mPayload[iv->id].gotFragment && !mPayload[iv->id].rxTmo ) {
DPRINT_IVID(DBG_INFO, iv->id); DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("retransmit on failed first request")); DBGPRINTLN(F("retransmit on failed first request"));
@ -346,24 +365,24 @@ class MiPayload {
mPayload[id].multi_parts = 0; mPayload[id].multi_parts = 0;
} else { } else {
bool change = false; bool change = false;
if ( cmd >= 0x36 && cmd < 0x39 ) { // MI-1500 Data command if ( cmd >= MI_REQ_4CH && cmd < 0x39 ) { // MI-1500 Data command
if (cmd > 0x36 && mPayload[iv->id].retransmits==1) // first request for the upper channels if (cmd > MI_REQ_4CH && mPayload[iv->id].retransmits==1) // first request for the upper channels
change = true; change = true;
} else if ( cmd == 0x09 ) {//MI single or dual channel device } 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].dataAB[CH1] && iv->type == INV_TYPE_2CH ) {
if (!mPayload[iv->id].stsAB[CH1] && mPayload[iv->id].retransmits<2) {} if (!mPayload[iv->id].stsAB[CH1] && mPayload[iv->id].retransmits<2) {}
//first try to get missing sts for first channel a second time //first try to get missing sts for first channel a second time
else if (!mPayload[iv->id].stsAB[CH2] || !mPayload[iv->id].dataAB[CH2] ) { else if (!mPayload[iv->id].stsAB[CH2] || !mPayload[iv->id].dataAB[CH2] ) {
cmd = 0x11; cmd = MI_REQ_CH2;
change = true; change = true;
if (mPayload[iv->id].rtrRes < 3) //only get back to first channel twice if (mPayload[iv->id].rtrRes < 3) //only get back to first channel twice
mPayload[iv->id].retransmits = 0; //reset counter mPayload[iv->id].retransmits = 0; //reset counter
} }
} }
} else if ( cmd == 0x11) { } else if ( cmd == MI_REQ_CH2) {
if ( mPayload[iv->id].dataAB[CH2] ) { // data + status ch2 are there? 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])) { if (mPayload[iv->id].stsAB[CH2] && (!mPayload[iv->id].stsAB[CH1] || !mPayload[iv->id].dataAB[CH1])) {
cmd = 0x09; cmd = MI_REQ_CH1;
change = true; change = true;
} }
} }
@ -408,19 +427,6 @@ class MiPayload {
if (!fastNext) { if (!fastNext) {
mPayload[iv->id].rxTmo = true; mPayload[iv->id].rxTmo = true;
} else { } else {
/*iv->setQueuedCmdFinished();
uint8_t cmd = iv->getQueuedCmd();
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("fast mode "));
DBGPRINT(F("prepareDevInformCmd 0x"));
DBGHEXLN(cmd);
}
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) if (mHighPrioIv == NULL)
mHighPrioIv = iv; mHighPrioIv = iv;
} }
@ -471,17 +477,29 @@ class MiPayload {
} }
uint16_t prntsts = statusMi == 3 ? 1 : statusMi; uint16_t prntsts = statusMi == 3 ? 1 : statusMi;
bool stsok = true;
if ( statusMi != mPayload[iv->id].sts[stschan] ) { //sth.'s changed? if ( statusMi != mPayload[iv->id].sts[stschan] ) { //sth.'s changed?
iv->alarmCnt = 1; // minimum... iv->alarmCnt = 1; // minimum...
if ((iv->type != INV_TYPE_1CH) && ((statusMi != 3) //sth is or was wrong! //sth is or was wrong?
|| ((mPayload[iv->id].sts[stschan] && statusMi == 3) && (mPayload[iv->id].sts[stschan] != 3))) 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->alarmCnt = iv->type == INV_TYPE_2CH ? 3 : 5;
iv->alarmLastId = iv->alarmMesIndex;
} }
iv->alarmLastId = prntsts; //iv->alarmMesIndex;
mPayload[iv->id].sts[stschan] = statusMi; 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) { if (mSerialDebug) {
DPRINT(DBG_WARN, F("New state on CH")); DPRINT(DBG_WARN, F("New state on CH"));
DBGPRINT(String(stschan)); DBGPRINT(F(" (")); DBGPRINT(String(stschan)); DBGPRINT(F(" ("));
@ -490,9 +508,9 @@ class MiPayload {
} }
} }
if ( !mPayload[iv->id].sts[0] || prntsts < mPayload[iv->id].sts[0] ) { if (!stsok) {
mPayload[iv->id].sts[0] = prntsts;
iv->setValue(iv->getPosByChFld(0, FLD_EVT, rec), rec, prntsts); 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)]) { if (iv->alarmMesIndex < rec->record[iv->getPosByChFld(0, FLD_EVT, rec)]) {
@ -502,18 +520,7 @@ class MiPayload {
DBGPRINT(F("alarm ID incremented to ")); DBGPRINT(F("alarm ID incremented to "));
DBGPRINTLN(String(iv->alarmMesIndex)); 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;
while(1) {
if(0 == iv->parseAlarmLog(i++, payload, payloadLen))
break;
if (NULL != mCbAlarm)
(mCbAlarm)(iv);
yield();
} }
}*/
} }
void miDataDecode(Inverter<> *iv, packet_t *p) { void miDataDecode(Inverter<> *iv, packet_t *p) {
@ -522,8 +529,8 @@ class MiPayload {
mPayload[iv->id].gotFragment = true; mPayload[iv->id].gotFragment = true;
mPayload[iv->id].multi_parts += 4; mPayload[iv->id].multi_parts += 4;
uint8_t datachan = ( p->packet[0] == 0x89 || p->packet[0] == (0x36 + ALL_FRAMES) ) ? CH1 : uint8_t datachan = ( p->packet[0] == (MI_REQ_CH1 + ALL_FRAMES) || p->packet[0] == (MI_REQ_4CH + ALL_FRAMES) ) ? CH1 :
( p->packet[0] == 0x91 || p->packet[0] == (0x37 + ALL_FRAMES) ) ? CH2 : ( p->packet[0] == (MI_REQ_CH2 + ALL_FRAMES) || p->packet[0] == (0x37 + ALL_FRAMES) ) ? CH2 :
p->packet[0] == (0x38 + ALL_FRAMES) ? CH3 : p->packet[0] == (0x38 + ALL_FRAMES) ? CH3 :
CH4; CH4;
// count in RF_communication_protocol.xlsx is with offset = -1 // count in RF_communication_protocol.xlsx is with offset = -1
@ -540,6 +547,7 @@ class MiPayload {
yield(); 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_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))); iv->setValue(iv->getPosByChFld(0, FLD_IRR, rec), rec, (float) (calcIrradiation(iv, datachan)));
mPayload[iv->id].rssi[(datachan-1)] = p->rssi;
if ( datachan < 3 ) { if ( datachan < 3 ) {
mPayload[iv->id].dataAB[datachan] = true; mPayload[iv->id].dataAB[datachan] = true;
@ -548,7 +556,7 @@ class MiPayload {
mPayload[iv->id].dataAB[CH0] = true; mPayload[iv->id].dataAB[CH0] = true;
} }
if (p->packet[0] >= (0x36 + ALL_FRAMES) ) { if (p->packet[0] >= (MI_REQ_4CH + ALL_FRAMES) ) {
/*For MI1500: /*For MI1500:
if (MI1500) { if (MI1500) {
STAT = (uint8_t)(p->packet[25] ); STAT = (uint8_t)(p->packet[25] );
@ -572,8 +580,10 @@ class MiPayload {
if ( mPayload[iv->id].complete ) if ( mPayload[iv->id].complete )
return; //if we got second message as well in repreated attempt return; //if we got second message as well in repreated attempt
mPayload[iv->id].complete = true; mPayload[iv->id].complete = true;
if (mSerialDebug) {
DPRINT_IVID(DBG_INFO, iv->id); DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINTLN(F("got all msgs")); DBGPRINTLN(F("got all msgs"));
}
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, calcYieldDayCh0(iv,0)); iv->setValue(iv->getPosByChFld(0, FLD_YD, rec), rec, calcYieldDayCh0(iv,0));
@ -587,6 +597,14 @@ class MiPayload {
} }
ac_pow = (int) (ac_pow*9.5); ac_pow = (int) (ac_pow*9.5);
iv->setValue(iv->getPosByChFld(0, FLD_PAC, rec), rec, (float) ac_pow/10); 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(); iv->doCalculations();
// update status state-machine, // update status state-machine,
@ -609,14 +627,14 @@ class MiPayload {
if(!*complete) { if(!*complete) {
DPRINTLN(DBG_VERBOSE, F("incomlete, txCmd is 0x") + String(txCmd, HEX)); DPRINTLN(DBG_VERBOSE, F("incomlete, txCmd is 0x") + String(txCmd, HEX));
//we got some delayed status msgs?!? //we got some delayed status msgs?!?
if ((txCmd == 0x09) || (txCmd == 0x11)) { if ((txCmd == MI_REQ_CH1) || (txCmd == MI_REQ_CH2)) {
if (mPayload[iv->id].stsAB[CH0] && mPayload[iv->id].dataAB[CH0]) { if (mPayload[iv->id].stsAB[CH0] && mPayload[iv->id].dataAB[CH0]) {
miComplete(iv); miComplete(iv);
return true; return true;
} }
return false; return false;
} }
if (txCmd >= 0x36 && txCmd <= 0x39) { if (txCmd >= MI_REQ_4CH && txCmd <= 0x39) {
return false; return false;
} }
if (txCmd == 0x0f) { //hw info request, at least hw part nr. and version have to be there... if (txCmd == 0x0f) { //hw info request, at least hw part nr. and version have to be there...
@ -751,6 +769,38 @@ const byteAssign_t InfoAssignment[] = {
mPayload[iv->id].requested= false; mPayload[iv->id].requested= false;
iv->radioStatistics.rxSuccess++; 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) { void reset(uint8_t id, bool setTxTmo = true, bool clrSts = false) {
@ -770,13 +820,12 @@ const byteAssign_t InfoAssignment[] = {
mPayload[id].txCmd = 0; mPayload[id].txCmd = 0;
mPayload[id].requested = false; mPayload[id].requested = false;
mPayload[id].ts = *mTimestamp; mPayload[id].ts = *mTimestamp;
mPayload[id].sts[0] = 0;
if (clrSts) { // only clear channel states at startup if (clrSts) { // only clear channel states at startup
mPayload[id].sts[0] = 0;
mPayload[id].sts[CH1] = 0; mPayload[id].sts[CH1] = 0;
mPayload[id].sts[CH2] = 0; mPayload[id].sts[CH2] = 0;
mPayload[id].sts[CH3] = 0; mPayload[id].sts[CH3] = 0;
mPayload[id].sts[CH4] = 0; mPayload[id].sts[CH4] = 0;
mPayload[id].sts[5] = 0; //remember last summarized state
} }
} }

16
src/hm/radio.h

@ -21,7 +21,7 @@ class Inverter;
// abstract radio interface // abstract radio interface
class Radio { class Radio {
public: public:
virtual void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit, bool isNoMI = true, bool is4chMI = false) = 0; virtual void sendControlPacket(Inverter<> *iv, uint8_t cmd, uint16_t *data, bool isRetransmit, bool isNoMI = true, uint16_t powerMax = 0) = 0;
virtual bool switchFrequency(Inverter<> *iv, uint32_t fromkHz, uint32_t tokHz) { return true; } virtual bool switchFrequency(Inverter<> *iv, uint32_t fromkHz, uint32_t tokHz) { return true; }
void handleIntr(void) { void handleIntr(void) {
@ -64,17 +64,17 @@ class Radio {
memset(&mTxBuf[10], 0x00, (MAX_RF_PAYLOAD_SIZE-10)); memset(&mTxBuf[10], 0x00, (MAX_RF_PAYLOAD_SIZE-10));
} }
void updateCrcs(uint8_t len, bool appendCrc16=true) { void updateCrcs(uint8_t *len, bool appendCrc16=true) {
// append crc's // append crc's
if (appendCrc16 && (len > 10)) { if (appendCrc16 && (*len > 10)) {
// crc control data // crc control data
uint16_t crc = ah::crc16(&mTxBuf[10], len - 10); uint16_t crc = ah::crc16(&mTxBuf[10], *len - 10);
mTxBuf[len++] = (crc >> 8) & 0xff; mTxBuf[(*len)++] = (crc >> 8) & 0xff;
mTxBuf[len++] = (crc ) & 0xff; mTxBuf[(*len)++] = (crc ) & 0xff;
} }
// crc over all // crc over all
mTxBuf[len] = ah::crc8(mTxBuf, len); mTxBuf[*len] = ah::crc8(mTxBuf, *len);
len++; (*len)++;
} }
void generateDtuSn(void) { void generateDtuSn(void) {

4
src/hms/hmsRadio.h

@ -47,7 +47,7 @@ class CmtRadio : public Radio {
return mCmtAvail; return mCmtAvail;
} }
void sendControlPacket(Inverter<> *iv, 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, uint16_t powerMax = 0) {
DPRINT(DBG_INFO, F("sendControlPacket cmd: 0x")); DPRINT(DBG_INFO, F("sendControlPacket cmd: 0x"));
DBGHEXLN(cmd); DBGHEXLN(cmd);
initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME); initPacket(iv->radioId.u64, TX_REQ_DEVCONTROL, SINGLE_FRAME);
@ -82,7 +82,7 @@ class CmtRadio : public Radio {
private: private:
void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) { void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) {
updateCrcs(len, appendCrc16); updateCrcs(&len, appendCrc16);
if(mSerialDebug) { if(mSerialDebug) {
DPRINT(DBG_INFO, F("TX ")); DPRINT(DBG_INFO, F("TX "));

19
src/web/RestApi.h

@ -39,7 +39,6 @@ class RestApi {
mHeapFree = 0; mHeapFree = 0;
mHeapFreeBlk = 0; mHeapFreeBlk = 0;
mHeapFrag = 0; mHeapFrag = 0;
nr = 0;
} }
void setup(IApp *app, HMSYSTEM *sys, AsyncWebServer *srv, settings_t *config) { void setup(IApp *app, HMSYSTEM *sys, AsyncWebServer *srv, settings_t *config) {
@ -63,9 +62,6 @@ class RestApi {
} }
void ctrlRequest(JsonObject obj) { void ctrlRequest(JsonObject obj) {
char out[128];
serializeJson(obj, out, 128);
//DPRINTLN(DBG_INFO, "RestApi: " + String(out));
DynamicJsonDocument json(128); DynamicJsonDocument json(128);
JsonObject dummy = json.as<JsonObject>(); JsonObject dummy = json.as<JsonObject>();
if(obj[F("path")] == "ctrl") if(obj[F("path")] == "ctrl")
@ -109,6 +105,8 @@ class RestApi {
getIvVersion(root, request->url().substring(22).toInt()); getIvVersion(root, request->url().substring(22).toInt());
else if(path.substring(0, 19) == "inverter/radiostat/") else if(path.substring(0, 19) == "inverter/radiostat/")
getIvStatistis(root, request->url().substring(24).toInt()); getIvStatistis(root, request->url().substring(24).toInt());
else if(path.substring(0, 16) == "inverter/pwrack/")
getIvPowerLimitAck(root, request->url().substring(21).toInt());
else else
getNotFound(root, F("http://") + request->host() + F("/api/")); getNotFound(root, F("http://") + request->host() + F("/api/"));
} }
@ -280,7 +278,7 @@ class RestApi {
void getHtmlSystem(AsyncWebServerRequest *request, JsonObject obj) { void getHtmlSystem(AsyncWebServerRequest *request, JsonObject obj) {
getSysInfo(request, obj.createNestedObject(F("system"))); getSysInfo(request, obj.createNestedObject(F("system")));
getGeneric(request, obj.createNestedObject(F("generic"))); getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>"); obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">AhoyFactory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
} }
void getHtmlLogout(AsyncWebServerRequest *request, JsonObject obj) { void getHtmlLogout(AsyncWebServerRequest *request, JsonObject obj) {
@ -326,6 +324,15 @@ class RestApi {
obj[F("retransmits")] = iv->radioStatistics.retransmits; obj[F("retransmits")] = iv->radioStatistics.retransmits;
} }
void getIvPowerLimitAck(JsonObject obj, uint8_t id) {
Inverter<> *iv = mSys->getInverterByPos(id);
if(NULL == iv) {
obj[F("error")] = F("inverter not found!");
return;
}
obj["ack"] = (bool)iv->powerLimitAck;
}
void getInverterList(JsonObject obj) { void getInverterList(JsonObject obj) {
JsonArray invArr = obj.createNestedArray(F("inverter")); JsonArray invArr = obj.createNestedArray(F("inverter"));
@ -675,6 +682,7 @@ class RestApi {
jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as<String>(); jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as<String>();
return false; return false;
} }
jsonOut[F("id")] = jsonIn[F("id")];
if(F("power") == jsonIn[F("cmd")]) if(F("power") == jsonIn[F("cmd")])
accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff); accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff);
@ -745,7 +753,6 @@ class RestApi {
uint32_t mTimezoneOffset; uint32_t mTimezoneOffset;
uint32_t mHeapFree, mHeapFreeBlk; uint32_t mHeapFree, mHeapFreeBlk;
uint8_t mHeapFrag; uint8_t mHeapFrag;
uint16_t nr;
}; };
#endif /*__WEB_API_H__*/ #endif /*__WEB_API_H__*/

2
src/web/html/includes/nav.html

@ -7,7 +7,7 @@
</a> </a>
<div id="topnav" class="mobile"> <div id="topnav" class="mobile">
<a id="nav3" class="hide" href="/live?v={#VERSION}">Live</a> <a id="nav3" class="hide" href="/live?v={#VERSION}">Live</a>
<a id="nav4" class="hide" href="/serial?v={#VERSION}">Serial / Control</a> <a id="nav4" class="hide" href="/serial?v={#VERSION}">Webserial</a>
<a id="nav5" class="hide" href="/setup?v={#VERSION}">Settings</a> <a id="nav5" class="hide" href="/setup?v={#VERSION}">Settings</a>
<span class="seperator"></span> <span class="seperator"></span>
<a id="nav6" class="hide" href="/update?v={#VERSION}">Update</a> <a id="nav6" class="hide" href="/update?v={#VERSION}">Update</a>

114
src/web/html/serial.html

@ -12,53 +12,13 @@
<textarea id="serial" class="mt-3" cols="80" rows="20" readonly></textarea> <textarea id="serial" class="mt-3" cols="80" rows="20" readonly></textarea>
</div> </div>
<div class="row my-3"> <div class="row my-3">
<div class="col-3">connected: <span class="dot" id="connected"></span></div> <div class="col-3">console active: <span class="dot" id="active"></span></div>
<div class="col-3 col-sm-4 my-3">Uptime: <span id="uptime"></span></div> <div class="col-3 col-sm-4 my-3">Uptime: <span id="uptime"></span></div>
<div class="col-6 col-sm-4"> <div class="col-6 col-sm-4 a-r">
<input type="button" value="clear" class="btn" id="clear"/> <input type="button" value="clear" class="btn" id="clear"/>
<input type="button" value="autoscroll" class="btn" id="scroll"/> <input type="button" value="autoscroll" class="btn" id="scroll"/>
</div> </div>
</div> </div>
<div class="hr my-3"></div>
<div class="row mb-3">
<h3>Commands</h3>
</div>
<div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Select Inverter</div>
<div class="col-12 col-sm-9"><select name="iv" id="InvID"></select></div>
</div>
<div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Power Limit Command</div>
<div class="col-12 col-sm-9">
<select name="pwrlimctrl">
<option value="" selected disabled hidden>select the unit and persistence</option>
<option value="limit_nonpersistent_absolute">absolute non persistent [W]</option>
<option value="limit_nonpersistent_relative">relative non persistent [%]</option>
<option value="limit_persistent_absolute">absolute persistent [W]</option>
<option value="limit_persistent_relative">relative persistent [%]</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Power Limit Value</div>
<div class="col-12 col-sm-9"><input type="number" name="pwrlimval" maxlength="4"/></div>
</div>
<div class="row mb-3">
<div class="col-12 col-sm-3"></div>
<div class="col-12 col-sm-9"><input type="button" value="Send Power Limit" class="btn" id="sendpwrlim"/></div>
</div>
<div class="row mb-3">
<div class="col-12 col-sm-3 my-2">Control Inverter</div>
<div class="col-12 col-sm-9" id="power">
<input type="button" value="Restart" class="btn" id="restart"/>
<input type="button" value="Turn Off" class="btn" id="power_off"/>
<input type="button" value="Turn On" class="btn" id="power_on"/>
</div>
</div>
<div class="row mb-5">
<div class="col-3 my-2">Ctrl result</div>
<div class="col-9 my-2"><span id="result">n/a</span></div>
</div>
</div> </div>
</div> </div>
{#HTML_FOOTER} {#HTML_FOOTER}
@ -84,23 +44,11 @@
parseESP(obj); parseESP(obj);
window.setInterval("getAjax('/api/generic', parseGeneric)", 10000); window.setInterval("getAjax('/api/generic', parseGeneric)", 10000);
exeOnce = false; exeOnce = false;
getAjax("/api/inverter/list", parse); setTimeOffset();
}
} }
function parse(root) {
select = document.getElementById('InvID');
if(null == root) return;
root = root.inverter;
for(var i = 0; i < root.length; i++) {
inv = root[i];
var opt = document.createElement('option');
opt.value = inv.id;
opt.innerHTML = inv.name;
select.appendChild(opt);
} }
function setTimeOffset() {
// set time offset for serial console // set time offset for serial console
var obj = new Object(); var obj = new Object();
obj.cmd = "serial_utc_offset"; obj.cmd = "serial_utc_offset";
@ -119,12 +67,12 @@
if (!!window.EventSource) { if (!!window.EventSource) {
var source = new EventSource('/events'); var source = new EventSource('/events');
source.addEventListener('open', function(e) { source.addEventListener('open', function(e) {
document.getElementById("connected").style.backgroundColor = "#0c0"; document.getElementById("active").style.backgroundColor = "#0c0";
}, false); }, false);
source.addEventListener('error', function(e) { source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) { if (e.target.readyState != EventSource.OPEN) {
document.getElementById("connected").style.backgroundColor = "#f00"; document.getElementById("active").style.backgroundColor = "#f00";
} }
}, false); }, false);
@ -135,56 +83,6 @@
}, false); }, false);
} }
function ctrlCb(obj) {
var e = document.getElementById("result");
if(obj["success"])
e.innerHTML = "ok";
else
e.innerHTML = "Error: " + obj["error"];
}
function get_selected_iv() {
var e = document.getElementById("InvID");
return parseInt(e.value);
}
const wrapper = document.getElementById('power');
wrapper.addEventListener('click', (event) => {
var obj = new Object();
obj.id = get_selected_iv();
obj.cmd = "power";
switch (event.target.value) {
default:
case "Turn On":
obj.val = 1;
break;
case "Turn Off":
obj.val = 0;
break;
}
getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj));
});
document.getElementById("sendpwrlim").addEventListener("click", function() {
var val = parseInt(document.getElementsByName('pwrlimval')[0].value);
var cmd = document.getElementsByName('pwrlimctrl')[0].value;
if(isNaN(val)) {
document.getElementById("result").textContent = "value is missing";
return;
}
var obj = new Object();
obj.id = get_selected_iv();
obj.cmd = cmd;
obj.val = val;
getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj));
});
getAjax("/api/generic", parseGeneric); getAjax("/api/generic", parseGeneric);
</script> </script>
</body> </body>

2
src/web/html/setup.html

@ -728,7 +728,7 @@
if(!obj["pwd_set"]) if(!obj["pwd_set"])
e.value = ""; e.value = "";
var d = document.getElementById("prot_mask"); var d = document.getElementById("prot_mask");
var a = ["Index", "Live", "Serial / Console", "Settings", "Update", "System"]; var a = ["Index", "Live", "Webserial", "Settings", "Update", "System"];
var el = []; var el = [];
for(var i = 0; i < 6; i++) { for(var i = 0; i < 6; i++) {
var chk = ((obj["prot_mask"] & (1 << i)) == (1 << i)); var chk = ((obj["prot_mask"] & (1 << i)) == (1 << i));

103
src/web/html/visualization.html

@ -21,6 +21,7 @@
var mIvHtml = []; var mIvHtml = [];
var mNum = 0; var mNum = 0;
var total = Array(5).fill(0); var total = Array(5).fill(0);
var tPwrAck;
function parseGeneric(obj) { function parseGeneric(obj) {
if(true == exeOnce){ if(true == exeOnce){
@ -114,8 +115,10 @@
ml("div", {class: "col mx-2 mx-md-1"}, ml("span", { class: "pointer", onclick: function() { ml("div", {class: "col mx-2 mx-md-1"}, ml("span", { class: "pointer", onclick: function() {
getAjax("/api/inverter/version/" + obj.id, parseIvVersion); getAjax("/api/inverter/version/" + obj.id, parseIvVersion);
}}, obj.name)), }}, obj.name)),
ml("div", {class: "col a-c d-none d-sm-block"}, "Active Power Control: " + pwrLimit), ml("div", {class: "col a-c", onclick: function() {limitModal(obj)}}, [
ml("div", {class: "col a-c d-block d-sm-none"}, "APC: " + pwrLimit), ml("span", {class: "d-none d-sm-block pointer"}, "Active Power Control: " + pwrLimit),
ml("span", {class: "d-block d-sm-none pointer"}, "APC: " + pwrLimit)
]),
ml("div", {class: "col a-c"}, ml("span", { class: "pointer", onclick: function() { ml("div", {class: "col a-c"}, ml("span", { class: "pointer", onclick: function() {
getAjax("/api/inverter/alarm/" + obj.id, parseIvAlarm); getAjax("/api/inverter/alarm/" + obj.id, parseIvAlarm);
}}, ("Alarms: " + obj.alarm_cnt))), }}, ("Alarms: " + obj.alarm_cnt))),
@ -306,6 +309,102 @@
modal("Radio statistics for inverter " + obj.name, ml("div", {}, html)); modal("Radio statistics for inverter " + obj.name, ml("div", {}, html));
} }
function limitModal(obj) {
var opt = [["pct", "%"], ["watt", "W"]];
var html = ml("div", {}, [
ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-12 col-sm-5 my-2"}, "Limit Value"),
ml("div", {class: "col-8 col-sm-5"}, ml("input", {name: "limit", type: "number"}, "")),
ml("div", {class: "col-4 col-sm-2"}, sel("type", opt, "pct"))
]),
ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-8 col-sm-5"}, "Keep limit over inverter restart"),
ml("div", {class: "col-4 col-sm-7"}, ml("input", {type: "checkbox", name: "keep"}))
]),
ml("div", {class: "row my-3"},
ml("div", {class: "col a-r"}, ml("input", {type: "button", value: "Apply", class: "btn", onclick: function() {
applyLimit(obj.id);
}}, null))
),
ml("div", {class: "row my-4"}, [
ml("div", {class: "col-12 col-sm-5 my-2"}, "Control"),
ml("div", {class: "col col-sm-7 a-r"}, [
ml("input", {type: "button", value: "restart", class: "btn", onclick: function() {
applyCtrl(obj.id, "restart");
}}, null),
ml("input", {type: "button", value: "turn off", class: "btn mx-1", onclick: function() {
applyCtrl(obj.id, "power", 0);
}}, null),
ml("input", {type: "button", value: "turn on", class: "btn", onclick: function() {
applyCtrl(obj.id, "power", 1);
}}, null)
])
]),
ml("div", {class: "row mt-1"}, [
ml("div", {class: "col-12 col-sm-5 my-2"}, "Result"),
ml("div", {class: "col-sm-7 my-2"}, ml("span", {name: "pwrres"}, "-"))
])
]);
modal("Active Power Control for inverter " + obj.name, html);
}
function applyLimit(id) {
var cmd = "limit_";
if(!document.getElementsByName("keep")[0].checked)
cmd += "non";
cmd += "persistent_";
if(document.getElementsByName("type")[0].value == "pct")
cmd += "relative";
else
cmd += "absolute";
var val = document.getElementsByName("limit")[0].value;
if(isNaN(val))
val = 100;
var obj = new Object();
obj.id = id;
obj.cmd = cmd;
obj.val = val;
getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj));
}
function applyCtrl(id, cmd, val=0) {
var obj = new Object();
obj.id = id;
obj.cmd = cmd;
obj.val = val;
getAjax("/api/ctrl", ctrlCb2, "POST", JSON.stringify(obj));
}
function ctrlCb(obj) {
var e = document.getElementsByName("pwrres")[0];
if(obj.success) {
e.innerHTML = "received command, waiting for inverter acknowledge ...";
tPwrAck = window.setInterval("getAjax('/api/inverter/pwrack/" + obj.id + "', updatePwrAck)", 1000);
}
else
e.innerHTML = "Error: " + obj["error"];
}
function ctrlCb2(obj) {
var e = document.getElementsByName("pwrres")[0];
if(obj.success)
e.innerHTML = "command received";
else
e.innerHTML = "Error: " + obj["error"];
}
function updatePwrAck(obj) {
if(!obj.ack)
return;
var e = document.getElementsByName("pwrres")[0];
clearInterval(tPwrAck);
if(null == e)
return;
e.innerHTML = "inverter acknowledged active power control command";
}
function parse(obj) { function parse(obj) {
if(null != obj) { if(null != obj) {
parseGeneric(obj["generic"]); parseGeneric(obj["generic"]);

5
src/web/web.h

@ -739,13 +739,14 @@ class Web {
metrics += String(type) + String(topic); metrics += String(type) + String(topic);
// NRF Statistics // NRF Statistics
stat = mApp->getNrfStatistics(); // @TODO 2023-10-01: the statistic data is now available per inverter
/*stat = mApp->getNrfStatistics();
metrics += radioStatistic(F("rx_success"), stat->rxSuccess); metrics += radioStatistic(F("rx_success"), stat->rxSuccess);
metrics += radioStatistic(F("rx_fail"), stat->rxFail); metrics += radioStatistic(F("rx_fail"), stat->rxFail);
metrics += radioStatistic(F("rx_fail_answer"), stat->rxFailNoAnser); metrics += radioStatistic(F("rx_fail_answer"), stat->rxFailNoAnser);
metrics += radioStatistic(F("frame_cnt"), stat->frmCnt); metrics += radioStatistic(F("frame_cnt"), stat->frmCnt);
metrics += radioStatistic(F("tx_cnt"), stat->txCnt); metrics += radioStatistic(F("tx_cnt"), stat->txCnt);
metrics += radioStatistic(F("retrans_cnt"), stat->retransmits); metrics += radioStatistic(F("retrans_cnt"), stat->retransmits);*/
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
// Next is Inverter information // Next is Inverter information

Loading…
Cancel
Save