Browse Source

reduced heap fragmentation (removed `strtok` completely) #644, #645, #682

added part of mac address to MQTT client ID to seperate multiple ESPs in same network
added dictionary for MQTT to reduce heap-fragmentation
removed `last Alarm` from Live view, because it showed always the same alarm - will change in future
pull/698/head
lumapu 2 years ago
parent
commit
a0879cfcbe
  1. 6
      src/CHANGES.md
  2. 45
      src/app.cpp
  3. 5
      src/app.h
  4. 6
      src/config/settings.h
  5. 4
      src/defines.h
  6. 17
      src/hm/hmInverter.h
  7. 79
      src/hm/hmPayload.h
  8. 11
      src/hm/hmRadio.h
  9. 125
      src/publisher/pubMqtt.h
  10. 104
      src/publisher/pubMqttDefs.h
  11. 56
      src/utils/dbg.h
  12. 18
      src/utils/helper.cpp
  13. 8
      src/utils/scheduler.h
  14. 29
      src/web/RestApi.h
  15. 2
      src/web/html/visualization.html
  16. 24
      src/wifi/ahoywifi.cpp
  17. 4
      src/wifi/ahoywifi.h

6
src/CHANGES.md

@ -2,6 +2,12 @@
(starting from release version `0.5.66`)
## 0.5.89
* reduced heap fragmentation (removed `strtok` completely) #644, #645, #682
* added part of mac address to MQTT client ID to seperate multiple ESPs in same network
* added dictionary for MQTT to reduce heap-fragmentation
* removed `last Alarm` from Live view, because it showed always the same alarm - will change in future
## 0.5.88
* MQTT Yield Day zero, next try to fix #671, thx @beegee3
* added Solenso inverter to supported devices

