Browse Source

0.7.29

* MqTT alarm data was never sent, fixed
* REST API: added alarm data
* REST API: made get record obsolete
* REST API: added power limit acknowledge `/api/inverter/id/[0-x]` #1072
pull/1093/head
lumapu 1 year ago
parent
commit
e5b5972cae
  1. 6
      src/CHANGES.md
  2. 7
      src/app.cpp
  3. 4
      src/app.h
  4. 2
      src/defines.h
  5. 42
      src/hm/hmInverter.h
  6. 23
      src/hm/hmPayload.h
  7. 17
      src/hm/miPayload.h
  8. 35
      src/hms/hmsPayload.h
  9. 76
      src/publisher/pubMqtt.h
  10. 6
      src/publisher/pubMqttDefs.h
  11. 121
      src/web/RestApi.h

6
src/CHANGES.md

@ -1,5 +1,11 @@
# Development Changes # Development Changes
## 0.7.29 - 2023-08-09
* MqTT alarm data was never sent, fixed
* REST API: added alarm data
* REST API: made get record obsolete
* REST API: added power limit acknowledge `/api/inverter/id/[0-x]` #1072
## 0.7.28 - 2023-08-08 ## 0.7.28 - 2023-08-08
* fix MI inverter support #1078 * fix MI inverter support #1078

7
src/app.cpp

@ -99,8 +99,11 @@ void app::setup() {
if (mMqttEnabled) { if (mMqttEnabled) {
mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp, &mUptime); mMqtt.setup(&mConfig->mqtt, mConfig->sys.deviceName, mVersion, &mSys, &mTimestamp, &mUptime);
mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1)); mMqtt.setSubscriptionCb(std::bind(&app::mqttSubRxCb, this, std::placeholders::_1));
mPayload.addAlarmListener(std::bind(&PubMqttType::alarmEventListener, &mMqtt, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); mPayload.addAlarmListener([this](Inverter<> *iv) { mMqtt.alarmEvent(iv); });
mMiPayload.addAlarmListener(std::bind(&PubMqttType::alarmEventListener, &mMqtt, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); mMiPayload.addAlarmListener([this](Inverter<> *iv) { mMqtt.alarmEvent(iv); });
#if defined(ESP32)
mHmsPayload.addAlarmListener([this](Inverter<> *iv) { mMqtt.alarmEvent(iv); });
#endif
} }
#endif #endif
setupLed(); setupLed();

4
src/app.h

