mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-02-07 08:58:09 +00:00
Added support for retrieving energy price from ENTSO-E API
This commit is contained in:
@@ -634,6 +634,40 @@ void AmsConfiguration::clearNtp() {
|
||||
}
|
||||
|
||||
|
||||
char* AmsConfiguration::getEntsoeApiToken() {
|
||||
return config.entsoeApiToken;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setEntsoeApiToken(const char* token) {
|
||||
strcpy(config.entsoeApiToken, token);
|
||||
}
|
||||
|
||||
char* AmsConfiguration::getEntsoeApiArea() {
|
||||
return config.entsoeApiArea;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setEntsoeApiArea(const char* area) {
|
||||
strcpy(config.entsoeApiArea, area);
|
||||
}
|
||||
|
||||
char* AmsConfiguration::getEntsoeApiCurrency() {
|
||||
return config.entsoeApiCurrency;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setEntsoeApiCurrency(const char* currency) {
|
||||
strcpy(config.entsoeApiCurrency, currency);
|
||||
}
|
||||
|
||||
double AmsConfiguration::getEntsoeApiMultiplier() {
|
||||
return config.entsoeApiMultiplier;
|
||||
}
|
||||
|
||||
void AmsConfiguration::setEntsoeApiMultiplier(double multiplier) {
|
||||
config.entsoeApiMultiplier = multiplier;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AmsConfiguration::clear() {
|
||||
clearMeter();
|
||||
clearWifi();
|
||||
@@ -662,6 +696,7 @@ bool AmsConfiguration::hasConfig() {
|
||||
case 81:
|
||||
case 82:
|
||||
case 83:
|
||||
case 84:
|
||||
return true;
|
||||
default:
|
||||
configVersion = 0;
|
||||
@@ -680,13 +715,13 @@ bool AmsConfiguration::load() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
int cs = EEPROM.read(address++);
|
||||
switch(cs) {
|
||||
case 81: // v1.2
|
||||
success = loadConfig81(address);
|
||||
break;
|
||||
case 82: // v1.3
|
||||
success = loadConfig82(address);
|
||||
break;
|
||||
case 83: // v1.4
|
||||
success = loadConfig83(address);
|
||||
break;
|
||||
case 84: // v1.5
|
||||
EEPROM.get(address, config);
|
||||
loadTempSensors();
|
||||
success = true;
|
||||
@@ -787,99 +822,73 @@ bool AmsConfiguration::loadConfig82(int address) {
|
||||
config.domoCL1IDX = config82.domoCL1IDX;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::loadConfig81(int address) {
|
||||
char* temp;
|
||||
bool AmsConfiguration::loadConfig83(int address) {
|
||||
ConfigObject83 config83;
|
||||
EEPROM.get(address, config83);
|
||||
config.boardType = config83.boardType;
|
||||
strcpy(config.wifiSsid, config83.wifiSsid);
|
||||
strcpy(config.wifiPassword, config83.wifiPassword);
|
||||
strcpy(config.wifiIp, config83.wifiIp);
|
||||
strcpy(config.wifiGw, config83.wifiGw);
|
||||
strcpy(config.wifiSubnet, config83.wifiSubnet);
|
||||
strcpy(config.wifiDns1, config83.wifiDns1);
|
||||
strcpy(config.wifiDns2, config83.wifiDns2);
|
||||
strcpy(config.wifiHostname, config83.wifiHostname);
|
||||
strcpy(config.mqttHost, config83.mqttHost);
|
||||
config.mqttPort = config83.mqttPort;
|
||||
strcpy(config.mqttClientId, config83.mqttClientId);
|
||||
strcpy(config.mqttPublishTopic, config83.mqttPublishTopic);
|
||||
strcpy(config.mqttSubscribeTopic, config83.mqttSubscribeTopic);
|
||||
strcpy(config.mqttUser, config83.mqttUser);
|
||||
strcpy(config.mqttPassword, config83.mqttPassword);
|
||||
config.mqttPayloadFormat = config83.mqttPayloadFormat;
|
||||
config.mqttSsl = config83.mqttSsl;
|
||||
config.authSecurity = config83.authSecurity;
|
||||
strcpy(config.authUser, config83.authUser);
|
||||
strcpy(config.authPassword, config83.authPassword);
|
||||
|
||||
config.meterType = config83.meterType;
|
||||
config.distributionSystem = config83.distributionSystem;
|
||||
config.mainFuse = config83.mainFuse;
|
||||
config.productionCapacity = config83.productionCapacity;
|
||||
memcpy(config.meterEncryptionKey, config83.meterEncryptionKey, 16);
|
||||
memcpy(config.meterAuthenticationKey, config83.meterAuthenticationKey, 16);
|
||||
config.substituteMissing = config83.substituteMissing;
|
||||
config.sendUnknown = config83.sendUnknown;
|
||||
|
||||
address += readString(address, &temp);
|
||||
setWifiSsid(temp);
|
||||
address += readString(address, &temp);
|
||||
setWifiPassword(temp);
|
||||
config.debugTelnet = config83.debugTelnet;
|
||||
config.debugSerial = config83.debugSerial;
|
||||
config.debugLevel = config83.debugLevel;
|
||||
|
||||
bool staticIp = false;
|
||||
address += readBool(address, &staticIp);
|
||||
if(staticIp) {
|
||||
address += readString(address, &temp);
|
||||
setWifiIp(temp);
|
||||
address += readString(address, &temp);
|
||||
setWifiGw(temp);
|
||||
address += readString(address, &temp);
|
||||
setWifiSubnet(temp);
|
||||
address += readString(address, &temp);
|
||||
setWifiDns1(temp);
|
||||
address += readString(address, &temp);
|
||||
setWifiDns2(temp);
|
||||
}
|
||||
address += readString(address, &temp);
|
||||
setWifiHostname(temp);
|
||||
bool mqtt = false;
|
||||
address += readBool(address, &mqtt);
|
||||
if(mqtt) {
|
||||
address += readString(address, &temp);
|
||||
setMqttHost(temp);
|
||||
int port;
|
||||
address += readInt(address, &port);
|
||||
setMqttPort(port);
|
||||
address += readString(address, &temp);
|
||||
setMqttClientId(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttPublishTopic(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttSubscribeTopic(temp);
|
||||
config.hanPin = config83.hanPin;
|
||||
config.apPin = config83.apPin;
|
||||
config.ledPin = config83.ledPin;
|
||||
config.ledInverted = config83.ledInverted;
|
||||
config.ledPinRed = config83.ledPinRed;
|
||||
config.ledPinGreen = config83.ledPinGreen;
|
||||
config.ledPinBlue = config83.ledPinBlue;
|
||||
config.ledRgbInverted = config83.ledRgbInverted;
|
||||
config.tempSensorPin = config83.tempSensorPin;
|
||||
config.vccPin = config83.vccPin;
|
||||
config.vccOffset = config83.vccOffset;
|
||||
config.vccMultiplier = config83.vccMultiplier;
|
||||
config.vccBootLimit = config83.vccBootLimit;
|
||||
|
||||
bool secure = false;
|
||||
address += readBool(address, &secure);
|
||||
if (secure)
|
||||
{
|
||||
address += readString(address, &temp);
|
||||
setMqttUser(temp);
|
||||
address += readString(address, &temp);
|
||||
setMqttPassword(temp);
|
||||
} else {
|
||||
setMqttUser("");
|
||||
setMqttPassword("");
|
||||
}
|
||||
int payloadFormat;
|
||||
address += readInt(address, &payloadFormat);
|
||||
setMqttPayloadFormat(payloadFormat);
|
||||
} else {
|
||||
clearMqtt();
|
||||
}
|
||||
config.domoELIDX = config83.domoELIDX;
|
||||
config.domoVL1IDX = config83.domoVL1IDX;
|
||||
config.domoVL2IDX = config83.domoVL2IDX;
|
||||
config.domoVL3IDX = config83.domoVL3IDX;
|
||||
config.domoCL1IDX = config83.domoCL1IDX;
|
||||
|
||||
byte b;
|
||||
address += readByte(address, &b);
|
||||
setAuthSecurity(b);
|
||||
if (b > 0) {
|
||||
address += readString(address, &temp);
|
||||
setAuthUser(temp);
|
||||
address += readString(address, &temp);
|
||||
setAuthPassword(temp);
|
||||
} else {
|
||||
clearAuth();
|
||||
}
|
||||
config.mDnsEnable = config83.mDnsEnable;
|
||||
config.ntpEnable = config83.ntpEnable;
|
||||
config.ntpDhcp = config83.ntpDhcp;
|
||||
config.ntpOffset = config83.ntpOffset;
|
||||
config.ntpSummerOffset = config83.ntpSummerOffset;
|
||||
strcpy(config.ntpServer, config83.ntpServer);
|
||||
|
||||
int i;
|
||||
address += readInt(address, &i);
|
||||
setMeterType(i);
|
||||
address += readInt(address, &i);
|
||||
setDistributionSystem(i);
|
||||
address += readInt(address, &i);
|
||||
setMainFuse(i);
|
||||
address += readInt(address, &i);
|
||||
setProductionCapacity(i);
|
||||
|
||||
bool debugTelnet = false;
|
||||
address += readBool(address, &debugTelnet);
|
||||
setDebugTelnet(debugTelnet);
|
||||
bool debugSerial = false;
|
||||
address += readBool(address, &debugSerial);
|
||||
setDebugSerial(debugSerial);
|
||||
address += readInt(address, &i);
|
||||
setDebugLevel(i);
|
||||
|
||||
ackWifiChange();
|
||||
|
||||
return true;
|
||||
}
|
||||
config.tempAnalogSensorPin = config83.tempAnalogSensorPin;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
int address = EEPROM_CONFIG_ADDRESS;
|
||||
|
||||
@@ -39,6 +39,76 @@ struct ConfigObject {
|
||||
bool debugSerial;
|
||||
uint8_t debugLevel;
|
||||
|
||||
uint8_t hanPin;
|
||||
uint8_t apPin;
|
||||
uint8_t ledPin;
|
||||
bool ledInverted;
|
||||
uint8_t ledPinRed;
|
||||
uint8_t ledPinGreen;
|
||||
uint8_t ledPinBlue;
|
||||
bool ledRgbInverted;
|
||||
uint8_t tempSensorPin;
|
||||
uint8_t tempAnalogSensorPin;
|
||||
uint8_t vccPin;
|
||||
int16_t vccOffset;
|
||||
uint16_t vccMultiplier;
|
||||
uint8_t vccBootLimit;
|
||||
|
||||
uint16_t domoELIDX;
|
||||
uint16_t domoVL1IDX;
|
||||
uint16_t domoVL2IDX;
|
||||
uint16_t domoVL3IDX;
|
||||
uint16_t domoCL1IDX;
|
||||
|
||||
bool mDnsEnable;
|
||||
bool ntpEnable;
|
||||
bool ntpDhcp;
|
||||
int16_t ntpOffset;
|
||||
int16_t ntpSummerOffset;
|
||||
char ntpServer[64];
|
||||
|
||||
char entsoeApiToken[37];
|
||||
char entsoeApiArea[17];
|
||||
char entsoeApiCurrency[4];
|
||||
double entsoeApiMultiplier;
|
||||
};
|
||||
|
||||
struct ConfigObject83 {
|
||||
uint8_t boardType;
|
||||
char wifiSsid[32];
|
||||
char wifiPassword[64];
|
||||
char wifiIp[15];
|
||||
char wifiGw[15];
|
||||
char wifiSubnet[15];
|
||||
char wifiDns1[15];
|
||||
char wifiDns2[15];
|
||||
char wifiHostname[32];
|
||||
char mqttHost[128];
|
||||
uint16_t mqttPort;
|
||||
char mqttClientId[32];
|
||||
char mqttPublishTopic[64];
|
||||
char mqttSubscribeTopic[64];
|
||||
char mqttUser[64];
|
||||
char mqttPassword[64];
|
||||
uint8_t mqttPayloadFormat;
|
||||
bool mqttSsl;
|
||||
uint8_t authSecurity;
|
||||
char authUser[64];
|
||||
char authPassword[64];
|
||||
|
||||
uint8_t meterType;
|
||||
uint8_t distributionSystem;
|
||||
uint8_t mainFuse;
|
||||
uint8_t productionCapacity;
|
||||
uint8_t meterEncryptionKey[16];
|
||||
uint8_t meterAuthenticationKey[16];
|
||||
bool substituteMissing;
|
||||
bool sendUnknown;
|
||||
|
||||
bool debugTelnet;
|
||||
bool debugSerial;
|
||||
uint8_t debugLevel;
|
||||
|
||||
uint8_t hanPin;
|
||||
uint8_t apPin;
|
||||
uint8_t ledPin;
|
||||
@@ -91,6 +161,7 @@ struct ConfigObject82 {
|
||||
uint8_t authSecurity;
|
||||
char authUser[64];
|
||||
char authPassword[64];
|
||||
|
||||
uint8_t meterType;
|
||||
uint8_t distributionSystem;
|
||||
uint8_t mainFuse;
|
||||
@@ -289,6 +360,15 @@ public:
|
||||
bool isNtpChanged();
|
||||
void ackNtpChange();
|
||||
|
||||
char* getEntsoeApiToken();
|
||||
void setEntsoeApiToken(const char* token);
|
||||
char* getEntsoeApiArea();
|
||||
void setEntsoeApiArea(const char* area);
|
||||
char* getEntsoeApiCurrency();
|
||||
void setEntsoeApiCurrency(const char* currency);
|
||||
double getEntsoeApiMultiplier();
|
||||
void setEntsoeApiMultiplier(double multiplier);
|
||||
|
||||
uint8_t getTempSensorCount();
|
||||
TempSensorConfig* getTempSensorConfig(uint8_t i);
|
||||
void updateTempSensorConfig(uint8_t address[8], const char name[32], bool common);
|
||||
@@ -343,6 +423,7 @@ private:
|
||||
0xFF, // Blue
|
||||
true, // Inverted
|
||||
0xFF, // Temp sensor
|
||||
0xFF, // Analog temp sensor
|
||||
0xFF, // Vcc
|
||||
0, // Offset
|
||||
100, // Multiplier
|
||||
@@ -359,8 +440,11 @@ private:
|
||||
360, // Timezone (*10)
|
||||
360, // Summertime offset (*10)
|
||||
"pool.ntp.org", // NTP server
|
||||
0xFF, // Analog temp sensor
|
||||
// 894 bytes
|
||||
"", // Entsoe token
|
||||
"", // Entsoe area
|
||||
"", // Entsoe currency
|
||||
1.00, // Entsoe multiplier
|
||||
// 960 bytes
|
||||
};
|
||||
bool wifiChanged, mqttChanged, meterChanged = true, domoChanged, ntpChanged;
|
||||
|
||||
@@ -368,15 +452,15 @@ private:
|
||||
TempSensorConfig* tempSensors[32];
|
||||
|
||||
const int EEPROM_SIZE = 1024 * 3;
|
||||
const int EEPROM_CHECK_SUM = 83; // Used to check if config is stored. Change if structure changes
|
||||
const int EEPROM_CHECK_SUM = 84; // Used to check if config is stored. Change if structure changes
|
||||
const int EEPROM_CONFIG_ADDRESS = 0;
|
||||
const int EEPROM_TEMP_CONFIG_ADDRESS = 2048;
|
||||
|
||||
void loadTempSensors();
|
||||
void saveTempSensors();
|
||||
|
||||
bool loadConfig81(int address);
|
||||
bool loadConfig82(int address);
|
||||
bool loadConfig83(int address);
|
||||
|
||||
int readString(int pAddress, char* pString[]);
|
||||
int readInt(int pAddress, int *pValue);
|
||||
|
||||
@@ -29,6 +29,7 @@ ADC_MODE(ADC_VCC);
|
||||
#endif
|
||||
|
||||
#include "HwTools.h"
|
||||
#include "entsoe/EntsoeApi.h"
|
||||
|
||||
#include "web/AmsWebServer.h"
|
||||
#include "AmsConfiguration.h"
|
||||
@@ -56,7 +57,11 @@ AmsConfiguration config;
|
||||
|
||||
RemoteDebug Debug;
|
||||
|
||||
AmsWebServer ws(&Debug, &hw);
|
||||
EntsoeApi eapi(&Debug);
|
||||
|
||||
Timezone* tz;
|
||||
|
||||
AmsWebServer ws(&Debug, &hw, &eapi);
|
||||
|
||||
MQTTClient mqtt(512);
|
||||
|
||||
@@ -67,6 +72,10 @@ Stream *hanSerial;
|
||||
void setup() {
|
||||
if(config.hasConfig()) {
|
||||
config.load();
|
||||
|
||||
TimeChangeRule std = {"STD", Last, Sun, Oct, 3, config.getNtpOffset() / 60};
|
||||
TimeChangeRule dst = {"DST", Last, Sun, Mar, 2, (config.getNtpOffset() + config.getNtpSummerOffset()) / 60};
|
||||
tz = new Timezone(dst, std);
|
||||
}
|
||||
|
||||
if(!config.hasConfig() || config.getConfigVersion() < 81) {
|
||||
@@ -136,6 +145,10 @@ void setup() {
|
||||
hw.ledBlink(LED_YELLOW, 1);
|
||||
hw.ledBlink(LED_GREEN, 1);
|
||||
hw.ledBlink(LED_BLUE, 1);
|
||||
eapi.setToken(config.getEntsoeApiToken());
|
||||
eapi.setArea(config.getEntsoeApiArea());
|
||||
eapi.setCurrency(config.getEntsoeApiCurrency());
|
||||
eapi.setMultiplier(config.getEntsoeApiMultiplier());
|
||||
|
||||
if(config.getHanPin() == 3) {
|
||||
switch(config.getMeterType()) {
|
||||
@@ -445,7 +458,12 @@ void loop() {
|
||||
}
|
||||
delay(1);
|
||||
readHanPort();
|
||||
ws.loop();
|
||||
if(WiFi.status() == WL_CONNECTED) {
|
||||
ws.loop();
|
||||
if(eapi.loop()) {
|
||||
sendPricesToMqtt();
|
||||
}
|
||||
}
|
||||
delay(1); // Needed for auto modem sleep
|
||||
}
|
||||
|
||||
@@ -592,6 +610,129 @@ void mqttMessageReceived(String &topic, String &payload)
|
||||
// Ideas could be to query for values or to initiate OTA firmware update
|
||||
}
|
||||
|
||||
void sendPricesToMqtt() {
|
||||
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;
|
||||
|
||||
if(val1 == ENTSOE_NO_VALUE) break;
|
||||
|
||||
if(val1 < min) min = val1;
|
||||
if(val1 > max) max = val1;
|
||||
|
||||
if(i >= 24) continue; // Only estimate 1hr, 3hr and 6hr cheapest interval for next 24 hrs
|
||||
|
||||
if(min1hrIdx == -1 || min1hr > val1) {
|
||||
min1hr = val1;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
char ts1hr[21];
|
||||
if(min1hrIdx != -1) {
|
||||
tmElements_t tm;
|
||||
breakTime(time(nullptr) + (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);
|
||||
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);
|
||||
sprintf(ts6hr, "%04d-%02d-%02dT%02d:00:00Z", tm.Year+1970, tm.Month, tm.Day, tm.Hour);
|
||||
}
|
||||
|
||||
switch(config.getMqttPayloadFormat()) {
|
||||
case 0: // JSON
|
||||
{
|
||||
StaticJsonDocument<512> json;
|
||||
json["id"] = WiFi.macAddress();
|
||||
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);
|
||||
}
|
||||
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++) {
|
||||
double val = values[i];
|
||||
if(val == ENTSOE_NO_VALUE) {
|
||||
mqtt.publish(String(config.getMqttPublishTopic()) + "/price/" + String(i), "");
|
||||
break;
|
||||
} else {
|
||||
mqtt.publish(String(config.getMqttPublishTopic()) + "/price/" + String(i), String(val, 4));
|
||||
}
|
||||
}
|
||||
if(min != INT16_MAX) {
|
||||
mqtt.publish(String(config.getMqttPublishTopic()) + "/price/min", String(min, 4));
|
||||
}
|
||||
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));
|
||||
}
|
||||
if(min3hrIdx != -1) {
|
||||
mqtt.publish(String(config.getMqttPublishTopic()) + "/price/cheapest/3hr", String(ts3hr));
|
||||
}
|
||||
if(min6hrIdx != -1) {
|
||||
mqtt.publish(String(config.getMqttPublishTopic()) + "/price/cheapest/6hr", String(ts6hr));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int currentMeterType = 0;
|
||||
AmsData lastMqttData;
|
||||
void readHanPort() {
|
||||
@@ -769,6 +910,8 @@ 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
|
||||
if(lastMqttData.getMeterId() != data.getMeterId() || config.getMqttPayloadFormat() == 2) {
|
||||
|
||||
60
src/entsoe/DnbCurrParser.cpp
Normal file
60
src/entsoe/DnbCurrParser.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "DnbCurrParser.h"
|
||||
#include "HardwareSerial.h"
|
||||
|
||||
double DnbCurrParser::getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
int DnbCurrParser::available() {
|
||||
|
||||
}
|
||||
|
||||
int DnbCurrParser::read() {
|
||||
|
||||
}
|
||||
|
||||
int DnbCurrParser::peek() {
|
||||
|
||||
}
|
||||
|
||||
void DnbCurrParser::flush() {
|
||||
|
||||
}
|
||||
|
||||
size_t DnbCurrParser::write(const uint8_t *buffer, size_t size) {
|
||||
for(int i = 0; i < size; i++) {
|
||||
write(buffer[i]);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t DnbCurrParser::write(uint8_t byte) {
|
||||
if(pos == 0) {
|
||||
if(byte == '<') {
|
||||
buf[pos++] = byte;
|
||||
}
|
||||
} else if(byte == '>') {
|
||||
buf[pos++] = byte;
|
||||
if(strncmp(buf, "<Obs", 4) == 0) {
|
||||
for(int i = 0; i < pos; i++) {
|
||||
if(strncmp(buf+i, "OBS_VALUE=\"", 11) == 0) {
|
||||
pos = i + 11;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for(int i = 0; i < 16; i++) {
|
||||
uint8_t b = buf[pos+i];
|
||||
if(b == '"') {
|
||||
buf[pos+i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
value = String(buf+pos).toDouble();
|
||||
}
|
||||
pos = 0;
|
||||
} else {
|
||||
buf[pos++] = byte;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
25
src/entsoe/DnbCurrParser.h
Normal file
25
src/entsoe/DnbCurrParser.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#ifndef _DNBCURRPARSER_H
|
||||
#define _DNBCURRPARSER_H
|
||||
|
||||
#include "Stream.h"
|
||||
|
||||
class DnbCurrParser: public Stream {
|
||||
public:
|
||||
double getValue();
|
||||
|
||||
int available();
|
||||
int read();
|
||||
int peek();
|
||||
void flush();
|
||||
size_t write(const uint8_t *buffer, size_t size);
|
||||
size_t write(uint8_t);
|
||||
|
||||
private:
|
||||
double value = 1.0;
|
||||
|
||||
char buf[256];
|
||||
uint8_t pos = 0;
|
||||
uint8_t mode = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
102
src/entsoe/EntsoeA44Parser.cpp
Normal file
102
src/entsoe/EntsoeA44Parser.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "EntsoeA44Parser.h"
|
||||
#include "HardwareSerial.h"
|
||||
|
||||
EntsoeA44Parser::EntsoeA44Parser() {
|
||||
for(int i = 0; i < 24; i++) points[i] = 0.0;
|
||||
}
|
||||
|
||||
char* EntsoeA44Parser::getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
|
||||
char* EntsoeA44Parser::getMeasurementUnit() {
|
||||
return measurementUnit;
|
||||
}
|
||||
|
||||
double EntsoeA44Parser::getPoint(uint8_t position) {
|
||||
return points[position];
|
||||
}
|
||||
|
||||
int EntsoeA44Parser::available() {
|
||||
|
||||
}
|
||||
|
||||
int EntsoeA44Parser::read() {
|
||||
|
||||
}
|
||||
|
||||
int EntsoeA44Parser::peek() {
|
||||
|
||||
}
|
||||
|
||||
void EntsoeA44Parser::flush() {
|
||||
|
||||
}
|
||||
|
||||
size_t EntsoeA44Parser::write(const uint8_t *buffer, size_t size) {
|
||||
for(int i = 0; i < size; i++) {
|
||||
write(buffer[i]);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t EntsoeA44Parser::write(uint8_t byte) {
|
||||
if(docPos == DOCPOS_CURRENCY) {
|
||||
buf[pos++] = byte;
|
||||
if(pos == 3) {
|
||||
buf[pos++] = '\0';
|
||||
memcpy(currency, buf, pos);
|
||||
docPos = DOCPOS_SEEK;
|
||||
pos = 0;
|
||||
}
|
||||
} else if(docPos == DOCPOS_MEASUREMENTUNIT) {
|
||||
buf[pos++] = byte;
|
||||
if(pos == 3) {
|
||||
buf[pos++] = '\0';
|
||||
memcpy(measurementUnit, buf, pos);
|
||||
docPos = DOCPOS_SEEK;
|
||||
pos = 0;
|
||||
}
|
||||
} else if(docPos == DOCPOS_POSITION) {
|
||||
if(byte == '<') {
|
||||
buf[pos] = '\0';
|
||||
pointNum = String(buf).toInt() - 1;
|
||||
docPos = DOCPOS_SEEK;
|
||||
pos = 0;
|
||||
} else {
|
||||
buf[pos++] = byte;
|
||||
}
|
||||
} else if(docPos == DOCPOS_AMOUNT) {
|
||||
if(byte == '<') {
|
||||
buf[pos] = '\0';
|
||||
points[pointNum] = String(buf).toDouble();
|
||||
docPos = DOCPOS_SEEK;
|
||||
pos = 0;
|
||||
} else {
|
||||
buf[pos++] = byte;
|
||||
}
|
||||
} else {
|
||||
if(pos == 0) {
|
||||
if(byte == '<') {
|
||||
buf[pos++] = byte;
|
||||
}
|
||||
} else if(byte == '>') {
|
||||
buf[pos++] = byte;
|
||||
buf[pos] = '\0';
|
||||
if(strcmp(buf, "<currency_Unit.name>") == 0) {
|
||||
docPos = DOCPOS_CURRENCY;
|
||||
} else if(strcmp(buf, "<price_Measure_Unit.name>") == 0) {
|
||||
docPos = DOCPOS_MEASUREMENTUNIT;
|
||||
} else if(strcmp(buf, "<position>") == 0) {
|
||||
docPos = DOCPOS_POSITION;
|
||||
pointNum = 0xFF;
|
||||
} else if(strcmp(buf, "<price.amount>") == 0) {
|
||||
docPos = DOCPOS_AMOUNT;
|
||||
}
|
||||
pos = 0;
|
||||
} else {
|
||||
buf[pos++] = byte;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
38
src/entsoe/EntsoeA44Parser.h
Normal file
38
src/entsoe/EntsoeA44Parser.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef _ENTSOEA44PARSER_H
|
||||
#define _ENTSOEA44PARSER_H
|
||||
|
||||
#include "Stream.h"
|
||||
|
||||
#define DOCPOS_SEEK 0
|
||||
#define DOCPOS_CURRENCY 1
|
||||
#define DOCPOS_MEASUREMENTUNIT 2
|
||||
#define DOCPOS_POSITION 3
|
||||
#define DOCPOS_AMOUNT 4
|
||||
|
||||
class EntsoeA44Parser: public Stream {
|
||||
public:
|
||||
EntsoeA44Parser();
|
||||
|
||||
char* getCurrency();
|
||||
char* getMeasurementUnit();
|
||||
double getPoint(uint8_t position);
|
||||
|
||||
int available();
|
||||
int read();
|
||||
int peek();
|
||||
void flush();
|
||||
size_t write(const uint8_t *buffer, size_t size);
|
||||
size_t write(uint8_t);
|
||||
|
||||
private:
|
||||
char currency[4];
|
||||
char measurementUnit[4];
|
||||
double points[24];
|
||||
|
||||
char buf[256];
|
||||
uint8_t pos = 0;
|
||||
uint8_t docPos = 0;
|
||||
uint8_t pointNum = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
261
src/entsoe/EntsoeApi.cpp
Normal file
261
src/entsoe/EntsoeApi.cpp
Normal file
@@ -0,0 +1,261 @@
|
||||
#include "EntsoeApi.h"
|
||||
#include <EEPROM.h>
|
||||
#include "Uptime.h"
|
||||
#include "Time.h"
|
||||
#include "DnbCurrParser.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <HTTPClient.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
EntsoeApi::EntsoeApi(RemoteDebug* Debug) {
|
||||
debugger = Debug;
|
||||
|
||||
// Entso-E uses CET/CEST
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120};
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60};
|
||||
tz = new Timezone(CEST, CET);
|
||||
}
|
||||
|
||||
void EntsoeApi::setToken(const char* token) {
|
||||
strcpy(this->token, token);
|
||||
}
|
||||
|
||||
void EntsoeApi::setArea(const char* area) {
|
||||
strcpy(this->area, area);
|
||||
}
|
||||
|
||||
void EntsoeApi::setCurrency(const char* currency) {
|
||||
strcpy(this->currency, currency);
|
||||
}
|
||||
|
||||
void EntsoeApi::setMultiplier(double multiplier) {
|
||||
this->multiplier = multiplier;
|
||||
}
|
||||
|
||||
char* EntsoeApi::getCurrency() {
|
||||
return currency;
|
||||
}
|
||||
|
||||
double EntsoeApi::getValueForHour(int hour) {
|
||||
tmElements_t tm;
|
||||
time_t cur = time(nullptr);
|
||||
if(tz != NULL)
|
||||
cur = tz->toLocal(cur);
|
||||
breakTime(cur, tm);
|
||||
int pos = tm.Hour + hour;
|
||||
if(pos >= 48)
|
||||
return ENTSOE_NO_VALUE;
|
||||
|
||||
double value = ENTSOE_NO_VALUE;
|
||||
double multiplier = this->multiplier;
|
||||
if(pos > 23) {
|
||||
if(tomorrow == NULL)
|
||||
return ENTSOE_NO_VALUE;
|
||||
value = tomorrow->getPoint(pos-24);
|
||||
if(strcmp(tomorrow->getMeasurementUnit(), "MWH") == 0) {
|
||||
multiplier *= 0.001;
|
||||
} else {
|
||||
return ENTSOE_NO_VALUE;
|
||||
}
|
||||
multiplier *= getCurrencyMultiplier(tomorrow->getCurrency(), currency);
|
||||
} else {
|
||||
if(today == NULL)
|
||||
return ENTSOE_NO_VALUE;
|
||||
value = today->getPoint(pos);
|
||||
if(strcmp(today->getMeasurementUnit(), "MWH") == 0) {
|
||||
multiplier *= 0.001;
|
||||
} else {
|
||||
return ENTSOE_NO_VALUE;
|
||||
}
|
||||
multiplier *= getCurrencyMultiplier(today->getCurrency(), currency);
|
||||
}
|
||||
return value * multiplier;
|
||||
}
|
||||
|
||||
bool EntsoeApi::loop() {
|
||||
if(strlen(token) == 0)
|
||||
return false;
|
||||
bool ret = false;
|
||||
|
||||
uint64_t now = millis64();
|
||||
|
||||
if(midnightMillis == 0) {
|
||||
time_t epoch = time(nullptr);
|
||||
|
||||
tmElements_t tm;
|
||||
breakTime(epoch, tm);
|
||||
if(tm.Year > 50) { // Make sure we are in 2021 or later (years after 1970)
|
||||
uint64_t curDeviceMillis = millis64();
|
||||
uint32_t curDayMillis = (((((tm.Hour * 60) + tm.Minute) * 60) + tm.Second) * 1000);
|
||||
|
||||
printD("Setting midnight millis");
|
||||
midnightMillis = curDeviceMillis + (SECS_PER_DAY * 1000) - curDayMillis;
|
||||
}
|
||||
} else if(now > midnightMillis) {
|
||||
printD("Rotating price objects");
|
||||
delete today;
|
||||
today = tomorrow;
|
||||
tomorrow = NULL;
|
||||
midnightMillis = 0; // Force new midnight millis calculation
|
||||
} else {
|
||||
if(today == NULL) {
|
||||
time_t e1 = time(nullptr) - (SECS_PER_DAY * 1);
|
||||
time_t e2 = e1 + SECS_PER_DAY;
|
||||
tmElements_t d1, d2;
|
||||
breakTime(e1, d1);
|
||||
breakTime(e2, d2);
|
||||
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url), "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s",
|
||||
"https://transparency.entsoe.eu/api", token,
|
||||
d1.Year+1970, d1.Month, d1.Day, 23, 00,
|
||||
d2.Year+1970, d2.Month, d2.Day, 23, 00,
|
||||
area, area);
|
||||
|
||||
printD("Fetching prices for today");
|
||||
printD(url);
|
||||
EntsoeA44Parser* a44 = new EntsoeA44Parser();
|
||||
if(retrieve(url, a44)) {
|
||||
today = a44;
|
||||
ret = true;
|
||||
} else {
|
||||
delete a44;
|
||||
today = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if(tomorrow == NULL
|
||||
&& midnightMillis - now < 43200000
|
||||
&& (lastTomorrowFetch == 0 || now - lastTomorrowFetch > 3600000)
|
||||
) {
|
||||
time_t e1 = time(nullptr);
|
||||
time_t e2 = e1 + SECS_PER_DAY;
|
||||
tmElements_t d1, d2;
|
||||
breakTime(e1, d1);
|
||||
breakTime(e2, d2);
|
||||
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url), "%s?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s",
|
||||
"https://transparency.entsoe.eu/api", token,
|
||||
d1.Year+1970, d1.Month, d1.Day, 23, 00,
|
||||
d2.Year+1970, d2.Month, d2.Day, 23, 00,
|
||||
area, area);
|
||||
|
||||
printD("Fetching prices for tomorrow");
|
||||
printD(url);
|
||||
EntsoeA44Parser* a44 = new EntsoeA44Parser();
|
||||
if(retrieve(url, a44)) {
|
||||
tomorrow = a44;
|
||||
ret = true;
|
||||
} else {
|
||||
delete a44;
|
||||
tomorrow = NULL;
|
||||
}
|
||||
lastTomorrowFetch = now;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool EntsoeApi::retrieve(const char* url, Stream* doc) {
|
||||
WiFiClientSecure client;
|
||||
#if defined(ESP8266)
|
||||
client.setBufferSizes(512, 512);
|
||||
client.setInsecure();
|
||||
#endif
|
||||
HTTPClient https;
|
||||
#if defined(ESP8266)
|
||||
https.setFollowRedirects(true);
|
||||
#endif
|
||||
|
||||
if(https.begin(client, url)) {
|
||||
int status = https.GET();
|
||||
if(status == HTTP_CODE_OK) {
|
||||
https.writeToStream(doc);
|
||||
return true;
|
||||
} else {
|
||||
printE("Communication error: ");
|
||||
printE(https.errorToString(status));
|
||||
printI(url);
|
||||
printD(https.getString());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
double EntsoeApi::getCurrencyMultiplier(const char* from, const char* to) {
|
||||
if(strcmp(from, to) == 0)
|
||||
return 1.00;
|
||||
|
||||
uint64_t now = millis64();
|
||||
if(lastCurrencyFetch == 0 || now - lastCurrencyFetch > (SECS_PER_HOUR * 1000)) {
|
||||
WiFiClientSecure client;
|
||||
#if defined(ESP8266)
|
||||
client.setBufferSizes(512, 512);
|
||||
client.setInsecure();
|
||||
#endif
|
||||
HTTPClient https;
|
||||
#if defined(ESP8266)
|
||||
https.setFollowRedirects(true);
|
||||
#endif
|
||||
|
||||
char url[256];
|
||||
snprintf(url, sizeof(url), "https://data.norges-bank.no/api/data/EXR/M.%s.%s.SP?lastNObservations=1",
|
||||
from,
|
||||
to
|
||||
);
|
||||
|
||||
if(https.begin(client, url)) {
|
||||
int status = https.GET();
|
||||
if(status == HTTP_CODE_OK) {
|
||||
DnbCurrParser p;
|
||||
https.writeToStream(&p);
|
||||
currencyMultiplier = p.getValue();
|
||||
} else {
|
||||
printE("Communication error: ");
|
||||
printE(https.errorToString(status));
|
||||
printI(url);
|
||||
printD(https.getString());
|
||||
}
|
||||
lastCurrencyFetch = now;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return currencyMultiplier;
|
||||
}
|
||||
|
||||
void EntsoeApi::printD(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void EntsoeApi::printI(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void EntsoeApi::printW(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void EntsoeApi::printE(String fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(String("(EntsoeApi)" + fmt + "\n").c_str(), args);
|
||||
va_end(args);
|
||||
}
|
||||
50
src/entsoe/EntsoeApi.h
Normal file
50
src/entsoe/EntsoeApi.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef _ENTSOEAPI_H
|
||||
#define _ENTSOEAPI_H
|
||||
|
||||
#include "time.h"
|
||||
#include "Timezone.h"
|
||||
#include "RemoteDebug.h"
|
||||
#include "EntsoeA44Parser.h"
|
||||
|
||||
#define ENTSOE_NO_VALUE -127
|
||||
#define ENTSOE_DEFAULT_MULTIPLIER 1.00
|
||||
|
||||
class EntsoeApi {
|
||||
public:
|
||||
EntsoeApi(RemoteDebug* Debug);
|
||||
bool loop();
|
||||
|
||||
double getValueForHour(int hour);
|
||||
char* getCurrency();
|
||||
|
||||
void setToken(const char* token);
|
||||
void setArea(const char* area);
|
||||
void setCurrency(const char* currency);
|
||||
void setMultiplier(double multiplier);
|
||||
|
||||
private:
|
||||
RemoteDebug* debugger;
|
||||
char token[37]; // UUID + null terminator
|
||||
|
||||
uint64_t midnightMillis = 0;
|
||||
uint64_t lastTomorrowFetch = 0;
|
||||
uint64_t lastCurrencyFetch = 0;
|
||||
EntsoeA44Parser* today = NULL;
|
||||
EntsoeA44Parser* tomorrow = NULL;
|
||||
|
||||
Timezone* tz = NULL;
|
||||
|
||||
char area[32];
|
||||
char currency[4];
|
||||
double multiplier = ENTSOE_DEFAULT_MULTIPLIER;
|
||||
double currencyMultiplier = ENTSOE_DEFAULT_MULTIPLIER;
|
||||
|
||||
bool retrieve(const char* url, Stream* doc);
|
||||
double getCurrencyMultiplier(const char* from, const char* to);
|
||||
|
||||
void printD(String fmt, ...);
|
||||
void printI(String fmt, ...);
|
||||
void printW(String fmt, ...);
|
||||
void printE(String fmt, ...);
|
||||
};
|
||||
#endif
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "root/configmqtt_html.h"
|
||||
#include "root/configweb_html.h"
|
||||
#include "root/configdomoticz_html.h"
|
||||
#include "root/entsoe_html.h"
|
||||
#include "root/ntp_html.h"
|
||||
#include "root/gpio_html.h"
|
||||
#include "root/debugging_html.h"
|
||||
@@ -23,12 +24,14 @@
|
||||
#include "root/delete_html.h"
|
||||
#include "root/reset_html.h"
|
||||
#include "root/temperature_html.h"
|
||||
#include "root/price_html.h"
|
||||
|
||||
#include "base64.h"
|
||||
|
||||
AmsWebServer::AmsWebServer(RemoteDebug* Debug, HwTools* hw) {
|
||||
AmsWebServer::AmsWebServer(RemoteDebug* Debug, HwTools* hw, EntsoeApi* eapi) {
|
||||
this->debugger = Debug;
|
||||
this->hw = hw;
|
||||
this->eapi = eapi;
|
||||
}
|
||||
|
||||
void AmsWebServer::setup(AmsConfiguration* config, MQTTClient* mqtt) {
|
||||
@@ -41,11 +44,13 @@ void AmsWebServer::setup(AmsConfiguration* config, MQTTClient* mqtt) {
|
||||
server.on("/temperature", HTTP_GET, std::bind(&AmsWebServer::temperature, this));
|
||||
server.on("/temperature", HTTP_POST, std::bind(&AmsWebServer::temperaturePost, this));
|
||||
server.on("/temperature.json", HTTP_GET, std::bind(&AmsWebServer::temperatureJson, this));
|
||||
server.on("/price", HTTP_GET, std::bind(&AmsWebServer::price, this));
|
||||
server.on("/config-meter", HTTP_GET, std::bind(&AmsWebServer::configMeterHtml, this));
|
||||
server.on("/config-wifi", HTTP_GET, std::bind(&AmsWebServer::configWifiHtml, this));
|
||||
server.on("/config-mqtt", HTTP_GET, std::bind(&AmsWebServer::configMqttHtml, this));
|
||||
server.on("/config-web", HTTP_GET, std::bind(&AmsWebServer::configWebHtml, this));
|
||||
server.on("/config-domoticz",HTTP_GET, std::bind(&AmsWebServer::configDomoticzHtml, this));
|
||||
server.on("/config-entsoe",HTTP_GET, std::bind(&AmsWebServer::configEntsoeHtml, this));
|
||||
server.on("/boot.css", HTTP_GET, std::bind(&AmsWebServer::bootCss, this));
|
||||
server.on("/gaugemeter.js", HTTP_GET, std::bind(&AmsWebServer::gaugemeterJs, this));
|
||||
server.on("/github.svg", HTTP_GET, std::bind(&AmsWebServer::githubSvg, this));
|
||||
@@ -74,6 +79,10 @@ void AmsWebServer::setup(AmsConfiguration* config, MQTTClient* mqtt) {
|
||||
server.on("/reset", HTTP_POST, std::bind(&AmsWebServer::factoryResetPost, this));
|
||||
|
||||
server.onNotFound(std::bind(&AmsWebServer::notFound, this));
|
||||
|
||||
TimeChangeRule STD = {"STD", Last, Sun, Oct, 3, config->getNtpOffset() / 60};
|
||||
TimeChangeRule DST = {"DST", Last, Sun, Mar, 2, (config->getNtpOffset() + config->getNtpSummerOffset()) / 60};
|
||||
tz = new Timezone(DST, STD);
|
||||
|
||||
server.begin(); // Web server start
|
||||
}
|
||||
@@ -127,6 +136,9 @@ bool AmsWebServer::checkSecurity(byte level) {
|
||||
void AmsWebServer::temperature() {
|
||||
printD("Serving /temperature.html over http...");
|
||||
|
||||
if(!checkSecurity(2))
|
||||
return;
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
server.sendHeader("Expires", "-1");
|
||||
@@ -138,6 +150,9 @@ void AmsWebServer::temperature() {
|
||||
}
|
||||
|
||||
void AmsWebServer::temperaturePost() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
printD("Saving temperature sensors...");
|
||||
for(int i = 0; i < 32; i++) {
|
||||
if(!server.hasArg("sensor" + String(i, DEC))) break;
|
||||
@@ -200,6 +215,38 @@ void AmsWebServer::temperatureJson() {
|
||||
server.send(200, "application/json", jsonStr);
|
||||
}
|
||||
|
||||
void AmsWebServer::price() {
|
||||
printD("Serving /price.html over http...");
|
||||
|
||||
if(!checkSecurity(2))
|
||||
return;
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
server.sendHeader("Expires", "-1");
|
||||
|
||||
String html = String((const __FlashStringHelper*) PRICE_HTML);
|
||||
for(int i = 0; i < 24; i++) {
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(time(nullptr)) + (SECS_PER_HOUR * i), tm);
|
||||
char ts[5];
|
||||
sprintf(ts, "%02d:00", tm.Hour);
|
||||
html.replace("${time" + String(i) + "}", String(ts));
|
||||
|
||||
double price = eapi->getValueForHour(i);
|
||||
if(price == ENTSOE_NO_VALUE) {
|
||||
html.replace("${price" + String(i) + "}", "--");
|
||||
} else {
|
||||
html.replace("${price" + String(i) + "}", String(price, 4));
|
||||
}
|
||||
}
|
||||
|
||||
server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN);
|
||||
server.send_P(200, "text/html", HEAD_HTML);
|
||||
server.sendContent(html);
|
||||
server.sendContent_P(FOOT_HTML);
|
||||
}
|
||||
|
||||
void AmsWebServer::indexHtml() {
|
||||
printD("Serving /index.html over http...");
|
||||
|
||||
@@ -441,10 +488,6 @@ void AmsWebServer::configDomoticzHtml() {
|
||||
|
||||
String html = String((const __FlashStringHelper*) CONFIGDOMOTICZ_HTML);
|
||||
|
||||
if(WiFi.getMode() != WIFI_AP) {
|
||||
html.replace("boot.css", BOOTSTRAP_URL);
|
||||
}
|
||||
|
||||
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
server.sendHeader("Pragma", "no-cache");
|
||||
|
||||
@@ -465,6 +508,41 @@ void AmsWebServer::configDomoticzHtml() {
|
||||
server.sendContent_P(FOOT_HTML);
|
||||
}
|
||||
|
||||
void AmsWebServer::configEntsoeHtml() {
|
||||
printD("Serving /config-entsoe.html over http...");
|
||||
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
String html = String((const __FlashStringHelper*) ENTSOE_HTML);
|
||||
|
||||
html.replace("${config.entsoeApiToken}", config->getEntsoeApiToken());
|
||||
html.replace("${config.entsoeApiMultiplier}", String(config->getEntsoeApiMultiplier(), 3));
|
||||
|
||||
html.replace("${config.entsoeApiAreaNo1}", strcmp(config->getEntsoeApiArea(), "10YNO-1--------2") == 0 ? "selected" : "");
|
||||
html.replace("${config.entsoeApiAreaNo2}", strcmp(config->getEntsoeApiArea(), "10YNO-2--------T") == 0 ? "selected" : "");
|
||||
html.replace("${config.entsoeApiAreaNo3}", strcmp(config->getEntsoeApiArea(), "10YNO-3--------J") == 0 ? "selected" : "");
|
||||
html.replace("${config.entsoeApiAreaNo4}", strcmp(config->getEntsoeApiArea(), "10YNO-4--------9") == 0 ? "selected" : "");
|
||||
html.replace("${config.entsoeApiAreaNo5}", strcmp(config->getEntsoeApiArea(), "10Y1001A1001A48H") == 0 ? "selected" : "");
|
||||
|
||||
html.replace("${config.entsoeApiAreaSe1}", strcmp(config->getEntsoeApiArea(), "10Y1001A1001A44P") == 0 ? "selected" : "");
|
||||
html.replace("${config.entsoeApiAreaSe2}", strcmp(config->getEntsoeApiArea(), "10Y1001A1001A45N") == 0 ? "selected" : "");
|
||||
html.replace("${config.entsoeApiAreaSe3}", strcmp(config->getEntsoeApiArea(), "10Y1001A1001A46L") == 0 ? "selected" : "");
|
||||
html.replace("${config.entsoeApiAreaSe4}", strcmp(config->getEntsoeApiArea(), "10Y1001A1001A47J") == 0 ? "selected" : "");
|
||||
|
||||
html.replace("${config.entsoeApiAreaDk1}", strcmp(config->getEntsoeApiArea(), "10YDK-1--------W") == 0 ? "selected" : "");
|
||||
html.replace("${config.entsoeApiAreaDk2}", strcmp(config->getEntsoeApiArea(), "10YDK-2--------M") == 0 ? "selected" : "");
|
||||
|
||||
html.replace("${config.entsoeApiCurrencyNOK}", strcmp(config->getEntsoeApiArea(), "NOK") == 0 ? "selected" : "");
|
||||
html.replace("${config.entsoeApiCurrencySEK}", strcmp(config->getEntsoeApiArea(), "SEK") == 0 ? "selected" : "");
|
||||
html.replace("${config.entsoeApiCurrencyDKK}", strcmp(config->getEntsoeApiArea(), "DKK") == 0 ? "selected" : "");
|
||||
html.replace("${config.entsoeApiCurrencyEUR}", strcmp(config->getEntsoeApiArea(), "EUR") == 0 ? "selected" : "");
|
||||
|
||||
server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN);
|
||||
server.send_P(200, "text/html", HEAD_HTML);
|
||||
server.sendContent(html);
|
||||
server.sendContent_P(FOOT_HTML);
|
||||
}
|
||||
|
||||
void AmsWebServer::configWebHtml() {
|
||||
printD("Serving /config-web.html over http...");
|
||||
@@ -773,6 +851,10 @@ void AmsWebServer::handleSetup() {
|
||||
}
|
||||
|
||||
void AmsWebServer::handleSave() {
|
||||
printD("Handling save method from http");
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
String temp;
|
||||
|
||||
if(server.hasArg("meterConfig") && server.arg("meterConfig") == "true") {
|
||||
@@ -790,7 +872,6 @@ void AmsWebServer::handleSave() {
|
||||
fromHex(hexStr, encryptionKeyHex, 16);
|
||||
config->setMeterEncryptionKey(hexStr);
|
||||
}
|
||||
printD("Meter 8");
|
||||
|
||||
String authenticationKeyHex = server.arg("meterAuthenticationKey");
|
||||
if(!authenticationKeyHex.isEmpty()) {
|
||||
@@ -919,6 +1000,13 @@ void AmsWebServer::handleSave() {
|
||||
config->setNtpServer(server.arg("ntpServer").c_str());
|
||||
}
|
||||
|
||||
if(server.hasArg("entsoeConfig") && server.arg("entsoeConfig") == "true") {
|
||||
config->setEntsoeApiToken(server.arg("entsoeApiToken").c_str());
|
||||
config->setEntsoeApiArea(server.arg("entsoeApiArea").c_str());
|
||||
config->setEntsoeApiCurrency(server.arg("entsoeApiCurrency").c_str());
|
||||
config->setEntsoeApiMultiplier(server.arg("entsoeApiMultiplier").toDouble());
|
||||
}
|
||||
|
||||
printI("Saving configuration now...");
|
||||
|
||||
if (debugger->isActive(RemoteDebug::DEBUG)) config->print(debugger);
|
||||
@@ -939,6 +1027,11 @@ void AmsWebServer::handleSave() {
|
||||
hw->setVccPin(config->getVccPin());
|
||||
hw->setVccOffset(config->getVccOffset());
|
||||
hw->setVccMultiplier(config->getVccMultiplier());
|
||||
|
||||
eapi->setToken(config->getEntsoeApiToken());
|
||||
eapi->setArea(config->getEntsoeApiArea());
|
||||
eapi->setCurrency(config->getEntsoeApiCurrency());
|
||||
eapi->setMultiplier(config->getEntsoeApiMultiplier());
|
||||
}
|
||||
} else {
|
||||
printE("Error saving configuration");
|
||||
@@ -1124,10 +1217,16 @@ void AmsWebServer::deleteFile(const char* path) {
|
||||
void AmsWebServer::firmwareHtml() {
|
||||
printD("Serving /firmware.html over http...");
|
||||
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
uploadHtml("CA file", "/firmware", "mqtt");
|
||||
}
|
||||
|
||||
void AmsWebServer::firmwareUpload() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
HTTPUpload& upload = server.upload();
|
||||
if(upload.status == UPLOAD_FILE_START) {
|
||||
String filename = upload.filename;
|
||||
@@ -1146,6 +1245,9 @@ void AmsWebServer::firmwareUpload() {
|
||||
const uint8_t githubFingerprint[] = {0x59, 0x74, 0x61, 0x88, 0x13, 0xCA, 0x12, 0x34, 0x15, 0x4D, 0x11, 0x0A, 0xC1, 0x7F, 0xE6, 0x67, 0x07, 0x69, 0x42, 0xF5};
|
||||
|
||||
void AmsWebServer::firmwareDownload() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
printD("Firmware download URL triggered");
|
||||
if(server.hasArg("version")) {
|
||||
String version = server.arg("version");
|
||||
@@ -1275,6 +1377,9 @@ void AmsWebServer::deleteHtml(const char* label, const char* action, const char*
|
||||
void AmsWebServer::mqttCa() {
|
||||
printD("Serving /mqtt-ca.html over http...");
|
||||
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
if(SPIFFS.begin()) {
|
||||
if(SPIFFS.exists(FILE_MQTT_CA)) {
|
||||
deleteHtml("CA file", "/mqtt-ca/delete", "mqtt");
|
||||
@@ -1289,6 +1394,9 @@ void AmsWebServer::mqttCa() {
|
||||
}
|
||||
|
||||
void AmsWebServer::mqttCaUpload() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
uploadFile(FILE_MQTT_CA);
|
||||
HTTPUpload& upload = server.upload();
|
||||
if(upload.status == UPLOAD_FILE_END) {
|
||||
@@ -1301,6 +1409,9 @@ void AmsWebServer::mqttCaUpload() {
|
||||
}
|
||||
|
||||
void AmsWebServer::mqttCaDelete() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
if(!uploading) { // Not an upload
|
||||
deleteFile(FILE_MQTT_CA);
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
@@ -1317,6 +1428,9 @@ void AmsWebServer::mqttCaDelete() {
|
||||
void AmsWebServer::mqttCert() {
|
||||
printD("Serving /mqtt-cert.html over http...");
|
||||
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
if(SPIFFS.begin()) {
|
||||
if(SPIFFS.exists(FILE_MQTT_CERT)) {
|
||||
deleteHtml("Certificate", "/mqtt-cert/delete", "mqtt");
|
||||
@@ -1331,6 +1445,9 @@ void AmsWebServer::mqttCert() {
|
||||
}
|
||||
|
||||
void AmsWebServer::mqttCertUpload() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
uploadFile(FILE_MQTT_CERT);
|
||||
HTTPUpload& upload = server.upload();
|
||||
if(upload.status == UPLOAD_FILE_END) {
|
||||
@@ -1343,6 +1460,9 @@ void AmsWebServer::mqttCertUpload() {
|
||||
}
|
||||
|
||||
void AmsWebServer::mqttCertDelete() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
if(!uploading) { // Not an upload
|
||||
deleteFile(FILE_MQTT_CERT);
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
@@ -1359,6 +1479,9 @@ void AmsWebServer::mqttCertDelete() {
|
||||
void AmsWebServer::mqttKey() {
|
||||
printD("Serving /mqtt-key.html over http...");
|
||||
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
if(SPIFFS.begin()) {
|
||||
if(SPIFFS.exists(FILE_MQTT_KEY)) {
|
||||
deleteHtml("Private key", "/mqtt-key/delete", "mqtt");
|
||||
@@ -1373,6 +1496,9 @@ void AmsWebServer::mqttKey() {
|
||||
}
|
||||
|
||||
void AmsWebServer::mqttKeyUpload() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
uploadFile(FILE_MQTT_KEY);
|
||||
HTTPUpload& upload = server.upload();
|
||||
if(upload.status == UPLOAD_FILE_END) {
|
||||
@@ -1385,6 +1511,9 @@ void AmsWebServer::mqttKeyUpload() {
|
||||
}
|
||||
|
||||
void AmsWebServer::mqttKeyDelete() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
if(!uploading) { // Not an upload
|
||||
deleteFile(FILE_MQTT_KEY);
|
||||
server.sendHeader("Location","/config-mqtt");
|
||||
@@ -1399,6 +1528,9 @@ void AmsWebServer::mqttKeyDelete() {
|
||||
}
|
||||
|
||||
void AmsWebServer::factoryResetHtml() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
server.sendHeader("Cache-Control", "public, max-age=3600");
|
||||
|
||||
server.setContentLength(RESET_HTML_LEN + HEAD_HTML_LEN + FOOT_HTML_LEN);
|
||||
@@ -1408,6 +1540,9 @@ void AmsWebServer::factoryResetHtml() {
|
||||
}
|
||||
|
||||
void AmsWebServer::factoryResetPost() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
printD("Performing factory reset");
|
||||
if(server.hasArg("perform") && server.arg("perform") == "true") {
|
||||
printD("Formatting SPIFFS");
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "AmsData.h"
|
||||
#include "Uptime.h"
|
||||
#include "RemoteDebug.h"
|
||||
#include "entsoe/EntsoeApi.h"
|
||||
|
||||
#if defined(ARDUINO) && ARDUINO >= 100
|
||||
#include "Arduino.h"
|
||||
@@ -33,7 +34,7 @@
|
||||
|
||||
class AmsWebServer {
|
||||
public:
|
||||
AmsWebServer(RemoteDebug* Debug, HwTools* hw);
|
||||
AmsWebServer(RemoteDebug* Debug, HwTools* hw, EntsoeApi* eapi);
|
||||
void setup(AmsConfiguration* config, MQTTClient* mqtt);
|
||||
void loop();
|
||||
|
||||
@@ -43,6 +44,8 @@ private:
|
||||
RemoteDebug* debugger;
|
||||
int maxPwr = 0;
|
||||
HwTools* hw;
|
||||
Timezone* tz;
|
||||
EntsoeApi* eapi;
|
||||
AmsConfiguration* config;
|
||||
AmsData data;
|
||||
MQTTClient* mqtt;
|
||||
@@ -63,11 +66,13 @@ private:
|
||||
void temperature();
|
||||
void temperaturePost();
|
||||
void temperatureJson();
|
||||
void price();
|
||||
void configMeterHtml();
|
||||
void configWifiHtml();
|
||||
void configMqttHtml();
|
||||
void configWebHtml();
|
||||
void configDomoticzHtml();
|
||||
void configEntsoeHtml();
|
||||
void configNtpHtml();
|
||||
void configGpioHtml();
|
||||
void configDebugHtml();
|
||||
|
||||
Reference in New Issue
Block a user