mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-24 06:16:11 +02:00
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
This commit is contained in:
parent
c59d26d858
commit
e5b5972cae
11 changed files with 199 additions and 136 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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("statistics")] = url + F("statistics");
|
|
||||||
ep[F("inverter/list")] = url + F("inverter/list");
|
ep[F("inverter/list")] = url + F("inverter/list");
|
||||||
|
ep[F("inverter/id/0")] = url + F("inverter/id/0");
|
||||||
|
ep[F("inverter/alarm/0")] = url + F("inverter/alarm/0");
|
||||||
|
ep[F("statistics")] = url + F("statistics");
|
||||||
|
ep[F("generic")] = url + F("generic");
|
||||||
ep[F("index")] = url + F("index");
|
ep[F("index")] = url + F("index");
|
||||||
ep[F("setup")] = url + F("setup");
|
ep[F("setup")] = url + F("setup");
|
||||||
|
ep[F("system")] = url + F("system");
|
||||||
ep[F("live")] = url + F("live");
|
ep[F("live")] = url + F("live");
|
||||||
ep[F("record/info")] = url + F("record/info");
|
|
||||||
ep[F("record/alarm")] = url + F("record/alarm");
|
|
||||||
ep[F("record/config")] = url + F("record/config");
|
|
||||||
ep[F("record/live")] = url + F("record/live");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -349,7 +350,11 @@ 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) {
|
||||||
|
obj[F("error")] = F("inverter not found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
obj[F("id")] = id;
|
obj[F("id")] = id;
|
||||||
obj[F("enabled")] = (bool)iv->config->enabled;
|
obj[F("enabled")] = (bool)iv->config->enabled;
|
||||||
|
@ -357,6 +362,7 @@ class RestApi {
|
||||||
obj[F("serial")] = String(iv->config->serial.u64, HEX);
|
obj[F("serial")] = String(iv->config->serial.u64, HEX);
|
||||||
obj[F("version")] = String(iv->getFwVersion());
|
obj[F("version")] = String(iv->getFwVersion());
|
||||||
obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit);
|
obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit);
|
||||||
|
obj[F("power_limit_ack")] = iv->powerLimitAck;
|
||||||
obj[F("ts_last_success")] = rec->ts;
|
obj[F("ts_last_success")] = rec->ts;
|
||||||
obj[F("generation")] = iv->ivGen;
|
obj[F("generation")] = iv->ivGen;
|
||||||
|
|
||||||
|
@ -389,6 +395,23 @@ class RestApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
@ -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…
Add table
Add a link
Reference in a new issue