@ -177,6 +177,10 @@ class app : public IApp, public ah::Scheduler {
mPayload.ivSendHighPrio(iv); mPayload.ivSendHighPrio(iv);
else if (iv->ivGen == IV_MI) else if (iv->ivGen == IV_MI)
mMiPayload.ivSendHighPrio(iv); mMiPayload.ivSendHighPrio(iv);
#if defined(ESP32)
else if((iv->ivGen == IV_HMS) || (iv->ivGen == IV_HMT))
mHmsPayload.ivSendHighPrio(iv);
#endif
} }
} }

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 28 #define VERSION_PATCH 29
//------------------------------------- //-------------------------------------
typedef struct { typedef struct {

42
src/hm/hmInverter.h

@ -66,6 +66,14 @@ struct record_t {
uint8_t pyldLen; // expected payload length for plausibility check uint8_t pyldLen; // expected payload length for plausibility check
}; };
struct alarm_t {
uint16_t code;
uint32_t start;
uint32_t end;
alarm_t(uint16_t c, uint32_t s, uint32_t e) : code(c), start(s), end(e) {}
alarm_t() : code(0), start(0), end(0) {}
};
class CommandAbstract { class CommandAbstract {
public: public:
CommandAbstract(uint8_t txType = 0, uint8_t cmd = 0) { CommandAbstract(uint8_t txType = 0, uint8_t cmd = 0) {
@ -120,6 +128,7 @@ class Inverter {
uint16_t alarmMesIndex; // Last recorded Alarm Message Index uint16_t alarmMesIndex; // Last recorded Alarm Message Index
uint16_t powerLimit[2]; // limit power output uint16_t powerLimit[2]; // limit power output
float actPowerLimit; // actual power limit float actPowerLimit; // actual power limit
bool powerLimitAck; // acknowledged power limit (default: false)
uint8_t devControlCmd; // carries the requested cmd uint8_t devControlCmd; // carries the requested cmd
serial_u radioId; // id converted to modbus serial_u radioId; // id converted to modbus
uint8_t channels; // number of PV channels (1-4) uint8_t channels; // number of PV channels (1-4)
@ -131,6 +140,10 @@ class Inverter {
bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null) bool initialized; // needed to check if the inverter was correctly added (ESP32 specific - union types are never null)
bool isConnected; // shows if inverter was successfully identified (fw version and hardware info) bool isConnected; // shows if inverter was successfully identified (fw version and hardware info)
InverterStatus status; // indicates the current inverter status InverterStatus status; // indicates the current inverter status
std::array<alarm_t, 10> lastAlarm; // holds last 10 alarms
uint8_t alarmNxtWrPos; // indicates the position in array (rolling buffer)
uint16_t alarmCnt; // counts the total number of occured alarms
static uint32_t *timestamp; // system timestamp static uint32_t *timestamp; // system timestamp
static cfgInst_t *generalConfig; // general inverter configuration from setup static cfgInst_t *generalConfig; // general inverter configuration from setup
@ -139,6 +152,7 @@ class Inverter {
ivGen = IV_HM; ivGen = IV_HM;
powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited
powerLimit[1] = AbsolutNonPersistent; // default power limit setting powerLimit[1] = AbsolutNonPersistent; // default power limit setting
powerLimitAck = false;
actPowerLimit = 0xffff; // init feedback from inverter to -1 actPowerLimit = 0xffff; // init feedback from inverter to -1
mDevControlRequest = false; mDevControlRequest = false;
devControlCmd = InitDataState; devControlCmd = InitDataState;
@ -147,6 +161,8 @@ class Inverter {
alarmMesIndex = 0; alarmMesIndex = 0;
isConnected = false; isConnected = false;
status = InverterStatus::OFF; status = InverterStatus::OFF;
alarmNxtWrPos = 0;
alarmCnt = 0;
} }
~Inverter() { ~Inverter() {
@ -338,11 +354,6 @@ class Inverter {
isProducing(); isProducing();
} }
/*inline REC_TYP getPowerLimit(void) {
record_t<> *rec = getRecordStruct(SystemConfigPara);
return getChannelFieldValue(CH0, FLD_ACT_ACTIVE_PWR_LIMIT, rec);
}*/
bool setValue(uint8_t pos, record_t<> *rec, REC_TYP val) { bool setValue(uint8_t pos, record_t<> *rec, REC_TYP val) {
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:setValue")); DPRINTLN(DBG_VERBOSE, F("hmInverter.h:setValue"));
if(NULL == rec) if(NULL == rec)
@ -530,27 +541,32 @@ class Inverter {
} }
} }
uint16_t parseAlarmLog(uint8_t id, uint8_t pyld[], uint8_t len, uint32_t *start, uint32_t *endTime) { uint16_t parseAlarmLog(uint8_t id, uint8_t pyld[], uint8_t len) {
uint8_t startOff = 2 + id * ALARM_LOG_ENTRY_SIZE; uint8_t startOff = 2 + id * ALARM_LOG_ENTRY_SIZE;
if((startOff + ALARM_LOG_ENTRY_SIZE) > len) if((startOff + ALARM_LOG_ENTRY_SIZE) > len)
return 0; return 0;
uint16_t wCode = ((uint16_t)pyld[startOff]) << 8 | pyld[startOff+1]; uint16_t wCode = ((uint16_t)pyld[startOff]) << 8 | pyld[startOff+1];
uint32_t startTimeOffset = 0, endTimeOffset = 0; uint32_t startTimeOffset = 0, endTimeOffset = 0;
uint32_t start, endTime;
if (((wCode >> 13) & 0x01) == 1) // check if is AM or PM if (((wCode >> 13) & 0x01) == 1) // check if is AM or PM
startTimeOffset = 12 * 60 * 60; startTimeOffset = 12 * 60 * 60;
if (((wCode >> 12) & 0x01) == 1) // check if is AM or PM if (((wCode >> 12) & 0x01) == 1) // check if is AM or PM
endTimeOffset = 12 * 60 * 60; endTimeOffset = 12 * 60 * 60;
*start = (((uint16_t)pyld[startOff + 4] << 8) | ((uint16_t)pyld[startOff + 5])) + startTimeOffset; start = (((uint16_t)pyld[startOff + 4] << 8) | ((uint16_t)pyld[startOff + 5])) + startTimeOffset;
*endTime = (((uint16_t)pyld[startOff + 6] << 8) | ((uint16_t)pyld[startOff + 7])) + endTimeOffset; endTime = (((uint16_t)pyld[startOff + 6] << 8) | ((uint16_t)pyld[startOff + 7])) + endTimeOffset;
DPRINTLN(DBG_DEBUG, "Alarm #" + String(pyld[startOff+1]) + " '" + String(getAlarmStr(pyld[startOff+1])) + "' start: " + ah::getTimeStr(start) + ", end: " + ah::getTimeStr(endTime));
addAlarm(pyld[startOff+1], start, endTime);
alarmCnt++;
DPRINTLN(DBG_INFO, "Alarm #" + String(pyld[startOff+1]) + " '" + String(getAlarmStr(pyld[startOff+1])) + "' start: " + ah::getTimeStr(*start) + ", end: " + ah::getTimeStr(*endTime));
return pyld[startOff+1]; return pyld[startOff+1];
} }
String getAlarmStr(uint16_t alarmCode) { static String getAlarmStr(uint16_t alarmCode) {
switch (alarmCode) { // breaks are intentionally missing! switch (alarmCode) { // breaks are intentionally missing!
case 1: return String(F("Inverter start")); case 1: return String(F("Inverter start"));
case 2: return String(F("DTU command failed")); case 2: return String(F("DTU command failed"));
@ -625,6 +641,12 @@ class Inverter {
} }
private: private:
inline void addAlarm(uint16_t code, uint32_t start, uint32_t end) {
lastAlarm[alarmNxtWrPos] = alarm_t(code, start, end);
if(++alarmNxtWrPos >= 10) // rolling buffer
alarmNxtWrPos = 0;
}
void toRadioId(void) { void toRadioId(void) {
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:toRadioId")); DPRINTLN(DBG_VERBOSE, F("hmInverter.h:toRadioId"));
radioId.u64 = 0ULL; radioId.u64 = 0ULL;

23
src/hm/hmPayload.h

@ -29,7 +29,7 @@ typedef struct {
typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType; typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType;
typedef std::function<void(uint16_t alarmCode, uint32_t start, uint32_t end)> alarmListenerType; typedef std::function<void(Inverter<> *)> alarmListenerType;
template<class HMSYSTEM, class HMRADIO> template<class HMSYSTEM, class HMRADIO>
@ -143,6 +143,7 @@ class HmPayload {
DBGPRINT(F(" power limit ")); DBGPRINT(F(" power limit "));
DBGPRINTLN(String(iv->powerLimit[0])); DBGPRINTLN(String(iv->powerLimit[0]));
} }
iv->powerLimitAck = false;
mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false); mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false);
mPayload[iv->id].txCmd = iv->devControlCmd; mPayload[iv->id].txCmd = iv->devControlCmd;
//iv->clearCmdQueue(); //iv->clearCmdQueue();
@ -190,9 +191,10 @@ class HmPayload {
if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) { if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) {
bool ok = true; bool ok = true;
if((p->packet[10] == 0x00) && (p->packet[11] == 0x00)) if((p->packet[10] == 0x00) && (p->packet[11] == 0x00)) {
mApp->setMqttPowerLimitAck(iv); mApp->setMqttPowerLimitAck(iv);
else iv->powerLimitAck = true;
} else
ok = false; ok = false;
DPRINT_IVID(DBG_INFO, iv->id); DPRINT_IVID(DBG_INFO, iv->id);
@ -289,10 +291,10 @@ class HmPayload {
record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
mPayload[iv->id].complete = true; mPayload[iv->id].complete = true;
uint8_t payload[128]; uint8_t payload[100];
uint8_t payloadLen = 0; uint8_t payloadLen = 0;
memset(payload, 0, 128); memset(payload, 0, 100);
for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i++) { for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId); i++) {
memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i])); memcpy(&payload[payloadLen], mPayload[iv->id].data[i], (mPayload[iv->id].len[i]));
@ -324,14 +326,12 @@ class HmPayload {
if(AlarmData == mPayload[iv->id].txCmd) { if(AlarmData == mPayload[iv->id].txCmd) {
uint8_t i = 0; uint8_t i = 0;
uint16_t code;
uint32_t start, end; uint32_t start, end;
while(1) { while(1) {
code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); if(0 == iv->parseAlarmLog(i++, payload, payloadLen))
if(0 == code)
break; break;
if (NULL != mCbAlarm) if (NULL != mCbAlarm)
(mCbAlarm)(code, start, end); (mCbAlarm)(iv);
yield(); yield();
} }
} }
@ -355,11 +355,6 @@ class HmPayload {
(mCbPayload)(val, iv); (mCbPayload)(val, iv);
} }
void notify(uint16_t code, uint32_t start, uint32_t endTime) {
if (NULL != mCbAlarm)
(mCbAlarm)(code, start, endTime);
}
bool build(uint8_t id, bool *complete) { bool build(uint8_t id, bool *complete) {
DPRINTLN(DBG_VERBOSE, F("build")); DPRINTLN(DBG_VERBOSE, F("build"));
uint16_t crc = 0xffff, crcRcv = 0x0000; uint16_t crc = 0xffff, crcRcv = 0x0000;

17
src/hm/miPayload.h

@ -66,7 +66,7 @@ class MiPayload {
} }
void addAlarmListener(alarmListenerType cb) { void addAlarmListener(alarmListenerType cb) {
mCbMiAlarm = cb; mCbAlarm = cb;
} }
void loop() { void loop() {
@ -125,6 +125,7 @@ class MiPayload {
DBGPRINT(F(" power limit ")); DBGPRINT(F(" power limit "));
DBGPRINTLN(String(iv->powerLimit[0])); DBGPRINTLN(String(iv->powerLimit[0]));
} }
iv->powerLimitAck = false;
mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false, false); mRadio->sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false, false);
mPayload[iv->id].txCmd = iv->devControlCmd; mPayload[iv->id].txCmd = iv->devControlCmd;
mPayload[iv->id].limitrequested = true; mPayload[iv->id].limitrequested = true;
@ -313,6 +314,7 @@ const byteAssign_t InfoAssignment[] = {
if ((p->packet[9] == 0x5a) && (p->packet[10] == 0x5a)) { if ((p->packet[9] == 0x5a) && (p->packet[10] == 0x5a)) {
mApp->setMqttPowerLimitAck(iv); mApp->setMqttPowerLimitAck(iv);
iv->powerLimitAck = true;
DPRINT_IVID(DBG_INFO, iv->id); DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F("has accepted power limit set point ")); DBGPRINT(F("has accepted power limit set point "));
DBGPRINT(String(iv->powerLimit[0])); DBGPRINT(String(iv->powerLimit[0]));
@ -368,14 +370,11 @@ const byteAssign_t InfoAssignment[] = {
if(AlarmData == mPayload[iv->id].txCmd) { if(AlarmData == mPayload[iv->id].txCmd) {
uint8_t i = 0; uint8_t i = 0;
uint16_t code;
uint32_t start, end;
while(1) { while(1) {
code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); if(0 == iv->parseAlarmLog(i++, payload, payloadLen))
if(0 == code)
break; break;
if (NULL != mCbMiAlarm) if (NULL != mCbAlarm)
(mCbMiAlarm)(code, start, end); (mCbAlarm)(iv);
yield(); yield();
} }
} }
@ -714,7 +713,7 @@ const byteAssign_t InfoAssignment[] = {
code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end);
if(0 == code) if(0 == code)
break; break;
if (NULL != mCbMiAlarm) if (NULL != mCbAlarm)
(mCbAlarm)(code, start, end); (mCbAlarm)(code, start, end);
yield(); yield();
} }
@ -835,7 +834,7 @@ const byteAssign_t InfoAssignment[] = {
bool mSerialDebug; bool mSerialDebug;
Inverter<> *mHighPrioIv; Inverter<> *mHighPrioIv;
alarmListenerType mCbMiAlarm; alarmListenerType mCbAlarm;
payloadListenerType mCbMiPayload; payloadListenerType mCbMiPayload;
}; };

35
src/hms/hmsPayload.h

@ -30,7 +30,7 @@ typedef struct {
typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType; typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType;
typedef std::function<void(uint16_t alarmCode, uint32_t start, uint32_t end)> alarmListenerType; typedef std::function<void(Inverter<> *)> alarmListenerType;
template<class HMSYSTEM, class RADIO> template<class HMSYSTEM, class RADIO>
@ -50,7 +50,7 @@ class HmsPayload {
mIvCmd56Cnt[i] = 0; mIvCmd56Cnt[i] = 0;
} }
mSerialDebug = false; mSerialDebug = false;
//mHighPrioIv = NULL; mHighPrioIv = NULL;
mCbAlarm = NULL; mCbAlarm = NULL;
mCbPayload = NULL; mCbPayload = NULL;
//mLastRx = 0; //mLastRx = 0;
@ -69,14 +69,14 @@ class HmsPayload {
} }
void loop() { void loop() {
/*if(NULL != mHighPrioIv) { if(NULL != mHighPrioIv) {
ivSend(mHighPrioIv, true); ivSend(mHighPrioIv, true);
mHighPrioIv = NULL; mHighPrioIv = NULL;
}*/ }
} }
void ivSendHighPrio(Inverter<> *iv) { void ivSendHighPrio(Inverter<> *iv) {
//mHighPrioIv = iv; mHighPrioIv = iv;
} }
void ivSend(Inverter<> *iv, bool highPrio = false) { void ivSend(Inverter<> *iv, bool highPrio = false) {
@ -127,6 +127,7 @@ class HmsPayload {
DBGPRINT(F(" power limit ")); DBGPRINT(F(" power limit "));
DBGPRINTLN(String(iv->powerLimit[0])); DBGPRINTLN(String(iv->powerLimit[0]));
} }
iv->powerLimitAck = false;
mRadio->sendControlPacket(&iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false); mRadio->sendControlPacket(&iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false);
mPayload[iv->id].txCmd = iv->devControlCmd; mPayload[iv->id].txCmd = iv->devControlCmd;
//iv->clearCmdQueue(); //iv->clearCmdQueue();
@ -178,9 +179,10 @@ class HmsPayload {
if ((p->data[13] == ActivePowerContr) && (p->data[14] == 0x00)) { if ((p->data[13] == ActivePowerContr) && (p->data[14] == 0x00)) {
bool ok = true; bool ok = true;
if((p->data[11] == 0x00) && (p->data[12] == 0x00)) if((p->data[11] == 0x00) && (p->data[12] == 0x00)) {
mApp->setMqttPowerLimitAck(iv); mApp->setMqttPowerLimitAck(iv);
else iv->powerLimitAck = true;
} else
ok = false; ok = false;
DPRINT_IVID(DBG_INFO, iv->id); DPRINT_IVID(DBG_INFO, iv->id);
DBGPRINT(F(" has ")); DBGPRINT(F(" has "));
@ -192,6 +194,8 @@ class HmsPayload {
iv->clearCmdQueue(); iv->clearCmdQueue();
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
if(mHighPrioIv == NULL) // do it immediately if possible
mHighPrioIv = iv;
} }
iv->devControlCmd = Init; iv->devControlCmd = Init;
} }
@ -305,19 +309,17 @@ class HmsPayload {
iv->doCalculations(); iv->doCalculations();
notify(mPayload[iv->id].txCmd, iv); notify(mPayload[iv->id].txCmd, iv);
/*if(AlarmData == mPayload[iv->id].txCmd) { if(AlarmData == mPayload[iv->id].txCmd) {
uint8_t i = 0; uint8_t i = 0;
uint16_t code;
uint32_t start, end; uint32_t start, end;
while(1) { while(1) {
code = iv->parseAlarmLog(i++, payload, payloadLen, &start, &end); if(0 == iv->parseAlarmLog(i++, payload, payloadLen))
if(0 == code)
break; break;
if (NULL != mCbAlarm) if (NULL != mCbAlarm)
(mCbAlarm)(code, start, end); (mCbAlarm)(iv);
yield(); yield();
} }
}*/ }
} else { } else {
DPRINT(DBG_ERROR, F("plausibility check failed, expected ")); DPRINT(DBG_ERROR, F("plausibility check failed, expected "));
DBGPRINT(String(rec->pyldLen)); DBGPRINT(String(rec->pyldLen));
@ -338,11 +340,6 @@ class HmsPayload {
(mCbPayload)(val, iv); (mCbPayload)(val, iv);
} }
void notify(uint16_t code, uint32_t start, uint32_t endTime) {
if (NULL != mCbAlarm)
(mCbAlarm)(code, start, endTime);
}
bool build(uint8_t id, bool *complete) { bool build(uint8_t id, bool *complete) {
DPRINTLN(DBG_VERBOSE, F("build")); DPRINTLN(DBG_VERBOSE, F("build"));
uint16_t crc = 0xffff, crcRcv = 0x0000; uint16_t crc = 0xffff, crcRcv = 0x0000;
@ -376,7 +373,7 @@ class HmsPayload {
DPRINT(DBG_INFO, "resetPayload: id: "); DPRINT(DBG_INFO, "resetPayload: id: ");
DBGPRINTLN(String(id)); DBGPRINTLN(String(id));
memset(&mPayload[id], 0, sizeof(hmsPayload_t)); memset(&mPayload[id], 0, sizeof(hmsPayload_t));
//mPayload[id].txCmd = 0; mPayload[id].txCmd = 0;
mPayload[id].gotFragment = false; mPayload[id].gotFragment = false;
//mPayload[id].retransmits = 0; //mPayload[id].retransmits = 0;
mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES; mPayload[id].maxPackId = MAX_PAYLOAD_ENTRIES;

76
src/publisher/pubMqtt.h

@ -28,13 +28,6 @@
typedef std::function<void(JsonObject)> subscriptionCb; typedef std::function<void(JsonObject)> subscriptionCb;
struct alarm_t {
uint16_t code;
uint32_t start;
uint32_t end;
alarm_t(uint16_t c, uint32_t s, uint32_t e) : code(c), start(s), end(e) {}
};
typedef struct { typedef struct {
bool running; bool running;
uint8_t lastIvId; uint8_t lastIvId;
@ -76,10 +69,10 @@ class PubMqtt {
if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0)) if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0))
mClient.setCredentials(mCfgMqtt->user, mCfgMqtt->pwd); mClient.setCredentials(mCfgMqtt->user, mCfgMqtt->pwd);
if(strlen(mCfgMqtt->clientId) > 0) {
snprintf(mClientId, 23, "%s-", mCfgMqtt->clientId); if(strlen(mCfgMqtt->clientId) > 0)
mClient.setClientId(mCfgMqtt->clientId); snprintf(mClientId, 23, "%s", mCfgMqtt->clientId);
} else{ else{
snprintf(mClientId, 24, "%s-", mDevName); snprintf(mClientId, 24, "%s-", mDevName);
uint8_t pos = strlen(mClientId); uint8_t pos = strlen(mClientId);
mClientId[pos++] = WiFi.macAddress().substring( 9, 10).c_str()[0]; mClientId[pos++] = WiFi.macAddress().substring( 9, 10).c_str()[0];
@ -89,9 +82,9 @@ class PubMqtt {
mClientId[pos++] = WiFi.macAddress().substring(15, 16).c_str()[0]; mClientId[pos++] = WiFi.macAddress().substring(15, 16).c_str()[0];
mClientId[pos++] = WiFi.macAddress().substring(16, 17).c_str()[0]; mClientId[pos++] = WiFi.macAddress().substring(16, 17).c_str()[0];
mClientId[pos++] = '\0'; mClientId[pos++] = '\0';
mClient.setClientId(mClientId);
} }
mClient.setClientId(mClientId);
mClient.setServer(mCfgMqtt->broker, mCfgMqtt->port); mClient.setServer(mCfgMqtt->broker, mCfgMqtt->port);
mClient.setWill(mLwtTopic, QOS_0, true, mqttStr[MQTT_STR_LWT_NOT_CONN]); mClient.setWill(mLwtTopic, QOS_0, true, mqttStr[MQTT_STR_LWT_NOT_CONN]);
mClient.onConnect(std::bind(&PubMqtt::onConnect, this, std::placeholders::_1)); mClient.onConnect(std::bind(&PubMqtt::onConnect, this, std::placeholders::_1));
@ -111,7 +104,6 @@ class PubMqtt {
discoveryConfigLoop(); discoveryConfigLoop();
} }
void tickerSecond() { void tickerSecond() {
if (mIntervalTimeout > 0) if (mIntervalTimeout > 0)
mIntervalTimeout--; mIntervalTimeout--;
@ -130,6 +122,8 @@ class PubMqtt {
sendIvData(); sendIvData();
} }
} }
sendAlarmData();
} }
void tickerMinute() { void tickerMinute() {
@ -179,10 +173,8 @@ class PubMqtt {
} }
} }
void alarmEventListener(uint16_t code, uint32_t start, uint32_t endTime) { void alarmEvent(Inverter<> *iv) {
if(mClient.connected()) { mSendAlarm[iv->id] = true;
mAlarmList.push(alarm_t(code, start, endTime));
}
} }
void publish(const char *subTopic, const char *payload, bool retained = false, bool addTopic = true) { void publish(const char *subTopic, const char *payload, bool retained = false, bool addTopic = true) {
@ -509,15 +501,43 @@ class PubMqtt {
} }
void sendAlarmData() { void sendAlarmData() {
if(mAlarmList.empty()) Inverter<> *iv;
return; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
Inverter<> *iv = mSys->getInverterByPos(0, false); if(!mSendAlarm[i])
while(!mAlarmList.empty()) { continue;
alarm_t alarm = mAlarmList.front();
publish(subtopics[MQTT_ALARM], iv->getAlarmStr(alarm.code).c_str()); iv = mSys->getInverterByPos(i, false);
publish(subtopics[MQTT_ALARM_START], String(alarm.start).c_str()); if (NULL == iv)
publish(subtopics[MQTT_ALARM_END], String(alarm.end).c_str()); continue;
mAlarmList.pop(); if (!iv->config->enabled)
continue;
mSendAlarm[i] = false;
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/alarm/cnt", iv->config->name);
snprintf(mVal, 40, "%d", iv->alarmCnt);
publish(mSubTopic, mVal, true);
for(uint8_t j = 0; j < 10; j++) {
if(0 != iv->lastAlarm[j].code) {
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/alarm/%d/code", iv->config->name, j);
snprintf(mVal, 40, "%d", iv->lastAlarm[j].code);
publish(mSubTopic, mVal, true);
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/alarm/%d/str", iv->config->name, j);
snprintf(mVal, 40, "%s", iv->getAlarmStr(iv->lastAlarm[j].code));
publish(mSubTopic, mVal, true);
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/alarm/%d/start", iv->config->name, j);
snprintf(mVal, 40, "%d", iv->lastAlarm[j].start);
publish(mSubTopic, mVal, true);
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/alarm/%d/end", iv->config->name, j);
snprintf(mVal, 40, "%d", iv->lastAlarm[j].end);
publish(mSubTopic, mVal, true);
yield();
}
}
} }
} }
@ -578,7 +598,7 @@ class PubMqtt {
uint32_t *mUtcTimestamp, *mUptime; uint32_t *mUtcTimestamp, *mUptime;
uint32_t mRxCnt, mTxCnt; uint32_t mRxCnt, mTxCnt;
std::queue<sendListCmdIv> mSendList; std::queue<sendListCmdIv> mSendList;
std::queue<alarm_t> mAlarmList; std::array<bool, MAX_NUM_INVERTERS> mSendAlarm{};
subscriptionCb mSubscriptionCb; subscriptionCb mSubscriptionCb;
bool mLastAnyAvail; bool mLastAnyAvail;
bool mZeroValues; bool mZeroValues;

6
src/publisher/pubMqttDefs.h

@ -53,9 +53,6 @@ enum {
MQTT_DEVICE, MQTT_DEVICE,
MQTT_IP_ADDR, MQTT_IP_ADDR,
MQTT_STATUS, MQTT_STATUS,
MQTT_ALARM,
MQTT_ALARM_START,
MQTT_ALARM_END,
MQTT_LWT_ONLINE, MQTT_LWT_ONLINE,
MQTT_LWT_OFFLINE, MQTT_LWT_OFFLINE,
MQTT_ACK_PWR_LMT MQTT_ACK_PWR_LMT
@ -77,9 +74,6 @@ const char* const subtopics[] PROGMEM = {
"device", "device",
"ip_addr", "ip_addr",
"status", "status",
"alarm",
"alarm_start",
"alarm_end",
"connected", "connected",
"not_connected", "not_connected",
"ack_pwr_limit" "ack_pwr_limit"

121
src/web/RestApi.h

@ -98,13 +98,15 @@ class RestApi {
else if(path == "setup/networks") getNetworks(root); else if(path == "setup/networks") getNetworks(root);
#endif /* !defined(ETHERNET) */ #endif /* !defined(ETHERNET) */
else if(path == "live") getLive(request,root); else if(path == "live") getLive(request,root);
else if(path == "record/info") getRecord(root, InverterDevInform_All); /*else if(path == "record/info") getRecord(root, InverterDevInform_All);
else if(path == "record/alarm") getRecord(root, AlarmData); else if(path == "record/alarm") getRecord(root, AlarmData);
else if(path == "record/config") getRecord(root, SystemConfigPara); else if(path == "record/config") getRecord(root, SystemConfigPara);
else if(path == "record/live") getRecord(root, RealTimeRunData_Debug); else if(path == "record/live") getRecord(root, RealTimeRunData_Debug);*/
else { else {
if(path.substring(0, 12) == "inverter/id/") if(path.substring(0, 12) == "inverter/id/")
getInverter(root, request->url().substring(17).toInt()); getInverter(root, request->url().substring(17).toInt());
else if(path.substring(0, 15) == "inverter/alarm/")
getIvAlarms(root, request->url().substring(20).toInt());
else else
getNotFound(root, F("http://") + request->host() + F("/api/")); getNotFound(root, F("http://") + request->host() + F("/api/"));
} }
@ -155,16 +157,15 @@ class RestApi {
void getNotFound(JsonObject obj, String url) { void getNotFound(JsonObject obj, String url) {
JsonObject ep = obj.createNestedObject("avail_endpoints"); JsonObject ep = obj.createNestedObject("avail_endpoints");
ep[F("system")] = url + F("system"); ep[F("inverter/list")] = url + F("inverter/list");
ep[F("statistics")] = url + F("statistics"); ep[F("inverter/id/0")] = url + F("inverter/id/0");
ep[F("inverter/list")] = url + F("inverter/list"); ep[F("inverter/alarm/0")] = url + F("inverter/alarm/0");
ep[F("index")] = url + F("index"); ep[F("statistics")] = url + F("statistics");
ep[F("setup")] = url + F("setup"); ep[F("generic")] = url + F("generic");
ep[F("live")] = url + F("live"); ep[F("index")] = url + F("index");
ep[F("record/info")] = url + F("record/info"); ep[F("setup")] = url + F("setup");
ep[F("record/alarm")] = url + F("record/alarm"); ep[F("system")] = url + F("system");
ep[F("record/config")] = url + F("record/config"); ep[F("live")] = url + F("live");
ep[F("record/live")] = url + F("record/live");
} }
@ -349,48 +350,70 @@ class RestApi {
void getInverter(JsonObject obj, uint8_t id) { void getInverter(JsonObject obj, uint8_t id) {
Inverter<> *iv = mSys->getInverterByPos(id); Inverter<> *iv = mSys->getInverterByPos(id);
if(NULL != iv) { if(NULL == iv) {
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); obj[F("error")] = F("inverter not found!");
obj[F("id")] = id; return;
obj[F("enabled")] = (bool)iv->config->enabled; }
obj[F("name")] = String(iv->config->name);
obj[F("serial")] = String(iv->config->serial.u64, HEX); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
obj[F("version")] = String(iv->getFwVersion()); obj[F("id")] = id;
obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit); obj[F("enabled")] = (bool)iv->config->enabled;
obj[F("ts_last_success")] = rec->ts; obj[F("name")] = String(iv->config->name);
obj[F("generation")] = iv->ivGen; obj[F("serial")] = String(iv->config->serial.u64, HEX);
obj[F("version")] = String(iv->getFwVersion());
JsonArray ch = obj.createNestedArray("ch"); obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit);
obj[F("power_limit_ack")] = iv->powerLimitAck;
// AC obj[F("ts_last_success")] = rec->ts;
uint8_t pos; obj[F("generation")] = iv->ivGen;
obj[F("ch_name")][0] = "AC";
JsonArray ch0 = ch.createNestedArray(); JsonArray ch = obj.createNestedArray("ch");
if(IV_HMT == iv->ivGen) {
for (uint8_t fld = 0; fld < sizeof(acListHmt); fld++) { // AC
pos = (iv->getPosByChFld(CH0, acListHmt[fld], rec)); uint8_t pos;
ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; obj[F("ch_name")][0] = "AC";
} JsonArray ch0 = ch.createNestedArray();
} else { if(IV_HMT == iv->ivGen) {
for (uint8_t fld = 0; fld < sizeof(acList); fld++) { for (uint8_t fld = 0; fld < sizeof(acListHmt); fld++) {
pos = (iv->getPosByChFld(CH0, acList[fld], rec)); pos = (iv->getPosByChFld(CH0, acListHmt[fld], rec));
ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
} }
} } else {
for (uint8_t fld = 0; fld < sizeof(acList); fld++) {
pos = (iv->getPosByChFld(CH0, acList[fld], rec));
ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
}
}
// DC // DC
for(uint8_t j = 0; j < iv->channels; j ++) { for(uint8_t j = 0; j < iv->channels; j ++) {
obj[F("ch_name")][j+1] = iv->config->chName[j]; obj[F("ch_name")][j+1] = iv->config->chName[j];
obj[F("ch_max_pwr")][j+1] = iv->config->chMaxPwr[j]; obj[F("ch_max_pwr")][j+1] = iv->config->chMaxPwr[j];
JsonArray cur = ch.createNestedArray(); JsonArray cur = ch.createNestedArray();
for (uint8_t fld = 0; fld < sizeof(dcList); fld++) { for (uint8_t fld = 0; fld < sizeof(dcList); fld++) {
pos = (iv->getPosByChFld((j+1), dcList[fld], rec)); pos = (iv->getPosByChFld((j+1), dcList[fld], rec));
cur[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; cur[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0;
}
} }
} }
} }
void getIvAlarms(JsonObject obj, uint8_t id) {
Inverter<> *iv = mSys->getInverterByPos(id);
if(NULL == iv) {
obj[F("error")] = F("inverter not found!");
return;
}
obj["cnt"] = iv->alarmCnt;
JsonArray alarm = obj.createNestedArray(F("alarm"));
for(uint8_t i = 0; i < 10; i++) {
alarm[i][F("code")] = iv->lastAlarm[i].code;
alarm[i][F("str")] = iv->getAlarmStr(iv->lastAlarm[i].code);
alarm[i][F("start")] = iv->lastAlarm[i].start;
alarm[i][F("end")] = iv->lastAlarm[i].end;
}
}
void getMqtt(JsonObject obj) { void getMqtt(JsonObject obj) {
obj[F("broker")] = String(mConfig->mqtt.broker); obj[F("broker")] = String(mConfig->mqtt.broker);
obj[F("clientId")] = String(mConfig->mqtt.clientId); obj[F("clientId")] = String(mConfig->mqtt.clientId);
@ -563,7 +586,7 @@ class RestApi {
} }
} }
void getRecord(JsonObject obj, uint8_t recType) { /*void getRecord(JsonObject obj, uint8_t recType) {
JsonArray invArr = obj.createNestedArray(F("inverter")); JsonArray invArr = obj.createNestedArray(F("inverter"));
Inverter<> *iv; Inverter<> *iv;
@ -583,7 +606,7 @@ class RestApi {
} }
} }
} }
} }*/
bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) { bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) {
Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]);

Loading…
Cancel
Save