mirror of
https://github.com/lumapu/ahoy.git
synced 2025-06-07 13:11:38 +02:00
corrected MQTT comm_disabled
#529
fix Prometheus and JSON endpoints (`config_override.h`) #561 publish MQTT with fixed interval even if inverter is not available #542 added JSON settings upload. NOTE: settings JSON download changed, so only settings should be uploaded starting from version `0.5.70` #551 MQTT topic and inverter name have more allowed characters: `[A-Za-z0-9./#$%&=+_-]+`, thx: @Mo Demman improved potential issue with `checkTicker`, thx @cbscpe MQTT option for reset values on midnight / not avail / communication stop #539 small fix in `tickIVCommunication` #534 add `YieldTotal` correction, eg. to have the option to zero at year start #512
This commit is contained in:
parent
4265856814
commit
d8e255ddc2
16 changed files with 346 additions and 218 deletions
133
src/web/web.h
133
src/web/web.h
|
@ -82,7 +82,9 @@ class Web {
|
|||
|
||||
mWeb.on("/update", HTTP_GET, std::bind(&Web::onUpdate, this, std::placeholders::_1));
|
||||
mWeb.on("/update", HTTP_POST, std::bind(&Web::showUpdate, this, std::placeholders::_1),
|
||||
std::bind(&Web::showUpdate2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6));
|
||||
std::bind(&Web::showUpdate2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6));
|
||||
mWeb.on("/upload", HTTP_POST, std::bind(&Web::onUpload, this, std::placeholders::_1),
|
||||
std::bind(&Web::onUpload2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6));
|
||||
mWeb.on("/serial", HTTP_GET, std::bind(&Web::onSerial, this, std::placeholders::_1));
|
||||
|
||||
|
||||
|
@ -92,6 +94,8 @@ class Web {
|
|||
mWeb.begin();
|
||||
|
||||
registerDebugCb(std::bind(&Web::serialCb, this, std::placeholders::_1)); // dbg.h
|
||||
|
||||
mUploadFail = false;
|
||||
}
|
||||
|
||||
void tickSecond() {
|
||||
|
@ -150,6 +154,34 @@ class Web {
|
|||
}
|
||||
}
|
||||
|
||||
void onUpload2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
|
||||
if(!index) {
|
||||
mUploadFail = false;
|
||||
mUploadFp = LittleFS.open("/tmp.json", "w");
|
||||
if(!mUploadFp) {
|
||||
DPRINTLN(DBG_ERROR, F("can't open file!"));
|
||||
mUploadFail = true;
|
||||
mUploadFp.close();
|
||||
}
|
||||
}
|
||||
mUploadFp.write(data, len);
|
||||
if(final) {
|
||||
mUploadFp.close();
|
||||
File fp = LittleFS.open("/tmp.json", "r");
|
||||
if(!fp)
|
||||
mUploadFail = true;
|
||||
else {
|
||||
if(!mApp->readSettings("tmp.json")) {
|
||||
mUploadFail = true;
|
||||
DPRINTLN(DBG_ERROR, F("upload JSON error!"));
|
||||
}
|
||||
else
|
||||
mApp->saveSettings();
|
||||
}
|
||||
DPRINTLN(DBG_INFO, F("upload finished!"));
|
||||
}
|
||||
}
|
||||
|
||||
void serialCb(String msg) {
|
||||
if(!mSerialClientConnnected)
|
||||
return;
|
||||
|
@ -214,6 +246,23 @@ class Web {
|
|||
mApp->setRebootFlag();
|
||||
}
|
||||
|
||||
void onUpload(AsyncWebServerRequest *request) {
|
||||
bool reboot = !mUploadFail;
|
||||
|
||||
String html = F("<!doctype html><html><head><title>Upload</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Upload: ");
|
||||
if(reboot)
|
||||
html += "success";
|
||||
else
|
||||
html += "failed";
|
||||
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
||||
|
||||
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), html);
|
||||
response->addHeader("Connection", "close");
|
||||
request->send(response);
|
||||
if(reboot)
|
||||
mApp->setRebootFlag();
|
||||
}
|
||||
|
||||
void onConnect(AsyncEventSourceClient *client) {
|
||||
DPRINTLN(DBG_VERBOSE, "onConnect");
|
||||
|
||||
|
@ -429,6 +478,7 @@ class Web {
|
|||
case 0x61: iv->type = INV_TYPE_4CH; iv->channels = 4; break;
|
||||
default: break;
|
||||
}
|
||||
iv->config->yieldCor = request->arg("inv" + String(i) + "YieldCor").toInt();
|
||||
|
||||
// name
|
||||
request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH);
|
||||
|
@ -495,6 +545,9 @@ class Web {
|
|||
request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN);
|
||||
mConfig->mqtt.port = request->arg("mqttPort").toInt();
|
||||
mConfig->mqtt.interval = request->arg("mqttInterval").toInt();
|
||||
mConfig->mqtt.rstYieldMidNight = (request->arg("mqttRstMid") == "on");
|
||||
mConfig->mqtt.rstValsNotAvail = (request->arg("mqttRstComStop") == "on");
|
||||
mConfig->mqtt.rstValsCommStop = (request->arg("mqttRstNotAvail") == "on");
|
||||
|
||||
// serial console
|
||||
if(request->arg("serIntvl") != "") {
|
||||
|
@ -627,59 +680,68 @@ class Web {
|
|||
}
|
||||
|
||||
#ifdef ENABLE_JSON_EP
|
||||
void showJson(void) {
|
||||
void showJson(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("web::showJson"));
|
||||
String modJson;
|
||||
Inverter<> *iv;
|
||||
record_t<> *rec;
|
||||
char topic[40], val[25];
|
||||
|
||||
modJson = F("{\n");
|
||||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||
if(NULL != iv) {
|
||||
char topic[40], val[25];
|
||||
snprintf(topic, 30, "\"%s\": {\n", iv->name);
|
||||
modJson += String(topic);
|
||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
||||
snprintf(topic, 40, "\t\"ch%d/%s\"", iv->assign[i].ch, iv->getFieldName(i));
|
||||
snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i), iv->getUnit(i));
|
||||
modJson += String(topic) + ": " + String(val) + F(",\n");
|
||||
}
|
||||
modJson += F("\t\"last_msg\": \"") + ah::getDateTimeStr(iv->ts) + F("\"\n\t},\n");
|
||||
}
|
||||
}
|
||||
modJson += F("\"json_ts\": \"") + String(ah::getDateTimeStr(mMain->mTimestamp)) + F("\"\n}\n");
|
||||
iv = mSys->getInverterByPos(id);
|
||||
if(NULL == iv)
|
||||
continue;
|
||||
|
||||
mWeb.send(200, F("application/json"), modJson);
|
||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
snprintf(topic, 30, "\"%s\": {\n", iv->config->name);
|
||||
modJson += String(topic);
|
||||
for(uint8_t i = 0; i < rec->length; i++) {
|
||||
snprintf(topic, 40, "\t\"ch%d/%s\"", rec->assign[i].ch, iv->getFieldName(i, rec));
|
||||
snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i, rec), iv->getUnit(i, rec));
|
||||
modJson += String(topic) + ": " + String(val) + F(",\n");
|
||||
}
|
||||
modJson += F("\t\"last_msg\": \"") + ah::getDateTimeStr(rec->ts) + F("\"\n\t},\n");
|
||||
}
|
||||
modJson += F("\"json_ts\": \"") + String(ah::getDateTimeStr(mApp->getTimestamp())) + F("\"\n}\n");
|
||||
|
||||
AsyncWebServerResponse *response = request->beginResponse(200, F("application/json"), modJson);
|
||||
request->send(response);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_PROMETHEUS_EP
|
||||
void showMetrics(void) {
|
||||
void showMetrics(AsyncWebServerRequest *request) {
|
||||
DPRINTLN(DBG_VERBOSE, F("web::showMetrics"));
|
||||
String metrics;
|
||||
char headline[80];
|
||||
|
||||
snprintf(headline, 80, "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1", mApp->getVersion(), mconfig->sys.deviceName);
|
||||
snprintf(headline, 80, "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1", mApp->getVersion(), mConfig->sys.deviceName);
|
||||
metrics += "# TYPE ahoy_solar_info gauge\n" + String(headline) + "\n";
|
||||
|
||||
Inverter<> *iv;
|
||||
record_t<> *rec;
|
||||
char type[60], topic[60], val[25];
|
||||
for(uint8_t id = 0; id < mSys->getNumInverters(); id++) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(id);
|
||||
if(NULL != iv) {
|
||||
char type[60], topic[60], val[25];
|
||||
for(uint8_t i = 0; i < iv->listLen; i++) {
|
||||
uint8_t channel = iv->assign[i].ch;
|
||||
if(channel == 0) {
|
||||
String promUnit, promType;
|
||||
std::tie(promUnit, promType) = convertToPromUnits( iv->getUnit(i) );
|
||||
snprintf(type, 60, "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(i), promUnit.c_str(), promType.c_str());
|
||||
snprintf(topic, 60, "ahoy_solar_%s_%s{inverter=\"%s\"}", iv->getFieldName(i), promUnit.c_str(), iv->name);
|
||||
snprintf(val, 25, "%.3f", iv->getValue(i));
|
||||
metrics += String(type) + "\n" + String(topic) + " " + String(val) + "\n";
|
||||
}
|
||||
iv = mSys->getInverterByPos(id);
|
||||
if(NULL == iv)
|
||||
continue;
|
||||
|
||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
for(uint8_t i = 0; i < rec->length; i++) {
|
||||
uint8_t channel = rec->assign[i].ch;
|
||||
if(channel == 0) {
|
||||
String promUnit, promType;
|
||||
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(i, rec));
|
||||
snprintf(type, 60, "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(i, rec), promUnit.c_str(), promType.c_str());
|
||||
snprintf(topic, 60, "ahoy_solar_%s_%s{inverter=\"%s\"}", iv->getFieldName(i, rec), promUnit.c_str(), iv->config->name);
|
||||
snprintf(val, 25, "%.3f", iv->getValue(i, rec));
|
||||
metrics += String(type) + "\n" + String(topic) + " " + String(val) + "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mWeb.send(200, F("text/plain"), metrics);
|
||||
AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), metrics);
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
std::pair<String, String> convertToPromUnits(String shortUnit) {
|
||||
|
@ -708,6 +770,9 @@ class Web {
|
|||
char mSerialBuf[WEB_SERIAL_BUF_SIZE];
|
||||
uint16_t mSerialBufFill;
|
||||
bool mSerialClientConnnected;
|
||||
|
||||
File mUploadFp;
|
||||
bool mUploadFail;
|
||||
};
|
||||
|
||||
#endif /*__WEB_H__*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue