Changes in user interface

This commit is contained in:
Gunnar Skjold
2021-01-14 16:19:00 +01:00
parent 837c3cf802
commit 037bac24de
11 changed files with 339 additions and 179 deletions

View File

@@ -287,6 +287,30 @@ void AmsData::extractFromOmnipower(HanReader& hanReader, int listSize) {
}
void AmsData::apply(AmsData& other) {
if(other.getListType() < 3) {
unsigned long ms = this->lastUpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastUpdateMillis;
if(ms > 0) {
if(other.getActiveImportPower() > 0)
activeImportCounter += (((double) ms) * other.getActiveImportPower()) / 3600000000;
counterEstimated = true;
}
if(other.getListType() > 1) {
unsigned long ms2 = this->lastList2UpdateMillis > other.getLastUpdateMillis() ? 0 : other.getLastUpdateMillis() - this->lastList2UpdateMillis;
if(ms2 > 0) {
// Not sure why, but I cannot make these numbers correct. It seems to be double of what it should, so dividing it by two...
if(other.getActiveExportPower() > 0)
activeExportCounter += (((double) ms2/2) * other.getActiveExportPower()) / 3600000000;
if(other.getReactiveImportPower() > 0)
reactiveImportCounter += (((double) ms2/2) * other.getReactiveImportPower()) / 3600000000;
if(other.getReactiveExportPower() > 0)
reactiveExportCounter += (((double) ms2/2) * other.getReactiveExportPower()) / 3600000000;
counterEstimated = true;
}
}
}
this->lastUpdateMillis = other.getLastUpdateMillis();
this->packageTimestamp = other.getPackageTimestamp();
this->listType = max(this->listType, other.getListType());
@@ -297,6 +321,7 @@ void AmsData::apply(AmsData& other) {
this->activeExportCounter = other.getActiveExportCounter();
this->reactiveImportCounter = other.getReactiveImportCounter();
this->reactiveExportCounter = other.getReactiveExportCounter();
this->counterEstimated = false;
case 2:
this->listId = other.getListId();
this->meterId = other.getMeterId();
@@ -311,6 +336,7 @@ void AmsData::apply(AmsData& other) {
this->l2voltage = other.getL2Voltage();
this->l3voltage = other.getL3Voltage();
this->threePhase = other.isThreePhase();
this->lastList2UpdateMillis = other.getLastUpdateMillis();
case 1:
this->activeImportPower = other.getActiveImportPower();
}

View File

@@ -51,6 +51,7 @@ public:
private:
unsigned long lastUpdateMillis = 0;
unsigned long lastList2UpdateMillis = 0;
int listType = 0;
unsigned long packageTimestamp = 0;
String listId, meterId, meterType;
@@ -58,7 +59,7 @@ private:
int activeImportPower = 0, reactiveImportPower = 0, activeExportPower = 0, reactiveExportPower = 0;
double l1voltage = 0, l2voltage = 0, l3voltage = 0, l1current = 0, l2current = 0, l3current = 0;
double activeImportCounter = 0, reactiveImportCounter = 0, activeExportCounter = 0, reactiveExportCounter = 0;
bool threePhase = false;
bool threePhase = false, counterEstimated = false;
void extractFromKaifa(HanReader& hanReader, int listSize);
void extractFromAidon(HanReader& hanReader, int listSize, bool substituteMissing);

View File

@@ -459,11 +459,11 @@ void loop() {
delay(1);
readHanPort();
if(WiFi.status() == WL_CONNECTED) {
ws.loop();
if(eapi.loop()) {
sendPricesToMqtt();
}
//if(eapi.loop()) {
// sendPricesToMqtt();
//}
}
ws.loop();
delay(1); // Needed for auto modem sleep
}
@@ -611,62 +611,77 @@ void mqttMessageReceived(String &topic, String &payload)
}
void sendPricesToMqtt() {
if(strlen(config.getMqttHost()) == 0 || strlen(config.getMqttPublishTopic()) == 0)
return;
if(strcmp(config.getEntsoeApiToken(), "") != 0)
return;
time_t now = time(nullptr);
double min1hr, min3hr, min6hr;
int min1hrIdx = -1, min3hrIdx = -1, min6hrIdx = -1;
double min = INT16_MAX, max = INT16_MIN;
double values[48];
for(int i = 0; i < 48; i++) {
double val1 = eapi.getValueForHour(i);
values[i] = val1;
double values[24] = {0};
for(int i = 0; i < 24; i++) {
double val = eapi.getValueForHour(now, i);
values[i] = val;
if(val1 == ENTSOE_NO_VALUE) break;
if(val == ENTSOE_NO_VALUE) break;
if(val1 < min) min = val1;
if(val1 > max) max = val1;
if(val < min) min = val;
if(val > max) max = val;
if(i >= 24) continue; // Only estimate 1hr, 3hr and 6hr cheapest interval for next 24 hrs
if(min1hrIdx == -1 || min1hr > val1) {
min1hr = val1;
if(min1hrIdx == -1 || min1hr > val) {
min1hr = val;
min1hrIdx = i;
}
double val2 = eapi.getValueForHour(i+1);
double val3 = eapi.getValueForHour(i+2);
if(val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue;
double val3hr = val1+val2+val3;
if(min3hrIdx == -1 || min3hr > val3hr) {
min3hr = val3hr;
min3hrIdx = i;
if(i >= 2) {
double val1 = values[i-2];
double val2 = values[i-1];
double val3 = val;
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE) continue;
double val3hr = val1+val2+val3;
if(min3hrIdx == -1 || min3hr > val3hr) {
min3hr = val3hr;
min3hrIdx = i-2;
}
}
double val4 = eapi.getValueForHour(i+3);
double val5 = eapi.getValueForHour(i+4);
double val6 = eapi.getValueForHour(i+5);
if(val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue;
double val6hr = val1+val2+val3+val4+val5+val6;
if(min6hrIdx == -1 || min6hr > val6hr) {
min6hr = val6hr;
min6hrIdx = i;
if(i >= 5) {
double val1 = values[i-5];
double val2 = values[i-4];
double val3 = values[i-3];
double val4 = values[i-2];
double val5 = values[i-1];
double val6 = val;
if(val1 == ENTSOE_NO_VALUE || val2 == ENTSOE_NO_VALUE || val3 == ENTSOE_NO_VALUE || val4 == ENTSOE_NO_VALUE || val5 == ENTSOE_NO_VALUE || val6 == ENTSOE_NO_VALUE) continue;
double val6hr = val1+val2+val3+val4+val5+val6;
if(min6hrIdx == -1 || min6hr > val6hr) {
min6hr = val6hr;
min6hrIdx = i-5;
}
}
}
char ts1hr[21];
if(min1hrIdx != -1) {
tmElements_t tm;
breakTime(time(nullptr) + (SECS_PER_HOUR * min1hrIdx), tm);
breakTime(now + (SECS_PER_HOUR * min1hrIdx), tm);
sprintf(ts1hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts3hr[21];
if(min3hrIdx != -1) {
tmElements_t tm;
breakTime(time(nullptr) + (SECS_PER_HOUR * min3hrIdx), tm);
breakTime(now + (SECS_PER_HOUR * min3hrIdx), tm);
sprintf(ts3hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
char ts6hr[21];
if(min6hrIdx != -1) {
tmElements_t tm;
breakTime(time(nullptr) + (SECS_PER_HOUR * min6hrIdx), tm);
breakTime(now + (SECS_PER_HOUR * min6hrIdx), tm);
sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
}
@@ -678,33 +693,37 @@ void sendPricesToMqtt() {
json["name"] = config.getMqttClientId();
json["up"] = millis();
JsonObject jp = json.createNestedObject("prices");
for(int i = 0; i < 48; i++) {
double val = values[i];
if(val == ENTSOE_NO_VALUE) break;
jp[String(i)] = serialized(String(val, 4));
}
if(min != INT16_MAX) {
jp["min"] = serialized(String(min, 4));
}
if(max != INT16_MIN) {
jp["max"] = serialized(String(max, 4));
}
if(min1hrIdx != -1) {
jp["cheapest1hr"] = String(ts1hr);
}
if(min3hrIdx != -1) {
jp["cheapest3hr"] = String(ts1hr);
}
if(min6hrIdx != -1) {
jp["cheapest6hr"] = String(ts1hr);
}
for(int i = 0; i < 24; i++) {
double val = values[i];
if(val == ENTSOE_NO_VALUE) break;
jp[String(i)] = serialized(String(val, 4));
}
if(min != INT16_MAX) {
jp["min"] = serialized(String(min, 4));
}
if(max != INT16_MIN) {
jp["max"] = serialized(String(max, 4));
}
if(min1hrIdx != -1) {
jp["cheapest1hr"] = String(ts1hr);
}
if(min3hrIdx != -1) {
jp["cheapest3hr"] = String(ts3hr);
}
if(min6hrIdx != -1) {
jp["cheapest6hr"] = String(ts6hr);
}
String msg;
serializeJson(json, msg);
mqtt.publish(config.getMqttPublishTopic(), msg.c_str());
break;
}
case 1: // RAW
case 2:
// Send updated prices if we have them
if(strcmp(config.getEntsoeApiToken(), "") != 0) {
for(int i = 0; i < 48; i++) {
{
for(int i = 0; i < 24; i++) {
double val = values[i];
if(val == ENTSOE_NO_VALUE) {
mqtt.publish(String(config.getMqttPublishTopic()) + "/price/" + String(i), "");
@@ -712,6 +731,8 @@ void sendPricesToMqtt() {
} else {
mqtt.publish(String(config.getMqttPublishTopic()) + "/price/" + String(i), String(val, 4));
}
mqtt.loop();
delay(10);
}
if(min != INT16_MAX) {
mqtt.publish(String(config.getMqttPublishTopic()) + "/price/min", String(min, 4));
@@ -719,6 +740,7 @@ void sendPricesToMqtt() {
if(max != INT16_MIN) {
mqtt.publish(String(config.getMqttPublishTopic()) + "/price/max", String(max, 4));
}
if(min1hrIdx != -1) {
mqtt.publish(String(config.getMqttPublishTopic()) + "/price/cheapest/1hr", String(ts1hr));
}
@@ -910,7 +932,6 @@ void readHanPort() {
mqtt.publish(String(config.getMqttPublishTopic()) + "/meter/import/active/accumulated", String(data.getActiveImportCounter(), 2));
mqtt.publish(String(config.getMqttPublishTopic()) + "/meter/export/reactive/accumulated", String(data.getReactiveExportCounter(), 2));
mqtt.publish(String(config.getMqttPublishTopic()) + "/meter/export/active/accumulated", String(data.getActiveExportCounter(), 2));
sendPricesToMqtt();
case 2:
// Only send data if changed. ID and Type is sent on the 10s interval only if changed

View File

@@ -40,10 +40,13 @@ void EntsoeApi::setMultiplier(double multiplier) {
char* EntsoeApi::getCurrency() {
return currency;
}
double EntsoeApi::getValueForHour(int hour) {
tmElements_t tm;
time_t cur = time(nullptr);
return getValueForHour(cur, hour);
}
double EntsoeApi::getValueForHour(time_t cur, int hour) {
tmElements_t tm;
if(tz != NULL)
cur = tz->toLocal(cur);
breakTime(cur, tm);

View File

@@ -15,6 +15,7 @@ public:
bool loop();
double getValueForHour(int hour);
double getValueForHour(time_t now, int hour);
char* getCurrency();
void setToken(const char* token);

View File

@@ -295,19 +295,18 @@ void AmsWebServer::indexHtml() {
html.replace("${data.P}", String(data.getActiveImportPower()));
html.replace("${data.PO}", String(data.getActiveExportPower()));
html.replace("${display.export}", config->getProductionCapacity() > 0 ? "" : "none");
html.replace("${text.import}", config->getProductionCapacity() > 0 ? "Import" : "Consumption");
html.replace("${display.nonexport}", config->getProductionCapacity() > 0 ? "none" : "");
html.replace("${text.import}", config->getProductionCapacity() > 0 ? "Import" : "Use");
html.replace("${display.3p}", data.isThreePhase() ? "" : "none");
html.replace("${data.U1}", u1 > 0 ? String(u1, 1) : "");
html.replace("${data.I1}", u1 > 0 ? String(i1, 1) : "");
html.replace("${display.P1}", u1 > 0 ? "" : "none");
html.replace("${data.U2}", u2 > 0 ? String(u2, 1) : "");
html.replace("${data.I2}", u2 > 0 ? String(i2, 1) : "");
html.replace("${display.P2}", u2 > 0 ? "" : "none");
html.replace("${data.U3}", u3 > 0 ? String(u3, 1) : "");
html.replace("${data.I3}", u3 > 0 ? String(i3, 1) : "");
html.replace("${display.P3}", u3 > 0 ? "" : "none");
html.replace("${data.tPI}", tpi > 0 ? String(tpi, 1) : "");
html.replace("${data.tPO}", tpi > 0 ? String(tpo, 1) : "");
@@ -327,6 +326,8 @@ void AmsWebServer::indexHtml() {
html.replace("${wifi.channel}", WiFi.channel() > 0 ? String(WiFi.channel()) : "");
html.replace("${wifi.ssid}", !WiFi.SSID().isEmpty() ? String(WiFi.SSID()) : "");
html.replace("${currentSeconds}", String((uint32_t)(millis64()/1000), 10));
server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN);
server.send_P(200, "text/html", HEAD_HTML);
server.sendContent(html);
@@ -628,6 +629,9 @@ void AmsWebServer::dataJson() {
double tqi = data.getReactiveImportCounter();
double tqo = data.getReactiveExportCounter();
double volt = u1;
double amp = i1;
if(u1 > 0) {
json["data"]["U1"] = u1;
json["data"]["I1"] = i1;
@@ -635,10 +639,13 @@ void AmsWebServer::dataJson() {
if(u2 > 0) {
json["data"]["U2"] = u2;
json["data"]["I2"] = i2;
if(i2 > amp) amp = i2;
}
if(u3 > 0) {
json["data"]["U3"] = u3;
json["data"]["I3"] = i3;
volt = (u1+u2+u3)/3;
if(i3 > amp) amp = i3;
}
if(tpi > 0) {
@@ -650,6 +657,14 @@ void AmsWebServer::dataJson() {
json["p_pct"] = min(data.getActiveImportPower()*100/maxPwr, 100);
json["v"] = volt;
json["v_pct"] = (max((int)volt-207, 1)*100/46);
int maxAmp = config->getMainFuse() == 0 ? 32 : config->getMainFuse();
json["a"] = amp;
json["a_pct"] = amp * 100 / maxAmp;
if(config->getProductionCapacity() > 0) {
int maxPrd = config->getProductionCapacity() * 1000;
json["po_pct"] = min(data.getActiveExportPower()*100/maxPrd, 100);