mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-03-14 22:28:50 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6aa02d54c8 | ||
|
|
9dbf9137c7 | ||
|
|
c0a9a01b41 | ||
|
|
7b203b196f | ||
|
|
50c06e2cfe | ||
|
|
e4d4753181 | ||
|
|
04daf551fb | ||
|
|
4b51f0f235 | ||
|
|
5e4ccca663 |
@@ -451,6 +451,9 @@ bool AmsConfiguration::getEntsoeConfig(EntsoeConfig& config) {
|
|||||||
EEPROM.begin(EEPROM_SIZE);
|
EEPROM.begin(EEPROM_SIZE);
|
||||||
EEPROM.get(CONFIG_ENTSOE_START, config);
|
EEPROM.get(CONFIG_ENTSOE_START, config);
|
||||||
EEPROM.end();
|
EEPROM.end();
|
||||||
|
if(strlen(config.token) != 0 && strlen(config.token) != 36) {
|
||||||
|
clearEntsoe(config);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ ADC_MODE(ADC_VCC);
|
|||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
#include <esp_task_wdt.h>
|
#include <esp_task_wdt.h>
|
||||||
#endif
|
#endif
|
||||||
#define WDT_TIMEOUT 10
|
#define WDT_TIMEOUT 30
|
||||||
|
|
||||||
#include "AmsToMqttBridge.h"
|
#include "AmsToMqttBridge.h"
|
||||||
#include "AmsStorage.h"
|
#include "AmsStorage.h"
|
||||||
@@ -137,12 +137,14 @@ void setup() {
|
|||||||
hw.ledBlink(LED_GREEN, 1);
|
hw.ledBlink(LED_GREEN, 1);
|
||||||
hw.ledBlink(LED_BLUE, 1);
|
hw.ledBlink(LED_BLUE, 1);
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
EntsoeConfig entsoe;
|
EntsoeConfig entsoe;
|
||||||
if(config.getEntsoeConfig(entsoe) && strlen(entsoe.token) > 0) {
|
if(config.getEntsoeConfig(entsoe) && strlen(entsoe.token) > 0) {
|
||||||
eapi = new EntsoeApi(&Debug);
|
eapi = new EntsoeApi(&Debug);
|
||||||
eapi->setup(entsoe);
|
eapi->setup(entsoe);
|
||||||
ws.setEntsoeApi(eapi);
|
ws.setEntsoeApi(eapi);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool shared = false;
|
bool shared = false;
|
||||||
config.getMeterConfig(meterConfig);
|
config.getMeterConfig(meterConfig);
|
||||||
@@ -436,6 +438,7 @@ void loop() {
|
|||||||
mqtt->disconnect();
|
mqtt->disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
if(eapi != NULL && ntpEnabled) {
|
if(eapi != NULL && ntpEnabled) {
|
||||||
if(eapi->loop() && mqtt != NULL && mqttHandler != NULL && mqtt->connected()) {
|
if(eapi->loop() && mqtt != NULL && mqttHandler != NULL && mqtt->connected()) {
|
||||||
mqttHandler->publishPrices(eapi);
|
mqttHandler->publishPrices(eapi);
|
||||||
@@ -453,10 +456,11 @@ void loop() {
|
|||||||
} else if(eapi != NULL) {
|
} else if(eapi != NULL) {
|
||||||
delete eapi;
|
delete eapi;
|
||||||
eapi = NULL;
|
eapi = NULL;
|
||||||
ws.setEntsoeApi(eapi);
|
ws.setEntsoeApi(NULL);
|
||||||
}
|
}
|
||||||
config.ackEntsoeChange();
|
config.ackEntsoeChange();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
ws.loop();
|
ws.loop();
|
||||||
}
|
}
|
||||||
if(mqtt != NULL) { // Run loop regardless, to let MQTT do its work.
|
if(mqtt != NULL) { // Run loop regardless, to let MQTT do its work.
|
||||||
|
|||||||
@@ -471,9 +471,8 @@ double IEC6205675::getNumber(CosemData* item) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CosemTypeLongSigned: {
|
case CosemTypeLongSigned: {
|
||||||
uint16_t u16 = ntohs(item->lu.data); // ntohs only works for uint16 ?
|
int16_t i16 = ntohs(item->ls.data);
|
||||||
int16_t i16 = u16; // Cast to int16 before use?
|
ret = i16;
|
||||||
ret = i16; // Who knows, got to try it all...
|
|
||||||
pos += 3;
|
pos += 3;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ size_t DnbCurrParser::write(const uint8_t *buffer, size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t DnbCurrParser::write(uint8_t byte) {
|
size_t DnbCurrParser::write(uint8_t byte) {
|
||||||
|
if(pos >= 64) pos = 0;
|
||||||
if(pos == 0) {
|
if(pos == 0) {
|
||||||
if(byte == '<') {
|
if(byte == '<') {
|
||||||
buf[pos++] = byte;
|
buf[pos++] = byte;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ char* EntsoeA44Parser::getMeasurementUnit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float EntsoeA44Parser::getPoint(uint8_t position) {
|
float EntsoeA44Parser::getPoint(uint8_t position) {
|
||||||
|
if(position >= 24) return ENTSOE_NO_VALUE;
|
||||||
return points[position];
|
return points[position];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +42,7 @@ size_t EntsoeA44Parser::write(const uint8_t *buffer, size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t EntsoeA44Parser::write(uint8_t byte) {
|
size_t EntsoeA44Parser::write(uint8_t byte) {
|
||||||
|
if(pos >= 64) pos = 0;
|
||||||
if(docPos == DOCPOS_CURRENCY) {
|
if(docPos == DOCPOS_CURRENCY) {
|
||||||
buf[pos++] = byte;
|
buf[pos++] = byte;
|
||||||
if(pos == 3) {
|
if(pos == 3) {
|
||||||
|
|||||||
@@ -13,11 +13,14 @@ EntsoeApi::EntsoeApi(RemoteDebug* Debug) {
|
|||||||
|
|
||||||
client.setInsecure();
|
client.setInsecure();
|
||||||
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
https.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||||
|
https.setTimeout(5000);
|
||||||
|
|
||||||
// Entso-E uses CET/CEST
|
// Entso-E uses CET/CEST
|
||||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||||
tz = new Timezone(CEST, CET);
|
tz = new Timezone(CEST, CET);
|
||||||
|
|
||||||
|
tomorrowFetchMillis = 36000000 + (random(1800) * 1000); // Random between 13:30 and 14:00
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntsoeApi::setup(EntsoeConfig& config) {
|
void EntsoeApi::setup(EntsoeConfig& config) {
|
||||||
@@ -61,8 +64,10 @@ float EntsoeApi::getValueForHour(time_t cur, uint8_t hour) {
|
|||||||
} else {
|
} else {
|
||||||
return ENTSOE_NO_VALUE;
|
return ENTSOE_NO_VALUE;
|
||||||
}
|
}
|
||||||
multiplier *= getCurrencyMultiplier(tomorrow->getCurrency(), config->currency);
|
float mult = getCurrencyMultiplier(tomorrow->getCurrency(), config->currency);
|
||||||
} else {
|
if(mult == 0) return ENTSOE_NO_VALUE;
|
||||||
|
multiplier *= mult;
|
||||||
|
} else if(pos >= 0) {
|
||||||
if(today == NULL)
|
if(today == NULL)
|
||||||
return ENTSOE_NO_VALUE;
|
return ENTSOE_NO_VALUE;
|
||||||
value = today->getPoint(pos);
|
value = today->getPoint(pos);
|
||||||
@@ -71,7 +76,9 @@ float EntsoeApi::getValueForHour(time_t cur, uint8_t hour) {
|
|||||||
} else {
|
} else {
|
||||||
return ENTSOE_NO_VALUE;
|
return ENTSOE_NO_VALUE;
|
||||||
}
|
}
|
||||||
multiplier *= getCurrencyMultiplier(today->getCurrency(), config->currency);
|
float mult = getCurrencyMultiplier(today->getCurrency(), config->currency);
|
||||||
|
if(mult == 0) return ENTSOE_NO_VALUE;
|
||||||
|
multiplier *= mult;
|
||||||
}
|
}
|
||||||
return value * multiplier;
|
return value * multiplier;
|
||||||
}
|
}
|
||||||
@@ -140,9 +147,11 @@ bool EntsoeApi::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prices for next day are published at 13:00 CE(S)T, but to avoid heavy server traffic at that time, we will
|
||||||
|
// fetch 1 hr after that (with some random delay) and retry every 15 minutes
|
||||||
if(tomorrow == NULL
|
if(tomorrow == NULL
|
||||||
&& midnightMillis - now < 39600000 // Fetch 11hrs before midnight (13:00 CE(S)T)
|
&& midnightMillis - now < tomorrowFetchMillis
|
||||||
&& (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 300000) // Retry every 5min
|
&& (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 900000)
|
||||||
) {
|
) {
|
||||||
lastTomorrowFetch = now;
|
lastTomorrowFetch = now;
|
||||||
time_t e1 = time(nullptr);
|
time_t e1 = time(nullptr);
|
||||||
@@ -261,10 +270,16 @@ float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to) {
|
|||||||
return 1.00;
|
return 1.00;
|
||||||
|
|
||||||
uint64_t now = millis64();
|
uint64_t now = millis64();
|
||||||
if(lastCurrencyFetch == 0 || now - lastCurrencyFetch > (SECS_PER_HOUR * 1000)) {
|
if(lastCurrencyFetch < midnightMillis) {
|
||||||
char url[256];
|
char url[256];
|
||||||
DnbCurrParser p;
|
DnbCurrParser p;
|
||||||
|
|
||||||
|
#if defined(ESP32)
|
||||||
|
esp_task_wdt_reset();
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
ESP.wdtFeed();
|
||||||
|
#endif
|
||||||
|
|
||||||
snprintf(url, sizeof(url), "https://data.norges-bank.no/api/data/EXR/M.%s.NOK.SP?lastNObservations=1", from);
|
snprintf(url, sizeof(url), "https://data.norges-bank.no/api/data/EXR/M.%s.NOK.SP?lastNObservations=1", from);
|
||||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Retrieving %s to NOK conversion\n", from);
|
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(EntsoeApi) Retrieving %s to NOK conversion\n", from);
|
||||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", url);
|
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) url: %s\n", url);
|
||||||
@@ -278,11 +293,15 @@ float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to) {
|
|||||||
if(retrieve(url, &p)) {
|
if(retrieve(url, &p)) {
|
||||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) got exchange rate %.4f\n", p.getValue());
|
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) got exchange rate %.4f\n", p.getValue());
|
||||||
currencyMultiplier /= p.getValue();
|
currencyMultiplier /= p.getValue();
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) Resulting currency multiplier: %.4f\n", currencyMultiplier);
|
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(EntsoeApi) Resulting currency multiplier: %.4f\n", currencyMultiplier);
|
||||||
lastCurrencyFetch = now;
|
lastCurrencyFetch = midnightMillis;
|
||||||
}
|
}
|
||||||
return currencyMultiplier;
|
return currencyMultiplier;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ private:
|
|||||||
WiFiClientSecure client;
|
WiFiClientSecure client;
|
||||||
HTTPClient https;
|
HTTPClient https;
|
||||||
|
|
||||||
|
uint32_t tomorrowFetchMillis = 36000000; // Number of ms before midnight. Default fetch 10hrs before midnight (14:00 CE(S)T)
|
||||||
uint64_t midnightMillis = 0;
|
uint64_t midnightMillis = 0;
|
||||||
uint64_t lastTodayFetch = 0;
|
uint64_t lastTodayFetch = 0;
|
||||||
uint64_t lastTomorrowFetch = 0;
|
uint64_t lastTomorrowFetch = 0;
|
||||||
|
|||||||
@@ -757,7 +757,7 @@ void AmsWebServer::dataJson() {
|
|||||||
if(eapi != NULL && strlen(eapi->getToken()) > 0)
|
if(eapi != NULL && strlen(eapi->getToken()) > 0)
|
||||||
price = eapi->getValueForHour(0);
|
price = eapi->getValueForHour(0);
|
||||||
|
|
||||||
char json[384];
|
char json[512];
|
||||||
snprintf_P(json, sizeof(json), DATA_JSON,
|
snprintf_P(json, sizeof(json), DATA_JSON,
|
||||||
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
|
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
|
||||||
meterConfig->productionCapacity,
|
meterConfig->productionCapacity,
|
||||||
@@ -791,9 +791,9 @@ void AmsWebServer::dataJson() {
|
|||||||
mqttStatus,
|
mqttStatus,
|
||||||
mqtt == NULL ? 0 : (int) mqtt->lastError(),
|
mqtt == NULL ? 0 : (int) mqtt->lastError(),
|
||||||
price == ENTSOE_NO_VALUE ? "null" : String(price, 2).c_str(),
|
price == ENTSOE_NO_VALUE ? "null" : String(price, 2).c_str(),
|
||||||
time(nullptr),
|
|
||||||
meterState->getMeterType(),
|
meterState->getMeterType(),
|
||||||
meterConfig->distributionSystem
|
meterConfig->distributionSystem,
|
||||||
|
(uint32_t) time(nullptr)
|
||||||
);
|
);
|
||||||
|
|
||||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
@@ -1040,7 +1040,6 @@ void AmsWebServer::handleSetup() {
|
|||||||
break;
|
break;
|
||||||
case 200: // ESP32
|
case 200: // ESP32
|
||||||
gpioConfig->hanPin = 16;
|
gpioConfig->hanPin = 16;
|
||||||
gpioConfig->apPin = 0;
|
|
||||||
gpioConfig->ledPin = 2;
|
gpioConfig->ledPin = 2;
|
||||||
gpioConfig->ledInverted = false;
|
gpioConfig->ledInverted = false;
|
||||||
break;
|
break;
|
||||||
@@ -1279,7 +1278,6 @@ void AmsWebServer::handleSave() {
|
|||||||
strcpy(entsoe.currency, server.arg("ecu").c_str());
|
strcpy(entsoe.currency, server.arg("ecu").c_str());
|
||||||
entsoe.multiplier = server.arg("em").toFloat() * 1000;
|
entsoe.multiplier = server.arg("em").toFloat() * 1000;
|
||||||
config->setEntsoeConfig(entsoe);
|
config->setEntsoeConfig(entsoe);
|
||||||
eapi->setup(entsoe);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
printI("Saving configuration now...");
|
printI("Saving configuration now...");
|
||||||
|
|||||||
@@ -717,7 +717,7 @@ var fetch = function() {
|
|||||||
if(ap && json.mf) {
|
if(ap && json.mf) {
|
||||||
switch(ds) {
|
switch(ds) {
|
||||||
case 1:
|
case 1:
|
||||||
ao.title = 'Current between';
|
ao.title = 'Line current';
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
ao.title = 'Phase current';
|
ao.title = 'Phase current';
|
||||||
@@ -730,19 +730,19 @@ var fetch = function() {
|
|||||||
var i1 = parseFloat(json.i1);
|
var i1 = parseFloat(json.i1);
|
||||||
a = Math.max(a, i1);
|
a = Math.max(a, i1);
|
||||||
var pct = (parseFloat(json.i1)/parseInt(json.mf))*100;
|
var pct = (parseFloat(json.i1)/parseInt(json.mf))*100;
|
||||||
arr[r++] = [ds == 1 ? 'L1-L2' : 'L1', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i1 + "A"];
|
arr[r++] = ['L1', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i1 + "A"];
|
||||||
}
|
}
|
||||||
if(json.i2) {
|
if(json.i2) {
|
||||||
var i2 = parseFloat(json.i2);
|
var i2 = parseFloat(json.i2);
|
||||||
a = Math.max(a, i2);
|
a = Math.max(a, i2);
|
||||||
var pct = (parseFloat(json.i2)/parseInt(json.mf))*100;
|
var pct = (parseFloat(json.i2)/parseInt(json.mf))*100;
|
||||||
arr[r++] = [ds == 1 ? 'L1-L3' : 'L2', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i2 + "A"];
|
arr[r++] = ['L2', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i2 + "A"];
|
||||||
}
|
}
|
||||||
if(json.i3) {
|
if(json.i3) {
|
||||||
var i3 = parseFloat(json.i3);
|
var i3 = parseFloat(json.i3);
|
||||||
a = Math.max(a, i3);
|
a = Math.max(a, i3);
|
||||||
var pct = (parseFloat(json.i3)/parseInt(json.mf))*100;
|
var pct = (parseFloat(json.i3)/parseInt(json.mf))*100;
|
||||||
arr[r++] = [ds == 1 ? 'L2-L3' : 'L3', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i3 + "A"];
|
arr[r++] = ['L3', pct, "color: " + ampcol(pct) + ";opacity: 0.9;", i3 + "A"];
|
||||||
}
|
}
|
||||||
if(a > 0) {
|
if(a > 0) {
|
||||||
aa = google.visualization.arrayToDataTable(arr);
|
aa = google.visualization.arrayToDataTable(arr);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
"mm" : %d,
|
"mm" : %d,
|
||||||
"me" : %d,
|
"me" : %d,
|
||||||
"p" : %s,
|
"p" : %s,
|
||||||
"c" : %lu,
|
|
||||||
"mt" : %d,
|
"mt" : %d,
|
||||||
"ds" : %d
|
"ds" : %d,
|
||||||
|
"c" : %lu
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user