diff --git a/src/AmsData.cpp b/src/AmsData.cpp
index 9295af95..1082462e 100644
--- a/src/AmsData.cpp
+++ b/src/AmsData.cpp
@@ -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();
}
diff --git a/src/AmsData.h b/src/AmsData.h
index dbb0930b..a5d3f680 100644
--- a/src/AmsData.h
+++ b/src/AmsData.h
@@ -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);
diff --git a/src/AmsToMqttBridge.ino b/src/AmsToMqttBridge.ino
index c8108e43..0d4c198a 100644
--- a/src/AmsToMqttBridge.ino
+++ b/src/AmsToMqttBridge.ino
@@ -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
diff --git a/src/entsoe/EntsoeApi.cpp b/src/entsoe/EntsoeApi.cpp
index 64ed839b..ef147a58 100644
--- a/src/entsoe/EntsoeApi.cpp
+++ b/src/entsoe/EntsoeApi.cpp
@@ -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);
diff --git a/src/entsoe/EntsoeApi.h b/src/entsoe/EntsoeApi.h
index 4e790ced..cd1f377d 100644
--- a/src/entsoe/EntsoeApi.h
+++ b/src/entsoe/EntsoeApi.h
@@ -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);
diff --git a/src/web/AmsWebServer.cpp b/src/web/AmsWebServer.cpp
index fd8afbd1..207a21f8 100644
--- a/src/web/AmsWebServer.cpp
+++ b/src/web/AmsWebServer.cpp
@@ -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);
diff --git a/web/application.js b/web/application.js
index a3732eb4..749f3d40 100644
--- a/web/application.js
+++ b/web/application.js
@@ -1,5 +1,5 @@
var nextVersion;
-var im, em;
+var im, em, vm, am;
$(function() {
im = $("#importMeter");
if(im && im.gaugeMeter) {
@@ -18,6 +18,24 @@ $(function() {
append: "W"
});
}
+
+ vm = $("#voltMeter");
+ if(vm && vm.gaugeMeter) {
+ vm.gaugeMeter({
+ percent: 0,
+ text: "-",
+ append: "V"
+ });
+ }
+
+ am = $("#ampMeter");
+ if(am && am.gaugeMeter) {
+ am.gaugeMeter({
+ percent: 0,
+ text: "-",
+ append: "A"
+ });
+ }
var meters = $('.SimpleMeter');
@@ -217,10 +235,12 @@ var fetch = function() {
timeout: 10000,
dataType: 'json',
}).done(function(json) {
- if(im && em) {
+ if(im) {
$(".SimpleMeter").hide();
im.show();
em.show();
+ vm.show();
+ am.show();
}
for(var id in json) {
@@ -237,8 +257,8 @@ var fetch = function() {
}
if(window.moment) {
- $('#currentMillis').html(moment.duration(parseInt(json.uptime_seconds), 'seconds').humanize());
- $('#currentMillis').closest('.row').show();
+ $('.currentSeconds').html(moment.duration(parseInt(json.uptime_seconds), 'seconds').humanize());
+ $('.currentSeconds').closest('.row').show();
}
if(json.status) {
@@ -304,16 +324,46 @@ var fetch = function() {
});
}
+ var v = parseFloat(json.v);
+ if(v > 0) {
+ var v_pct = parseInt(json.v_pct);
+
+ if(vm && vm.gaugeMeter) {
+ vm.gaugeMeter({
+ percent: v_pct,
+ text: v.toFixed(1)
+ });
+ }
+ }
+
+ var a = parseFloat(json.a);
+ if(a > 0) {
+ var a_pct = parseInt(json.a_pct);
+
+ if(am && am.gaugeMeter) {
+ am.gaugeMeter({
+ percent: a_pct,
+ text: a.toFixed(1)
+ });
+ }
+ }
+
for(var id in json.data) {
var str = json.data[id];
if(isNaN(str)) {
- $('#'+id).html(str);
+ $('.'+id).html(str);
} else {
var num = parseFloat(str);
- $('#'+id).html(num.toFixed(1));
+ $('.'+id).html(num.toFixed(1));
$('#'+id+'-row').show();
+ $('.'+id+'-row').show();
}
}
+
+ var temp = parseInt(json.temp);
+ if(temp == -127) {
+ $('.temp').html("N/A");
+ }
} else {
if(im && im.gaugeMeter) {
im.gaugeMeter({
@@ -330,26 +380,25 @@ var fetch = function() {
append: "W"
});
}
+
+ if(vm && vm.gaugeMeter) {
+ vm.gaugeMeter({
+ percent: 0,
+ text: "-"
+ });
+ }
+
+ if(am && am.gaugeMeter) {
+ am.gaugeMeter({
+ percent: 0,
+ text: "-"
+ });
+ }
}
setTimeout(fetch, interval);
}).fail(function() {
setTimeout(fetch, interval*4);
- if(im && im.gaugeMeter) {
- im.gaugeMeter({
- percent: 0,
- text: "-",
- append: "W"
- });
- }
-
- if(em && em.gaugeMeter) {
- em.gaugeMeter({
- percent: 0,
- text: "-",
- append: "W"
- });
- }
setStatus("mqtt", "secondary");
setStatus("wifi", "secondary");
setStatus("han", "secondary");
diff --git a/web/gaugemeter.js b/web/gaugemeter.js
index 0e9b54fb..6978a738 100644
--- a/web/gaugemeter.js
+++ b/web/gaugemeter.js
@@ -55,6 +55,18 @@
"LightGreen-DarkGreen" === option.theme && (e > 0 && (t = "#3afc00"), e > 10 && (t = "#39f900"), e > 20 && (t = "#38f600"), e > 30 && (t = "#38f100"), e > 40 && (t = "#37ec00"), e > 50 && (t = "#36e700"), e > 60 && (t = "#34e200"), e > 70 && (t = "#34df00"), e > 80 && (t = "#33db00"), e > 90 && (t = "#32d900")),
"DarkGold-LightGold" === option.theme && (e > 0 && (t = "#ffb800"), e > 10 && (t = "#ffba00"), e > 20 && (t = "#ffbd00"), e > 30 && (t = "#ffc200"), e > 40 && (t = "#ffc600"), e > 50 && (t = "#ffcb00"), e > 60 && (t = "#ffcf00"), e > 70 && (t = "#ffd400"), e > 80 && (t = "#ffd600"), e > 90 && (t = "#ffd900")),
"LightGold-DarkGold" === option.theme && (e > 0 && (t = "#ffd900"), e > 10 && (t = "#ffd600"), e > 20 && (t = "#ffd400"), e > 30 && (t = "#ffcf00"), e > 40 && (t = "#ffcb00"), e > 50 && (t = "#ffc600"), e > 60 && (t = "#ffc200"), e > 70 && (t = "#ffbd00"), e > 80 && (t = "#ffba00"), e > 90 && (t = "#ffb800")),
+ "Voltage" === option.theme && (
+ e > 0 && (t = "#d90000"),
+ e > 10 && (t = "#f35100"),
+ e > 20 && (t = "#ffb800"),
+ e > 30 && (t = "#a6d900"),
+ e > 40 && (t = "#32d900"),
+ e > 50 && (t = "#32d900"),
+ e > 60 && (t = "#a6d900"),
+ e > 70 && (t = "#ffb800"),
+ e > 80 && (t = "#f35100"),
+ e > 90 && (t = "#d90000")
+ ),
"White" === option.theme && (t = "#fff"),
"Black" === option.theme && (t = "#000"),
t;
diff --git a/web/head.html b/web/head.html
index 2b2fd101..86a54165 100644
--- a/web/head.html
+++ b/web/head.html
@@ -49,7 +49,7 @@
-