45
src/app.cpp

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://ahoydtu.de
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -21,9 +21,19 @@ void app::setup() {
resetSystem();
/*DBGPRINTLN("--- start");
DBGPRINTLN(String(ESP.getFreeHeap()));
DBGPRINTLN(String(ESP.getHeapFragmentation()));
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
mSettings.setup();
mSettings.getPtr(mConfig);
DPRINTLN(DBG_INFO, F("Settings valid: ") + String((mSettings.getValid()) ? F("true") : F("false")));
DPRINT(DBG_INFO, F("Settings valid: "));
if(mSettings.getValid())
DBGPRINTLN(F("true"));
else
DBGPRINTLN(F("false"));
mSys.enableDebug();
mSys.setup(mConfig->nrf.amplifierPower, mConfig->nrf.pinIrq, mConfig->nrf.pinCe, mConfig->nrf.pinCs);
@ -47,6 +57,11 @@ void app::setup() {
mMiPayload.setup(this, &mSys, &mStat, mConfig->nrf.maxRetransPerPyld, &mTimestamp);
mMiPayload.enableSerialDebug(mConfig->serial.debug);
/*DBGPRINTLN("--- after payload");
DBGPRINTLN(String(ESP.getFreeHeap()));
DBGPRINTLN(String(ESP.getHeapFragmentation()));
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
if(!mSys.Radio.isChipConnected())
DPRINTLN(DBG_WARN, F("WARNING! your NRF24 module can't be reached, check the wiring"));
@ -73,6 +88,12 @@ void app::setup() {
mPubSerial.setup(mConfig, &mSys, &mTimestamp);
regularTickers();
/*DBGPRINTLN("--- end setup");
DBGPRINTLN(String(ESP.getFreeHeap()));
DBGPRINTLN(String(ESP.getHeapFragmentation()));
DBGPRINTLN(String(ESP.getMaxFreeBlockSize()));*/
}
//-----------------------------------------------------------------------------
@ -89,7 +110,11 @@ void app::loopStandard(void) {
packet_t *p = &mSys.Radio.mBufCtrl.front();
if (mConfig->serial.debug) {
DPRINT(DBG_INFO, "RX " + String(p->len) + "B Ch" + String(p->ch) + " | ");
DPRINT(DBG_INFO, F("RX "));
DBGPRINT(String(p->len));
DBGPRINT(F("B Ch"));
DBGPRINT(String(p->ch));
DBGPRINT(F(" | "));
mSys.Radio.dumpBuf(p->packet, p->len);
}
mStat.frmCnt++;
@ -158,13 +183,13 @@ void app::tickNtpUpdate(void) {
bool isOK = mWifi.getNtpTime();
if (isOK || mTimestamp != 0) {
if (mMqttReconnect && mMqttEnabled) {
mMqtt.connect();
mMqtt.tickerMinute();
everySec(std::bind(&PubMqttType::tickerSecond, &mMqtt), "mqttS");
everyMin(std::bind(&PubMqttType::tickerMinute, &mMqtt), "mqttM");
}
// only install schedulers once even if NTP wasn't successful in first loop
if(mMqttReconnect) { // @TODO: mMqttReconnect is wrong name here
if(mMqttReconnect) { // @TODO: mMqttReconnect is variable which scope has changed
if(mConfig->inst.rstValsNotAvail)
everyMin(std::bind(&app::tickMinute, this), "tMin");
if(mConfig->inst.rstYieldMidNight) {
@ -282,8 +307,6 @@ void app::tickMidnight(void) {
uint32_t nxtTrig = mTimestamp - ((mTimestamp - 1) % 86400) + 86400; // next midnight
onceAt(std::bind(&app::tickMidnight, this), nxtTrig, "mid2");
DPRINTLN(DBG_INFO, "tickMidnight " + String(nxtTrig));
Inverter<> *iv;
// set values to zero, except yield total
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
@ -302,13 +325,15 @@ void app::tickMidnight(void) {
//-----------------------------------------------------------------------------
void app::tickSend(void) {
if(!mSys.Radio.isChipConnected()) {
DPRINTLN(DBG_WARN, "NRF24 not connected!");
DPRINTLN(DBG_WARN, F("NRF24 not connected!"));
return;
}
if (mIVCommunicationOn) {
if (!mSys.Radio.mBufCtrl.empty()) {
if (mConfig->serial.debug)
DPRINTLN(DBG_DEBUG, F("recbuf not empty! #") + String(mSys.Radio.mBufCtrl.size()));
if (mConfig->serial.debug) {
DPRINT(DBG_DEBUG, F("recbuf not empty! #"));
DBGPRINTLN(String(mSys.Radio.mBufCtrl.size()));
}
}
int8_t maxLoop = MAX_NUM_INVERTERS;

5
src/app.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://ahoydtu.de
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -182,7 +182,8 @@ class app : public IApp, public ah::Scheduler {
}
void setTimestamp(uint32_t newTime) {
DPRINTLN(DBG_DEBUG, F("setTimestamp: ") + String(newTime));
DPRINT(DBG_DEBUG, F("setTimestamp: "));
DBGPRINTLN(String(newTime));
if(0 == newTime)
mWifi.getNtpTime();
else

6
src/config/settings.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://ahoydtu.de
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -212,7 +212,6 @@ class settings {
}
bool readSettings(const char* path) {
bool success = false;
loadDefaults();
File fp = LittleFS.open(path, "r");
if(!fp)
@ -233,7 +232,6 @@ class settings {
jsonLed(root[F("led")]);
jsonPlugin(root[F("plugin")]);
jsonInst(root[F("inst")]);
success = true;
}
else {
Serial.println(F("failed to parse json, using default config"));
@ -241,7 +239,7 @@ class settings {
fp.close();
}
return success;
return mCfg.valid;
}
bool saveSettings(void) {

4
src/defines.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -13,7 +13,7 @@
//-------------------------------------
#define VERSION_MAJOR 0
#define VERSION_MINOR 5
#define VERSION_PATCH 88
#define VERSION_PATCH 89
//-------------------------------------
typedef struct {

17
src/hm/hmInverter.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -119,7 +119,7 @@ class Inverter {
record_t<REC_TYP> recordInfo; // structure for info values
record_t<REC_TYP> recordConfig; // structure for system config values
record_t<REC_TYP> recordAlarm; // structure for alarm values
String lastAlarmMsg;
//String lastAlarmMsg;
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)
@ -131,7 +131,7 @@ class Inverter {
mDevControlRequest = false;
devControlCmd = InitDataState;
initialized = false;
lastAlarmMsg = "nothing";
//lastAlarmMsg = "nothing";
alarmMesIndex = 0;
isConnected = false;
}
@ -294,9 +294,9 @@ class Inverter {
}
else if (rec->assign == AlarmDataAssignment) {
DPRINTLN(DBG_DEBUG, "add alarm");
if (getPosByChFld(0, FLD_LAST_ALARM_CODE, rec) == pos){
lastAlarmMsg = getAlarmStr(rec->record[pos]);
}
//if (getPosByChFld(0, FLD_LAST_ALARM_CODE, rec) == pos){
// lastAlarmMsg = getAlarmStr(rec->record[pos]);
//}
}
else
DPRINTLN(DBG_WARN, F("add with unknown assginment"));
@ -305,6 +305,11 @@ class Inverter {
DPRINTLN(DBG_ERROR, F("addValue: assignment not found with cmd 0x"));
}
/*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) {
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:setValue"));
if(NULL == rec)

79
src/hm/hmPayload.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://ahoydtu.de
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -71,7 +71,7 @@ class HmPayload {
}
void zeroYieldDay(Inverter<> *iv) {
DPRINTLN(DBG_INFO, "zeroYieldDay");
DPRINTLN(DBG_DEBUG, F("zeroYieldDay"));
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
uint8_t pos;
for(uint8_t ch = 0; ch < iv->channels; ch++) {
@ -81,7 +81,7 @@ class HmPayload {
}
void zeroInverterValues(Inverter<> *iv) {
DPRINTLN(DBG_INFO, "zeroInverterValues");
DPRINTLN(DBG_DEBUG, F("zeroInverterValues"));
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
for(uint8_t ch = 0; ch <= iv->channels; ch++) {
uint8_t pos = 0;
@ -119,8 +119,11 @@ class HmPayload {
if (mSerialDebug)
DPRINTLN(DBG_INFO, F("enqueued cmd failed/timeout"));
if (mSerialDebug) {
DPRINT(DBG_INFO, F("(#") + String(iv->id) + ") ");
DPRINTLN(DBG_INFO, F("no Payload received! (retransmits: ") + String(mPayload[iv->id].retransmits) + ")");
DPRINT(DBG_INFO, F("(#"));
DBGPRINT(String(iv->id));
DBGPRINT(F(") no Payload received! (retransmits: "));
DBGPRINT(String(mPayload[iv->id].retransmits));
DBGPRINTLN(F(")"));
}
}
}
@ -130,19 +133,31 @@ class HmPayload {
mPayload[iv->id].requested = true;
yield();
if (mSerialDebug)
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Requesting Inv SN ") + String(iv->config->serial.u64, HEX));
if (mSerialDebug) {
DPRINT(DBG_INFO, F("(#"));
DBGPRINT(String(iv->id));
DBGPRINT(F(") Requesting Inv SN "));
DBGPRINTLN(String(iv->config->serial.u64, HEX));
}
if (iv->getDevControlRequest()) {
if (mSerialDebug)
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") Devcontrol request 0x") + String(iv->devControlCmd, HEX) + F(" power limit ") + String(iv->powerLimit[0]));
if (mSerialDebug) {
DPRINT(DBG_INFO, F("(#"));
DBGPRINT(String(iv->id));
DBGPRINT(F(") Devcontrol request 0x"));
DBGPRINT(String(iv->devControlCmd, HEX));
DBGPRINT(F(" power limit "));
DBGPRINTLN(String(iv->powerLimit[0]));
}
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit, false);
mPayload[iv->id].txCmd = iv->devControlCmd;
//iv->clearCmdQueue();
//iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
} else {
uint8_t cmd = iv->getQueuedCmd();
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd")); // + String(cmd, HEX));
DPRINT(DBG_INFO, F("(#"));
DBGPRINT(String(iv->id));
DBGPRINT(F(") prepareDevInformCmd")); // + String(cmd, HEX));
mSys->Radio.prepareDevInformCmd(iv->radioId.u64, cmd, mPayload[iv->id].ts, iv->alarmMesIndex, false);
mPayload[iv->id].txCmd = cmd;
}
@ -179,12 +194,20 @@ class HmPayload {
iv->clearDevControlRequest();
if ((p->packet[12] == ActivePowerContr) && (p->packet[13] == 0x00)) {
String msg = "";
bool ok = true;
if((p->packet[10] == 0x00) && (p->packet[11] == 0x00))
mApp->setMqttPowerLimitAck(iv);
else
msg = "NOT ";
DPRINTLN(DBG_INFO, F("Inverter ") + String(iv->id) + F(" has ") + msg + F("accepted power limit set point ") + String(iv->powerLimit[0]) + F(" with PowerLimitControl ") + String(iv->powerLimit[1]));
ok = false;
DPRINT(DBG_INFO, F("(#"));
DBGPRINT(String(iv->id));
DBGPRINT(F(" has "));
if(!ok) DBGPRINT(F("not "));
DBGPRINT(F("accepted power limit set point "));
DBGPRINT(String(iv->powerLimit[0]));
DBGPRINT(F(" with PowerLimitControl "));
DBGPRINT(String(iv->powerLimit[1]));
iv->clearCmdQueue();
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
}
@ -229,12 +252,16 @@ class HmPayload {
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd 0x") + String(mPayload[iv->id].txCmd, HEX));
mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
*/
DPRINTLN(DBG_WARN, F("(#") + String(iv->id) + F(") nothing received"));
DPRINT(DBG_INFO, F("(#"));
DBGPRINT(String(iv->id));
DBGPRINTLN(F(") nothing received"));
mPayload[iv->id].retransmits = mMaxRetrans;
} else {
for (uint8_t i = 0; i < (mPayload[iv->id].maxPackId - 1); i++) {
if (mPayload[iv->id].len[i] == 0) {
DPRINTLN(DBG_WARN, F("Frame ") + String(i + 1) + F(" missing: Request Retransmit"));
DPRINT(DBG_WARN, F("Frame "));
DBGPRINT(String(i + 1));
DBGPRINTLN(F(" missing: Request Retransmit"));
mSys->Radio.sendCmdPacket(iv->radioId.u64, TX_REQ_INFO, (SINGLE_FRAME + i), true);
break; // only request retransmit one frame per loop
}
@ -249,12 +276,17 @@ class HmPayload {
mPayload[iv->id].retransmits++;
DPRINTLN(DBG_WARN, F("CRC Error: Request Complete Retransmit"));
mPayload[iv->id].txCmd = iv->getQueuedCmd();
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") prepareDevInformCmd 0x") + String(mPayload[iv->id].txCmd, HEX));
DPRINT(DBG_INFO, F("(#"));
DBGPRINT(String(iv->id));
DBGPRINT(F(") prepareDevInformCmd 0x"));
DBGPRINTLN(String(mPayload[iv->id].txCmd, HEX));
mSys->Radio.prepareDevInformCmd(iv->radioId.u64, mPayload[iv->id].txCmd, mPayload[iv->id].ts, iv->alarmMesIndex, true);
}
} else { // payload complete
DPRINTLN(DBG_INFO, F("procPyld: cmd: 0x") + String(mPayload[iv->id].txCmd, HEX));
DPRINTLN(DBG_INFO, F("procPyld: txid: 0x") + String(mPayload[iv->id].txId, HEX));
DPRINT(DBG_INFO, F("procPyld: cmd: 0x"));
DBGPRINTLN(String(mPayload[iv->id].txCmd, HEX));
DPRINT(DBG_INFO, F("procPyld: txid: 0x"));
DBGPRINTLN(String(mPayload[iv->id].txId, HEX));
DPRINTLN(DBG_DEBUG, F("procPyld: max: ") + String(mPayload[iv->id].maxPackId));
record_t<> *rec = iv->getRecordStruct(mPayload[iv->id].txCmd); // choose the parser
mPayload[iv->id].complete = true;
@ -272,7 +304,9 @@ class HmPayload {
payloadLen -= 2;
if (mSerialDebug) {
DPRINT(DBG_INFO, F("Payload (") + String(payloadLen) + "): ");
DPRINT(DBG_INFO, F("Payload ("));
DBGPRINT(String(payloadLen));
DBGPRINT(F("): "));
mSys->Radio.dumpBuf(payload, payloadLen);
}
@ -304,7 +338,9 @@ class HmPayload {
}
}
} else {
DPRINTLN(DBG_ERROR, F("plausibility check failed, expected ") + String(rec->pyldLen) + F(" bytes"));
DPRINT(DBG_ERROR, F("plausibility check failed, expected "));
DBGPRINT(String(rec->pyldLen));
DBGPRINTLN(F(" bytes"));
mStat->rxFail++;
}
@ -356,7 +392,8 @@ class HmPayload {
}
void reset(uint8_t id) {
DPRINTLN(DBG_INFO, "resetPayload: id: " + String(id));
DPRINT(DBG_INFO, "resetPayload: id: ");
DBGPRINTLN(String(id));
memset(mPayload[id].len, 0, MAX_PAYLOAD_ENTRIES);
mPayload[id].txCmd = 0;
mPayload[id].gotFragment = false;

11
src/hm/hmRadio.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://github.com/lumpapu/ahoy
// 2023 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -176,7 +176,8 @@ class HmRadio {
}
void sendControlPacket(uint64_t invId, uint8_t cmd, uint16_t *data, bool isRetransmit) {
DPRINTLN(DBG_INFO, F("sendControlPacket cmd: 0x") + String(cmd, HEX));
DPRINT(DBG_INFO, F("sendControlPacket cmd: 0x"));
DBGPRINTLN(String(cmd, HEX));
initPacket(invId, TX_REQ_DEVCONTROL, SINGLE_FRAME);
uint8_t cnt = 10;
mTxBuf[cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor
@ -294,7 +295,11 @@ class HmRadio {
len++;
if(mSerialDebug) {
DPRINT(DBG_INFO, "TX " + String(len) + "B Ch" + String(mRfChLst[mTxChIdx]) + " | ");
DPRINT(DBG_INFO, F("TX "));
DBGPRINT(String(len));
DBGPRINT("B Ch");
DBGPRINT(String(mRfChLst[mTxChIdx]));
DBGPRINT(F(" | "));
dumpBuf(mTxBuf, len);
}

125
src/publisher/pubMqtt.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://ahoydtu.de
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -21,6 +21,8 @@
#include "../defines.h"
#include "../hm/hmSystem.h"
#include "pubMqttDefs.h"
#define QOS_0 0
typedef std::function<void(JsonObject)> subscriptionCb;
@ -57,9 +59,11 @@ class PubMqtt {
if((strlen(mCfgMqtt->user) > 0) && (strlen(mCfgMqtt->pwd) > 0))
mClient.setCredentials(mCfgMqtt->user, mCfgMqtt->pwd);
mClient.setClientId(mDevName); // TODO: add mac?
char clientId[64];
snprintf(clientId, 64, "%s_%s-%s-%s", mDevName, WiFi.macAddress().substring(9,11).c_str(), WiFi.macAddress().substring(12,14).c_str(), WiFi.macAddress().substring(15,17).c_str());
mClient.setClientId(clientId);
mClient.setServer(mCfgMqtt->broker, mCfgMqtt->port);
mClient.setWill(mLwtTopic, QOS_0, true, mLwtOffline);
mClient.setWill(mLwtTopic, QOS_0, true, mqttStr[MQTT_STR_LWT_NOT_CONN]);
mClient.onConnect(std::bind(&PubMqtt::onConnect, this, std::placeholders::_1));
mClient.onDisconnect(std::bind(&PubMqtt::onDisconnect, this, std::placeholders::_1));
mClient.onMessage(std::bind(&PubMqtt::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6));
@ -72,10 +76,6 @@ class PubMqtt {
#endif
}
inline void connect() {
if(!mClient.connected())
mClient.connect();
}
void tickerSecond() {
if (mIntervalTimeout > 0)
@ -100,20 +100,20 @@ class PubMqtt {
void tickerMinute() {
char val[12];
snprintf(val, 12, "%ld", millis() / 1000);
publish("uptime", val);
publish("wifi_rssi", String(WiFi.RSSI()).c_str());
publish("free_heap", String(ESP.getFreeHeap()).c_str());
publish(subtopics[MQTT_UPTIME], val);
publish(subtopics[MQTT_RSSI], String(WiFi.RSSI()).c_str());
publish(subtopics[MQTT_FREE_HEAP], String(ESP.getFreeHeap()).c_str());
}
bool tickerSun(uint32_t sunrise, uint32_t sunset, uint32_t offs, bool disNightCom) {
if (!mClient.connected())
return false;
publish("sunrise", String(sunrise).c_str(), true);
publish("sunset", String(sunset).c_str(), true);
publish("comm_start", String(sunrise - offs).c_str(), true);
publish("comm_stop", String(sunset + offs).c_str(), true);
publish("dis_night_comm", ((disNightCom) ? "true" : "false"), true);
publish(subtopics[MQTT_SUNRISE], String(sunrise).c_str(), true);
publish(subtopics[MQTT_SUNSET], String(sunset).c_str(), true);
publish(subtopics[MQTT_COMM_START], String(sunrise - offs).c_str(), true);
publish(subtopics[MQTT_COMM_STOP], String(sunset + offs).c_str(), true);
publish(subtopics[MQTT_DIS_NIGHT_COMM], ((disNightCom) ? dict[STR_TRUE] : dict[STR_FALSE]), true);
return true;
}
@ -122,8 +122,8 @@ class PubMqtt {
if (!mClient.connected())
return false;
publish("comm_disabled", ((disabled) ? "true" : "false"), true);
publish("comm_dis_ts", String(*mUtcTimestamp).c_str(), true);
publish(subtopics[MQTT_COMM_DISABLED], ((disabled) ? dict[STR_TRUE] : dict[STR_FALSE]), true);
publish(subtopics[MQTT_COMM_DIS_TS], String(*mUtcTimestamp).c_str(), true);
return true;
}
@ -138,7 +138,6 @@ class PubMqtt {
}
void payloadEventListener(uint8_t cmd) {
connect();
if(mClient.connected()) { // prevent overflow if MQTT broker is not reachable but set
if((0 == mCfgMqtt->interval) || (RealTimeRunData_Debug != cmd)) // no interval or no live data
mSendList.push(cmd);
@ -290,7 +289,7 @@ class PubMqtt {
if (NULL != iv) {
char topic[7 + MQTT_TOPIC_LEN];
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/ack_pwr_limit", iv->config->name);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, subtopics[MQTT_ACK_PWR_LMT]);
publish(topic, "true", true);
}
}
@ -299,18 +298,18 @@ class PubMqtt {
void onConnect(bool sessionPreset) {
DPRINTLN(DBG_INFO, F("MQTT connected"));
publish("version", mVersion, true);
publish("device", mDevName, true);
publish("ip_addr", WiFi.localIP().toString().c_str(), true);
publish(subtopics[MQTT_VERSION], mVersion, true);
publish(subtopics[MQTT_DEVICE], mDevName, true);
publish(subtopics[MQTT_IP_ADDR], WiFi.localIP().toString().c_str(), true);
tickerMinute();
publish(mLwtTopic, mLwtOnline, true, false);
subscribe("ctrl/limit_persistent_relative");
subscribe("ctrl/limit_persistent_absolute");
subscribe("ctrl/limit_nonpersistent_relative");
subscribe("ctrl/limit_nonpersistent_absolute");
subscribe("setup/set_time");
subscribe("setup/sync_ntp");
publish(mLwtTopic, mqttStr[MQTT_STR_LWT_CONN], true, false);
subscribe(subscr[MQTT_SUBS_LMT_PERI_REL]);
subscribe(subscr[MQTT_SUBS_LMT_PERI_ABS]);
subscribe(subscr[MQTT_SUBS_LMT_NONPERI_REL]);
subscribe(subscr[MQTT_SUBS_LMT_NONPERI_ABS]);
subscribe(subscr[MQTT_SUBS_SET_TIME]);
subscribe(subscr[MQTT_SUBS_SYNC_NTP]);
//subscribe("status/#");
}
@ -341,16 +340,14 @@ class PubMqtt {
}
void onMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total) {
DPRINTLN(DBG_INFO, F("MQTT got topic: ") + String(topic));
DPRINT(DBG_INFO, mqttStr[MQTT_STR_GOT_TOPIC]);
DBGPRINTLN(String(topic));
if(NULL == mSubscriptionCb)
return;
char *tpc = new char[strlen(topic) + 1];
uint8_t cnt = 0;
DynamicJsonDocument json(128);
JsonObject root = json.to<JsonObject>();
strncpy(tpc, topic, strlen(topic) + 1);
if(len > 0) {
char *pyld = new char[len + 1];
strncpy(pyld, (const char*)payload, len);
@ -359,34 +356,28 @@ class PubMqtt {
delete[] pyld;
}
char *p = strtok(tpc, "/");
p = strtok(NULL, "/"); // remove mCfgMqtt->topic
while(NULL != p) {
if(0 == cnt) {
if(0 == strncmp(p, "ctrl", 4)) {
if(NULL != (p = strtok(NULL, "/"))) {
root[F("path")] = F("ctrl");
root[F("cmd")] = p;
}
} else if(0 == strncmp(p, "setup", 5)) {
if(NULL != (p = strtok(NULL, "/"))) {
root[F("path")] = F("setup");
root[F("cmd")] = p;
}
} else if(0 == strncmp(p, "status", 6)) {
if(NULL != (p = strtok(NULL, "/"))) {
root[F("path")] = F("status");
root[F("cmd")] = p;
}
const char *p = topic;
uint8_t pos = 0;
uint8_t elm = 0;
char tmp[30];
while(1) {
if(('/' == p[pos]) || ('\0' == p[pos])) {
strncpy(tmp, p, pos);
tmp[pos] = '\0';
switch(elm++) {
case 1: root[F("path")] = String(tmp); break;
case 2: root[F("cmd")] = String(tmp); break;
case 3: root[F("id")] = atoi(tmp); break;
default: break;
}
if('\0' == p[pos])
break;
p = p + pos + 1;
pos = 0;
}
else if(1 == cnt) {
root[F("id")] = atoi(p);
}
p = strtok(NULL, "/");
cnt++;
pos++;
}
delete[] tpc;
/*char out[128];
serializeJson(root, out, 128);
@ -447,11 +438,11 @@ class PubMqtt {
mLastIvState[id] = status;
changed = true;
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/available", iv->config->name);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, mqttStr[MQTT_STR_AVAILABLE]);
snprintf(val, 40, "%d", status);
publish(topic, val, true);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/last_success", iv->config->name);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", iv->config->name, mqttStr[MQTT_STR_LAST_SUCCESS]);
snprintf(val, 40, "%d", iv->getLastTs(rec));
publish(topic, val, true);
}
@ -459,7 +450,7 @@ class PubMqtt {
if(changed) {
snprintf(val, 32, "%d", ((allAvail) ? MQTT_STATUS_ONLINE : ((anyAvail) ? MQTT_STATUS_PARTIAL : MQTT_STATUS_OFFLINE)));
publish("status", val, true);
publish(subtopics[MQTT_STATUS], val, true);
}
return anyAvail;
@ -471,9 +462,9 @@ class PubMqtt {
Inverter<> *iv = mSys->getInverterByPos(0, false);
while(!mAlarmList.empty()) {
alarm_t alarm = mAlarmList.front();
publish("alarm", iv->getAlarmStr(alarm.code).c_str());
publish("alarm_start", String(alarm.start).c_str());
publish("alarm_end", String(alarm.end).c_str());
publish(subtopics[MQTT_ALARM], iv->getAlarmStr(alarm.code).c_str());
publish(subtopics[MQTT_ALARM_START], String(alarm.start).c_str());
publish(subtopics[MQTT_ALARM_END], String(alarm.end).c_str());
mAlarmList.pop();
}
}
@ -573,7 +564,7 @@ class PubMqtt {
fieldId = FLD_PDC;
break;
}
snprintf(topic, 32 + MAX_NAME_LENGTH, "total/%s", fields[fieldId]);
snprintf(topic, 32 + MAX_NAME_LENGTH, "%s/%s", mqttStr[MQTT_STR_TOTAL], fields[fieldId]);
snprintf(val, 40, "%g", ah::round3(total[i]));
publish(topic, val, true);
}
@ -606,8 +597,6 @@ class PubMqtt {
// last will topic and payload must be available trough lifetime of 'espMqttClient'
char mLwtTopic[MQTT_TOPIC_LEN+5];
const char* mLwtOnline = "connected";
const char* mLwtOffline = "not connected";
const char *mDevName, *mVersion;
};

104
src/publisher/pubMqttDefs.h

@ -0,0 +1,104 @@
//-----------------------------------------------------------------------------
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
#ifndef __PUB_MQTT_DEFS_H__
#define __PUB_MQTT_DEFS_H__
#include <Arduino.h>
enum {
STR_TRUE,
STR_FALSE
};
const char* const dict[] PROGMEM = {
"true",
"false"
};
enum {
MQTT_STR_LWT_CONN,
MQTT_STR_LWT_NOT_CONN,
MQTT_STR_AVAILABLE,
MQTT_STR_LAST_SUCCESS,
MQTT_STR_TOTAL,
MQTT_STR_GOT_TOPIC
};
const char* const mqttStr[] PROGMEM = {
"connected",
"not connected",
"available",
"last_success",
"total",
"MQTT got topic: "
};
enum {
MQTT_UPTIME = 0,
MQTT_RSSI,
MQTT_FREE_HEAP,
MQTT_SUNRISE,
MQTT_SUNSET,
MQTT_COMM_START,
MQTT_COMM_STOP,
MQTT_DIS_NIGHT_COMM,
MQTT_COMM_DISABLED,
MQTT_COMM_DIS_TS,
MQTT_VERSION,
MQTT_DEVICE,
MQTT_IP_ADDR,
MQTT_STATUS,
MQTT_ALARM,
MQTT_ALARM_START,
MQTT_ALARM_END,
MQTT_LWT_ONLINE,
MQTT_LWT_OFFLINE,
MQTT_ACK_PWR_LMT
};
const char* const subtopics[] PROGMEM = {
"uptime",
"wifi_rssi",
"free_heap",
"sunrise",
"sunset",
"comm_start",
"comm_stop",
"dis_night_comm",
"comm_disabled",
"comm_dis_ts",
"version",
"device",
"ip_addr",
"status",
"alarm",
"alarm_start",
"alarm_end",
"connected",
"not connected",
"ack_pwr_limit"
};
enum {
MQTT_SUBS_LMT_PERI_REL,
MQTT_SUBS_LMT_PERI_ABS,
MQTT_SUBS_LMT_NONPERI_REL,
MQTT_SUBS_LMT_NONPERI_ABS,
MQTT_SUBS_SET_TIME,
MQTT_SUBS_SYNC_NTP
};
const char* const subscr[] PROGMEM = {
"ctrl/limit_persistent_relative",
"ctrl/limit_persistent_absolute",
"ctrl/limit_nonpersistent_relative",
"ctrl/limit_nonpersistent_absolute",
"setup/set_time",
"setup/sync_ntp"
};
#endif /*__PUB_MQTT_DEFS_H__*/

56
src/utils/dbg.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -19,6 +19,8 @@
#define DBG_DEBUG 4
#define DBG_VERBOSE 5
//#define LOG_MAX_MSG_LEN 100
//-----------------------------------------------------------------------------
// globally used level
@ -58,7 +60,7 @@
mCb(String(b, HEX));
}
}
inline void DHEX(uint16_t b) {
/*inline void DHEX(uint16_t b) {
if( b<0x10 ) DSERIAL.print(F("000"));
else if( b<0x100 ) DSERIAL.print(F("00"));
else if( b<0x1000 ) DSERIAL.print(F("0"));
@ -89,7 +91,7 @@
else if( b<0x10000000 ) mCb(F("0"));
mCb(String(b, HEX));
}
}
}*/
#endif
#endif
@ -154,4 +156,52 @@
}\
})
/*class ahoyLog {
public:
ahoyLog() {}
inline void logMsg(uint8_t lvl, bool newLine, const char *fmt, va_list args) {
snprintf(mLogBuf, LOG_MAX_MSG_LEN, fmt, args);
DSERIAL.print(mLogBuf);
if(NULL != mCb)
mCb(mLogBuf);
if(newLine) {
DSERIAL.print(F("\r\n"));
if(NULL != mCb)
mCb(F("\r\n"));
}
}
inline void logError(const char *fmt, ...) {
#if DEBUG_LEVEL >= DBG_ERROR
va_list args;
va_start(args, fmt);
logMsg(DBG_ERROR, true, fmt, args);
va_end(args);
#endif
}
inline void logWarn(const char *fmt, ...) {
#if DEBUG_LEVEL >= DBG_WARN
va_list args;
va_start(args, fmt);
logMsg(DBG_ERROR, true, fmt, args);
va_end(args);
#endif
}
inline void logInfo(const char *fmt, ...) {
#if DEBUG_LEVEL >= DBG_INFO
va_list args;
va_start(args, fmt);
logMsg(DBG_ERROR, true, fmt, args);
va_end(args);
#endif
}
private:
char mLogBuf[LOG_MAX_MSG_LEN];
DBG_CB mCb = NULL;
};*/
#endif /*__DBG_H__*/

18
src/utils/helper.cpp

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://github.com/lumpapu/ahoy
// 2023 Ahoy, https://github.com/lumpapu/ahoy
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -7,15 +7,15 @@
namespace ah {
void ip2Arr(uint8_t ip[], const char *ipStr) {
char tmp[16];
uint8_t p = 1;
memset(ip, 0, 4);
memset(tmp, 0, 16);
snprintf(tmp, 16, ipStr);
char *p = strtok(tmp, ".");
uint8_t i = 0;
while(NULL != p) {
ip[i++] = atoi(p);
p = strtok(NULL, ".");
for(uint8_t i = 0; i < 16; i++) {
if(ipStr[i] == 0)
return;
if(0 == i)
ip[0] = atoi(ipStr);
else if(ipStr[i] == '.')
ip[p++] = atoi(&ipStr[i+1]);
}
}

8
src/utils/scheduler.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://ahoydtu.de
// 2023 Ahoy, https://ahoydtu.de
// Lukas Pusch, lukas@lpusch.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -106,7 +106,11 @@ namespace ah {
void printSchedulers() {
for (uint8_t i = 0; i < MAX_NUM_TICKER; i++) {
if (mTickerInUse[i]) {
DPRINTLN(DBG_INFO, String(mTicker[i].name) + ", tmt: " + String(mTicker[i].timeout) + ", rel: " + String(mTicker[i].reload));
DPRINT(DBG_INFO, String(mTicker[i].name));
DBGPRINT(", tmt: ");
DBGPRINT(String(mTicker[i].timeout));
DBGPRINT(", rel: ");
DBGPRINTLN(String(mTicker[i].reload));
}
}
}

29
src/web/RestApi.h

@ -1,3 +1,8 @@
//-----------------------------------------------------------------------------
// 2023 Ahoy, https://ahoydtu.de
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
#ifndef __WEB_API_H__
#define __WEB_API_H__
@ -24,7 +29,9 @@ class RestApi {
public:
RestApi() {
mTimezoneOffset = 0;
mFreeHeap = 0;
mHeapFree = 0;
mHeapFreeBlk = 0;
mHeapFrag = 0;
nr = 0;
}
@ -49,7 +56,7 @@ class RestApi {
serializeJson(obj, out, 128);
DPRINTLN(DBG_INFO, "RestApi: " + String(out));*/
DynamicJsonDocument json(128);
JsonObject dummy = json.to<JsonObject>();
JsonObject dummy = json.as<JsonObject>();
if(obj[F("path")] == "ctrl")
setCtrl(obj, dummy);
else if(obj[F("path")] == "setup")
@ -58,7 +65,11 @@ class RestApi {
private:
void onApi(AsyncWebServerRequest *request) {
mFreeHeap = ESP.getFreeHeap();
mHeapFree = ESP.getFreeHeap();
#ifndef ESP32
mHeapFreeBlk = ESP.getMaxFreeBlockSize();
mHeapFrag = ESP.getHeapFragmentation();
#endif
AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192);
JsonObject root = response->getRoot();
@ -84,6 +95,7 @@ class RestApi {
else
getNotFound(root, F("http://") + request->host() + F("/api/"));
//DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage()));
response->addHeader("Access-Control-Allow-Origin", "*");
response->addHeader("Access-Control-Allow-Headers", "content-type");
response->setLength();
@ -194,7 +206,7 @@ class RestApi {
obj[F("sdk")] = ESP.getSdkVersion();
obj[F("cpu_freq")] = ESP.getCpuFreqMHz();
obj[F("heap_free")] = mFreeHeap;
obj[F("heap_free")] = mHeapFree;
obj[F("sketch_total")] = ESP.getFreeSketchSpace();
obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb
getGeneric(obj);
@ -219,8 +231,8 @@ class RestApi {
//obj[F("chip_cores")] = F("n/a");
obj[F("core_version")] = ESP.getCoreVersion();
obj[F("flash_size")] = ESP.getFlashChipRealSize() / 1024; // in kb
obj[F("heap_frag")] = ESP.getHeapFragmentation();
obj[F("max_free_blk")] = ESP.getMaxFreeBlockSize();
obj[F("heap_frag")] = mHeapFrag;
obj[F("max_free_blk")] = mHeapFreeBlk;
obj[F("reboot_reason")] = ESP.getResetReason();
#endif
//obj[F("littlefs_total")] = LittleFS.totalBytes();
@ -498,7 +510,7 @@ class RestApi {
obj2[F("name")] = String(iv->config->name);
obj2[F("channels")] = iv->channels;
obj2[F("power_limit_read")] = ah::round3(iv->actPowerLimit);
obj2[F("last_alarm")] = String(iv->lastAlarmMsg);
//obj2[F("last_alarm")] = String(iv->lastAlarmMsg);
obj2[F("ts_last_success")] = rec->ts;
JsonArray ch = obj2.createNestedArray("ch");
@ -625,7 +637,8 @@ class RestApi {
settings_t *mConfig;
uint32_t mTimezoneOffset;
uint32_t mFreeHeap;
uint32_t mHeapFree, mHeapFreeBlk;
uint8_t mHeapFrag;
uint16_t nr;
};

2
src/web/html/visualization.html

@ -67,7 +67,7 @@
var limit = iv["power_limit_read"] + "%";
if(limit == "65535%")
limit = "n/a";
ch0.appendChild(span(iv["name"] + " Limit " + limit + " | last Alarm: " + iv["last_alarm"], ["head"]));
ch0.appendChild(span(iv["name"] + " Limit " + limit, ["head"]));
for(var j = 0; j < root.ch0_fld_names.length; j++) {
var val = Math.round(iv["ch"][0][j] * 100) / 100;

24
src/wifi/ahoywifi.cpp

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -72,7 +72,7 @@ void ahoywifi::tickWifiLoop() {
mScanActive = false;
}
DBGPRINTLN(F("AP client connected"));
welcome(mApIp.toString());
welcome(mApIp.toString(), "");
WiFi.mode(WIFI_AP);
mDns.start(53, "*", mApIp);
mAppWifiCb(true);
@ -118,7 +118,7 @@ void ahoywifi::tickWifiLoop() {
if(mStaConn != CONNECTED)
mStaConn = CONNECTING;
if(mBSSIDList.size() > 0) { // get first BSSID in list
DBGPRINT("try to connect to AP with BSSID:");
DBGPRINT(F("try to connect to AP with BSSID:"));
uint8_t bssid[6];
for (int j = 0; j < 6; j++) {
bssid[j] = mBSSIDList.front();
@ -142,7 +142,11 @@ void ahoywifi::setupAp(void) {
DBGPRINTLN(F("\n---------\nAhoyDTU Info:"));
DBGPRINT(F("Version: "));
DBGPRINTLN(String(VERSION_MAJOR) + F(".") + String(VERSION_MINOR) + F(".") + String(VERSION_PATCH));
DBGPRINT(String(VERSION_MAJOR));
DBGPRINT(F("."));
DBGPRINT(String(VERSION_MINOR));
DBGPRINT(F("."));
DBGPRINTLN(String(VERSION_PATCH));
DBGPRINT(F("Github Hash: "));
DBGPRINTLN(String(AUTO_GIT_HASH));
@ -150,7 +154,8 @@ void ahoywifi::setupAp(void) {
DBGPRINTLN(WIFI_AP_SSID);
DBGPRINT(F("PWD: "));
DBGPRINTLN(WIFI_AP_PWD);
DBGPRINTLN("IP Address: http://" + mApIp.toString());
DBGPRINT(F("IP Address: http://"));
DBGPRINTLN(mApIp.toString());
DBGPRINTLN(F("---------\n"));
WiFi.mode(WIFI_AP_STA);
@ -327,9 +332,11 @@ void ahoywifi::connectionEvent(WiFiStatus_t status) {
WiFi.scanDelete();
mScanActive = false;
}
welcome(WiFi.localIP().toString() + F(" (Station)"));
welcome(WiFi.localIP().toString(), F(" (Station)"));
WiFi.softAPdisconnect();
WiFi.mode(WIFI_STA);
DBGPRINTLN(F("[WiFi] AP disabled"));
delay(100);
mAppWifiCb(true);
break;
@ -393,11 +400,12 @@ void ahoywifi::connectionEvent(WiFiStatus_t status) {
//-----------------------------------------------------------------------------
void ahoywifi::welcome(String msg) {
void ahoywifi::welcome(String ip, String mode) {
DBGPRINTLN(F("\n\n--------------------------------"));
DBGPRINTLN(F("Welcome to AHOY!"));
DBGPRINT(F("\npoint your browser to http://"));
DBGPRINTLN(msg);
DBGPRINT(ip);
DBGPRINTLN(mode);
DBGPRINTLN(F("to configure your device"));
DBGPRINTLN(F("--------------------------------\n"));
}

4
src/wifi/ahoywifi.h

@ -1,5 +1,5 @@
//-----------------------------------------------------------------------------
// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778
// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/
//-----------------------------------------------------------------------------
@ -52,7 +52,7 @@ class ahoywifi {
#else
void onWiFiEvent(WiFiEvent_t event);
#endif
void welcome(String msg);
void welcome(String ip, String mode);
settings_t *mConfig;

Loading…
Cancel
Save