mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-01-13 07:29:23 +00:00
Initial changes for v2.3
This commit is contained in:
parent
8cada69aaf
commit
ae82914795
@ -1,31 +1,54 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSCONFIGURATION_h
|
||||
#define _AMSCONFIGURATION_h
|
||||
#include <EEPROM.h>
|
||||
#include "Arduino.h"
|
||||
|
||||
#define EEPROM_SIZE 1024*3
|
||||
#define EEPROM_CHECK_SUM 103 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CHECK_SUM 104 // Used to check if config is stored. Change if structure changes
|
||||
#define EEPROM_CLEARED_INDICATOR 0xFC
|
||||
#define EEPROM_CONFIG_ADDRESS 0
|
||||
#define EEPROM_TEMP_CONFIG_ADDRESS 2048
|
||||
|
||||
#define CONFIG_SYSTEM_START 8
|
||||
#define CONFIG_METER_START 32
|
||||
#define CONFIG_UPGRADE_INFO_START 216
|
||||
#define CONFIG_UI_START 248
|
||||
#define CONFIG_GPIO_START 266
|
||||
#define CONFIG_ENTSOE_START 290
|
||||
#define CONFIG_WIFI_START 360
|
||||
#define CONFIG_ENERGYACCOUNTING_START 576
|
||||
#define CONFIG_WEB_START 648
|
||||
#define CONFIG_DEBUG_START 824
|
||||
#define CONFIG_DOMOTICZ_START 856
|
||||
#define CONFIG_NTP_START 872
|
||||
#define CONFIG_MQTT_START 1004
|
||||
#define CONFIG_HA_START 1680
|
||||
#define CONFIG_UPGRADE_INFO_START 16
|
||||
#define CONFIG_NETWORK_START 40
|
||||
#define CONFIG_METER_START 296
|
||||
#define CONFIG_GPIO_START 368
|
||||
#define CONFIG_ENTSOE_START 400
|
||||
#define CONFIG_ENERGYACCOUNTING_START 464
|
||||
#define CONFIG_WEB_START 488
|
||||
#define CONFIG_DEBUG_START 624
|
||||
#define CONFIG_NTP_START 632
|
||||
#define CONFIG_MQTT_START 760
|
||||
#define CONFIG_DOMOTICZ_START 1528
|
||||
#define CONFIG_HA_START 1544
|
||||
#define CONFIG_UI_START 1712
|
||||
#define CONFIG_CLOUD_START 1728
|
||||
|
||||
#define CONFIG_METER_START_93 224
|
||||
#define CONFIG_METER_START_103 32
|
||||
#define CONFIG_UPGRADE_INFO_START_103 216
|
||||
#define CONFIG_UI_START_103 248
|
||||
#define CONFIG_GPIO_START_103 266
|
||||
#define CONFIG_ENTSOE_START_103 290
|
||||
#define CONFIG_WIFI_START_103 360
|
||||
#define CONFIG_ENERGYACCOUNTING_START_103 576
|
||||
#define CONFIG_WEB_START_103 648
|
||||
#define CONFIG_DEBUG_START_103 824
|
||||
#define CONFIG_DOMOTICZ_START_103 856
|
||||
#define CONFIG_NTP_START_103 872
|
||||
#define CONFIG_MQTT_START_103 1004
|
||||
#define CONFIG_HA_START_103 1680
|
||||
|
||||
#define LED_BEHAVIOUR_DEFAULT 0
|
||||
#define LED_BEHAVIOUR_BOOT 1
|
||||
#define LED_BEHAVIOUR_ERROR_ONLY 3
|
||||
#define LED_BEHAVIOUR_OFF 9
|
||||
|
||||
struct SystemConfig {
|
||||
uint8_t boardType;
|
||||
@ -33,9 +56,10 @@ struct SystemConfig {
|
||||
bool userConfigured;
|
||||
uint8_t dataCollectionConsent; // 0 = unknown, 1 = accepted, 2 = declined
|
||||
char country[3];
|
||||
}; // 7
|
||||
uint8_t energyspeedometer;
|
||||
}; // 8
|
||||
|
||||
struct WiFiConfig {
|
||||
struct NetworkConfig {
|
||||
char ssid[32];
|
||||
char psk[64];
|
||||
char ip[16];
|
||||
@ -49,7 +73,8 @@ struct WiFiConfig {
|
||||
uint8_t sleep;
|
||||
uint8_t use11b;
|
||||
bool autoreboot;
|
||||
}; // 213
|
||||
uint8_t mode;
|
||||
}; // 214
|
||||
|
||||
struct MqttConfig {
|
||||
char host[128];
|
||||
@ -85,41 +110,10 @@ struct MeterConfig {
|
||||
uint8_t source;
|
||||
uint8_t parser;
|
||||
uint8_t bufferSize;
|
||||
}; // 62
|
||||
|
||||
struct MeterConfig100 {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
uint8_t distributionSystem;
|
||||
uint8_t mainFuse;
|
||||
uint8_t productionCapacity;
|
||||
uint8_t encryptionKey[16];
|
||||
uint8_t authenticationKey[16];
|
||||
uint32_t wattageMultiplier;
|
||||
uint32_t voltageMultiplier;
|
||||
uint32_t amperageMultiplier;
|
||||
uint32_t accumulatedMultiplier;
|
||||
uint8_t source;
|
||||
uint8_t parser;
|
||||
}; // 59
|
||||
|
||||
struct MeterConfig95 {
|
||||
uint32_t baud;
|
||||
uint8_t parity;
|
||||
bool invert;
|
||||
uint8_t distributionSystem;
|
||||
uint8_t mainFuse;
|
||||
uint8_t productionCapacity;
|
||||
uint8_t encryptionKey[16];
|
||||
uint8_t authenticationKey[16];
|
||||
uint16_t wattageMultiplier;
|
||||
uint16_t voltageMultiplier;
|
||||
uint16_t amperageMultiplier;
|
||||
uint16_t accumulatedMultiplier;
|
||||
uint8_t source;
|
||||
uint8_t parser;
|
||||
}; // 50
|
||||
uint8_t rxPin;
|
||||
bool rxPinPullup;
|
||||
uint8_t txPin;
|
||||
}; // 65
|
||||
|
||||
struct DebugConfig {
|
||||
bool telnet;
|
||||
@ -128,6 +122,26 @@ struct DebugConfig {
|
||||
}; // 3
|
||||
|
||||
struct GpioConfig {
|
||||
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 vccResistorGnd;
|
||||
uint16_t vccResistorVcc;
|
||||
uint8_t ledDisablePin;
|
||||
uint8_t ledBehaviour;
|
||||
}; // 21
|
||||
|
||||
struct GpioConfig103 {
|
||||
uint8_t hanPin;
|
||||
uint8_t apPin;
|
||||
uint8_t ledPin;
|
||||
@ -145,7 +159,9 @@ struct GpioConfig {
|
||||
uint16_t vccResistorGnd;
|
||||
uint16_t vccResistorVcc;
|
||||
bool hanPinPullup;
|
||||
}; // 21
|
||||
uint8_t ledDisablePin;
|
||||
uint8_t ledBehaviour;
|
||||
}; // 23
|
||||
|
||||
struct DomoticzConfig {
|
||||
uint16_t elidx;
|
||||
@ -168,14 +184,6 @@ struct NtpConfig {
|
||||
char timezone[32];
|
||||
}; // 98
|
||||
|
||||
struct NtpConfig96 {
|
||||
bool enable;
|
||||
bool dhcp;
|
||||
int16_t offset;
|
||||
int16_t summerOffset;
|
||||
char server[64];
|
||||
}; // 70
|
||||
|
||||
struct EntsoeConfig {
|
||||
char token[37];
|
||||
char area[17];
|
||||
@ -207,7 +215,9 @@ struct UiConfig {
|
||||
uint8_t showDayPlot;
|
||||
uint8_t showMonthPlot;
|
||||
uint8_t showTemperaturePlot;
|
||||
}; // 11
|
||||
uint8_t showRealtimePlot;
|
||||
uint8_t darkMode;
|
||||
}; // 12
|
||||
|
||||
struct TempSensorConfig {
|
||||
uint8_t address[8];
|
||||
@ -222,6 +232,15 @@ struct UpgradeInformation {
|
||||
int16_t errorCode;
|
||||
}; // 20
|
||||
|
||||
struct CloudConfig {
|
||||
bool enabled;
|
||||
uint8_t interval;
|
||||
char hostname[64];
|
||||
uint16_t port;
|
||||
char clientId[17];
|
||||
char clientSecret[17];
|
||||
};
|
||||
|
||||
class AmsConfiguration {
|
||||
public:
|
||||
bool hasConfig();
|
||||
@ -231,13 +250,15 @@ public:
|
||||
|
||||
bool getSystemConfig(SystemConfig&);
|
||||
bool setSystemConfig(SystemConfig&);
|
||||
bool isSystemConfigChanged();
|
||||
void ackSystemConfigChanged();
|
||||
|
||||
bool getWiFiConfig(WiFiConfig&);
|
||||
bool setWiFiConfig(WiFiConfig&);
|
||||
void clearWifi(WiFiConfig&);
|
||||
void clearWifiIp(WiFiConfig&);
|
||||
bool isWifiChanged();
|
||||
void ackWifiChange();
|
||||
bool getNetworkConfig(NetworkConfig&);
|
||||
bool setNetworkConfig(NetworkConfig&);
|
||||
void clearNetworkConfig(NetworkConfig&);
|
||||
void clearNetworkConfigIp(NetworkConfig&);
|
||||
bool isNetworkConfigChanged();
|
||||
void ackNetworkConfigChange();
|
||||
|
||||
bool getMqttConfig(MqttConfig&);
|
||||
bool setMqttConfig(MqttConfig&);
|
||||
@ -299,18 +320,16 @@ public:
|
||||
bool setUiConfig(UiConfig&);
|
||||
void clearUiConfig(UiConfig&);
|
||||
|
||||
void loadTempSensors();
|
||||
void saveTempSensors();
|
||||
uint8_t getTempSensorCount();
|
||||
TempSensorConfig* getTempSensorConfig(uint8_t address[8]);
|
||||
void updateTempSensorConfig(uint8_t address[8], const char name[32], bool common);
|
||||
|
||||
bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]);
|
||||
|
||||
bool getUpgradeInformation(UpgradeInformation&);
|
||||
bool setUpgradeInformation(int16_t exitCode, int16_t errorCode, const char* currentVersion, const char* nextVersion);
|
||||
void clearUpgradeInformation(UpgradeInformation&);
|
||||
|
||||
bool getCloudConfig(CloudConfig&);
|
||||
bool setCloudConfig(CloudConfig&);
|
||||
void clearCloudConfig(CloudConfig&);
|
||||
bool isCloudChanged();
|
||||
void ackCloudConfig();
|
||||
|
||||
void clear();
|
||||
|
||||
protected:
|
||||
@ -318,18 +337,11 @@ protected:
|
||||
private:
|
||||
uint8_t configVersion = 0;
|
||||
|
||||
bool wifiChanged, mqttChanged, meterChanged = true, ntpChanged = true, entsoeChanged = false, energyAccountingChanged = true;
|
||||
bool sysChanged = false, networkChanged, mqttChanged, meterChanged = true, ntpChanged = true, entsoeChanged = false, energyAccountingChanged = true, cloudChanged = true;
|
||||
|
||||
uint8_t tempSensorCount = 0;
|
||||
TempSensorConfig** tempSensors = NULL;
|
||||
|
||||
bool relocateConfig93(); // 2.1.0
|
||||
bool relocateConfig94(); // 2.1.0
|
||||
bool relocateConfig95(); // 2.1.4
|
||||
bool relocateConfig96(); // 2.1.14
|
||||
bool relocateConfig100(); // 2.2-dev
|
||||
bool relocateConfig101(); // 2.2.0 through 2.2.8
|
||||
bool relocateConfig102(); // 2.2.9 through 2.2.11
|
||||
bool relocateConfig103(); // 2.2.12 onward
|
||||
|
||||
void saveToFs();
|
||||
bool loadFromFs(uint8_t version);
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSSTORAGE_H
|
||||
#define _AMSSTORAGE_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include <Timezone.h>
|
||||
|
||||
#define JULY1970 15634800
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HEXUTILS_H
|
||||
#define _HEXUTILS_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsConfiguration.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
@ -13,12 +19,22 @@ bool AmsConfiguration::getSystemConfig(SystemConfig& config) {
|
||||
config.vendorConfigured = false;
|
||||
config.userConfigured = false;
|
||||
config.dataCollectionConsent = 0;
|
||||
config.energyspeedometer = 0;
|
||||
strcpy(config.country, "");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setSystemConfig(SystemConfig& config) {
|
||||
SystemConfig existing;
|
||||
if(getSystemConfig(existing)) {
|
||||
sysChanged |= config.boardType != existing.boardType;
|
||||
sysChanged |= config.vendorConfigured != existing.vendorConfigured;
|
||||
sysChanged |= config.userConfigured != existing.userConfigured;
|
||||
sysChanged |= config.dataCollectionConsent != existing.dataCollectionConsent;
|
||||
sysChanged |= strcmp(config.country, existing.country) != 0;
|
||||
sysChanged |= config.energyspeedometer != existing.energyspeedometer;
|
||||
}
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
stripNonAscii((uint8_t*) config.country, 2);
|
||||
EEPROM.put(CONFIG_SYSTEM_START, config);
|
||||
@ -27,39 +43,48 @@ bool AmsConfiguration::setSystemConfig(SystemConfig& config) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getWiFiConfig(WiFiConfig& config) {
|
||||
bool AmsConfiguration::isSystemConfigChanged() {
|
||||
return sysChanged;
|
||||
}
|
||||
|
||||
void AmsConfiguration::ackSystemConfigChanged() {
|
||||
sysChanged = false;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getNetworkConfig(NetworkConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_WIFI_START, config);
|
||||
EEPROM.get(CONFIG_NETWORK_START, config);
|
||||
EEPROM.end();
|
||||
if(config.sleep > 2) config.sleep = 1;
|
||||
return true;
|
||||
} else {
|
||||
clearWifi(config);
|
||||
clearNetworkConfig(config);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setWiFiConfig(WiFiConfig& config) {
|
||||
WiFiConfig existing;
|
||||
bool AmsConfiguration::setNetworkConfig(NetworkConfig& config) {
|
||||
NetworkConfig existing;
|
||||
if(config.sleep > 2) config.sleep = 1;
|
||||
if(getWiFiConfig(existing)) {
|
||||
wifiChanged |= strcmp(config.ssid, existing.ssid) != 0;
|
||||
wifiChanged |= strcmp(config.psk, existing.psk) != 0;
|
||||
wifiChanged |= strcmp(config.ip, existing.ip) != 0;
|
||||
if(getNetworkConfig(existing)) {
|
||||
networkChanged |= strcmp(config.ssid, existing.ssid) != 0;
|
||||
networkChanged |= strcmp(config.psk, existing.psk) != 0;
|
||||
networkChanged |= strcmp(config.ip, existing.ip) != 0;
|
||||
if(strlen(config.ip) > 0) {
|
||||
wifiChanged |= strcmp(config.gateway, existing.gateway) != 0;
|
||||
wifiChanged |= strcmp(config.subnet, existing.subnet) != 0;
|
||||
wifiChanged |= strcmp(config.dns1, existing.dns1) != 0;
|
||||
wifiChanged |= strcmp(config.dns2, existing.dns2) != 0;
|
||||
networkChanged |= strcmp(config.gateway, existing.gateway) != 0;
|
||||
networkChanged |= strcmp(config.subnet, existing.subnet) != 0;
|
||||
networkChanged |= strcmp(config.dns1, existing.dns1) != 0;
|
||||
networkChanged |= strcmp(config.dns2, existing.dns2) != 0;
|
||||
}
|
||||
wifiChanged |= strcmp(config.hostname, existing.hostname) != 0;
|
||||
wifiChanged |= config.power != existing.power;
|
||||
wifiChanged |= config.sleep != existing.sleep;
|
||||
wifiChanged |= config.use11b != existing.use11b;
|
||||
wifiChanged |= config.autoreboot != existing.autoreboot;
|
||||
networkChanged |= strcmp(config.hostname, existing.hostname) != 0;
|
||||
networkChanged |= config.power != existing.power;
|
||||
networkChanged |= config.sleep != existing.sleep;
|
||||
networkChanged |= config.use11b != existing.use11b;
|
||||
networkChanged |= config.autoreboot != existing.autoreboot;
|
||||
networkChanged |= config.mode != existing.mode;
|
||||
} else {
|
||||
wifiChanged = true;
|
||||
networkChanged = true;
|
||||
}
|
||||
|
||||
stripNonAscii((uint8_t*) config.ssid, 32, true);
|
||||
@ -72,20 +97,20 @@ bool AmsConfiguration::setWiFiConfig(WiFiConfig& config) {
|
||||
stripNonAscii((uint8_t*) config.hostname, 32);
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(CONFIG_WIFI_START, config);
|
||||
EEPROM.put(CONFIG_NETWORK_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearWifi(WiFiConfig& config) {
|
||||
void AmsConfiguration::clearNetworkConfig(NetworkConfig& config) {
|
||||
strcpy(config.ssid, "");
|
||||
strcpy(config.psk, "");
|
||||
clearWifiIp(config);
|
||||
clearNetworkConfigIp(config);
|
||||
|
||||
uint16_t chipId;
|
||||
#if defined(ESP32)
|
||||
chipId = ESP.getEfuseMac();
|
||||
chipId = ( ESP.getEfuseMac() >> 32 ) % 0xFFFFFFFF;
|
||||
config.power = 195;
|
||||
#else
|
||||
chipId = ESP.getChipId();
|
||||
@ -97,7 +122,7 @@ void AmsConfiguration::clearWifi(WiFiConfig& config) {
|
||||
config.use11b = 1;
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearWifiIp(WiFiConfig& config) {
|
||||
void AmsConfiguration::clearNetworkConfigIp(NetworkConfig& config) {
|
||||
strcpy(config.ip, "");
|
||||
strcpy(config.gateway, "");
|
||||
strcpy(config.subnet, "");
|
||||
@ -105,12 +130,12 @@ void AmsConfiguration::clearWifiIp(WiFiConfig& config) {
|
||||
strcpy(config.dns2, "");
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isWifiChanged() {
|
||||
return wifiChanged;
|
||||
bool AmsConfiguration::isNetworkConfigChanged() {
|
||||
return networkChanged;
|
||||
}
|
||||
|
||||
void AmsConfiguration::ackWifiChange() {
|
||||
wifiChanged = false;
|
||||
void AmsConfiguration::ackNetworkConfigChange() {
|
||||
networkChanged = false;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getMqttConfig(MqttConfig& config) {
|
||||
@ -234,6 +259,9 @@ bool AmsConfiguration::setMeterConfig(MeterConfig& config) {
|
||||
meterChanged |= strcmp((char*) config.encryptionKey, (char*) existing.encryptionKey);
|
||||
meterChanged |= strcmp((char*) config.authenticationKey, (char*) existing.authenticationKey);
|
||||
meterChanged |= config.bufferSize != existing.bufferSize;
|
||||
meterChanged |= config.rxPin != existing.rxPin;
|
||||
meterChanged |= config.rxPinPullup != existing.rxPinPullup;
|
||||
meterChanged |= config.txPin != existing.txPin;
|
||||
} else {
|
||||
meterChanged = true;
|
||||
}
|
||||
@ -245,6 +273,9 @@ bool AmsConfiguration::setMeterConfig(MeterConfig& config) {
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearMeter(MeterConfig& config) {
|
||||
config.rxPin = 0xFF;
|
||||
config.txPin = 0xFF;
|
||||
config.rxPinPullup = true;
|
||||
config.baud = 0;
|
||||
config.parity = 0;
|
||||
config.invert = false;
|
||||
@ -386,7 +417,6 @@ bool AmsConfiguration::pinUsed(uint8_t pin, GpioConfig& config) {
|
||||
if(pin == 0xFF)
|
||||
return false;
|
||||
return
|
||||
pin == config.hanPin ||
|
||||
pin == config.apPin ||
|
||||
pin == config.ledPin ||
|
||||
pin == config.ledPinRed ||
|
||||
@ -394,7 +424,8 @@ bool AmsConfiguration::pinUsed(uint8_t pin, GpioConfig& config) {
|
||||
pin == config.ledPinBlue ||
|
||||
pin == config.tempSensorPin ||
|
||||
pin == config.tempAnalogSensorPin ||
|
||||
pin == config.vccPin
|
||||
pin == config.vccPin ||
|
||||
pin == config.ledDisablePin
|
||||
;
|
||||
}
|
||||
|
||||
@ -413,10 +444,6 @@ bool AmsConfiguration::getGpioConfig(GpioConfig& config) {
|
||||
|
||||
bool AmsConfiguration::setGpioConfig(GpioConfig& config) {
|
||||
GpioConfig existing;
|
||||
if(getGpioConfig(existing)) {
|
||||
meterChanged |= config.hanPin != existing.hanPin;
|
||||
meterChanged |= config.hanPinPullup != existing.hanPinPullup;
|
||||
}
|
||||
/* This currently does not work, as it checks its own pin
|
||||
if(pinUsed(config.hanPin, config)) {
|
||||
debugger->println(F("HAN pin already used"));
|
||||
@ -454,6 +481,10 @@ bool AmsConfiguration::setGpioConfig(GpioConfig& config) {
|
||||
debugger->println(F("Vcc pin already used"));
|
||||
return false;
|
||||
}
|
||||
if(pinUsed(config.ledDisablePin, config)) {
|
||||
debugger->println(F("ledDisablePin already used"));
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
if(config.apPin >= 0)
|
||||
pinMode(config.apPin, INPUT_PULLUP);
|
||||
@ -466,8 +497,6 @@ bool AmsConfiguration::setGpioConfig(GpioConfig& config) {
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearGpio(GpioConfig& config) {
|
||||
config.hanPin = 3;
|
||||
config.hanPinPullup = true;
|
||||
config.apPin = 0xFF;
|
||||
config.ledPin = 0xFF;
|
||||
config.ledInverted = true;
|
||||
@ -483,6 +512,8 @@ void AmsConfiguration::clearGpio(GpioConfig& config) {
|
||||
config.vccBootLimit = 0;
|
||||
config.vccResistorGnd = 0;
|
||||
config.vccResistorVcc = 0;
|
||||
config.ledDisablePin = 0xFF;
|
||||
config.ledBehaviour = LED_BEHAVIOUR_DEFAULT;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getNtpConfig(NtpConfig& config) {
|
||||
@ -502,7 +533,7 @@ bool AmsConfiguration::setNtpConfig(NtpConfig& config) {
|
||||
if(getNtpConfig(existing)) {
|
||||
if(config.enable != existing.enable) {
|
||||
if(!existing.enable) {
|
||||
wifiChanged = true;
|
||||
networkChanged = true;
|
||||
} else {
|
||||
ntpChanged = true;
|
||||
}
|
||||
@ -660,6 +691,7 @@ bool AmsConfiguration::getUiConfig(UiConfig& config) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_UI_START, config);
|
||||
if(config.showImport > 2) clearUiConfig(config); // Must be wrong
|
||||
if(config.showRealtimePlot > 2) config.showRealtimePlot = 1; // TODO: Move to new config version for v2.3
|
||||
EEPROM.end();
|
||||
return true;
|
||||
} else {
|
||||
@ -677,7 +709,7 @@ bool AmsConfiguration::setUiConfig(UiConfig& config) {
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearUiConfig(UiConfig& config) {
|
||||
// 1 = Always, 2 = If value present, 0 = Hidden
|
||||
// 1 = Enable, 2 = Auto, 0 = Disable
|
||||
config.showImport = 1;
|
||||
config.showExport = 2;
|
||||
config.showVoltage = 2;
|
||||
@ -689,6 +721,8 @@ void AmsConfiguration::clearUiConfig(UiConfig& config) {
|
||||
config.showDayPlot = 1;
|
||||
config.showMonthPlot = 1;
|
||||
config.showTemperaturePlot = 2;
|
||||
config.showRealtimePlot = 1;
|
||||
config.darkMode = 2;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setUpgradeInformation(int16_t exitCode, int16_t errorCode, const char* currentVersion, const char* nextVersion) {
|
||||
@ -730,6 +764,58 @@ void AmsConfiguration::clearUpgradeInformation(UpgradeInformation& upinfo) {
|
||||
memset(upinfo.toVersion, 0, 8);
|
||||
}
|
||||
|
||||
bool AmsConfiguration::getCloudConfig(CloudConfig& config) {
|
||||
if(hasConfig()) {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_CLOUD_START, config);
|
||||
EEPROM.end();
|
||||
return true;
|
||||
} else {
|
||||
clearCloudConfig(config);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::setCloudConfig(CloudConfig& config) {
|
||||
CloudConfig existing;
|
||||
if(getCloudConfig(existing)) {
|
||||
cloudChanged |= config.enabled != existing.enabled;
|
||||
cloudChanged |= config.interval!= existing.interval;
|
||||
cloudChanged |= config.port!= existing.port;
|
||||
cloudChanged |= strcmp(config.hostname, existing.hostname) != 0;
|
||||
cloudChanged |= strcmp(config.clientId, existing.clientId) != 0;
|
||||
cloudChanged |= strcmp(config.clientSecret, existing.clientSecret) != 0;
|
||||
} else {
|
||||
cloudChanged = true;
|
||||
}
|
||||
|
||||
stripNonAscii((uint8_t*) config.hostname, 64);
|
||||
stripNonAscii((uint8_t*) config.clientId, 17);
|
||||
stripNonAscii((uint8_t*) config.clientSecret, 17);
|
||||
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(CONFIG_CLOUD_START, config);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AmsConfiguration::clearCloudConfig(CloudConfig& config) {
|
||||
config.enabled = false;
|
||||
strcpy(config.hostname, "cloud.amsleser.no");
|
||||
config.port = 7443;
|
||||
config.interval = 10;
|
||||
strcpy(config.clientId, "");
|
||||
strcpy(config.clientSecret, "");
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isCloudChanged() {
|
||||
return cloudChanged;
|
||||
}
|
||||
|
||||
void AmsConfiguration::ackCloudConfig() {
|
||||
cloudChanged = false;
|
||||
}
|
||||
|
||||
void AmsConfiguration::clear() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
@ -738,6 +824,7 @@ void AmsConfiguration::clear() {
|
||||
EEPROM.get(CONFIG_SYSTEM_START, sys);
|
||||
sys.userConfigured = false;
|
||||
sys.dataCollectionConsent = 0;
|
||||
sys.energyspeedometer = 0;
|
||||
strcpy(sys.country, "");
|
||||
EEPROM.put(CONFIG_SYSTEM_START, sys);
|
||||
|
||||
@ -745,9 +832,9 @@ void AmsConfiguration::clear() {
|
||||
clearMeter(meter);
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
|
||||
WiFiConfig wifi;
|
||||
clearWifi(wifi);
|
||||
EEPROM.put(CONFIG_WIFI_START, wifi);
|
||||
NetworkConfig network;
|
||||
clearNetworkConfig(network);
|
||||
EEPROM.put(CONFIG_NETWORK_START, network);
|
||||
|
||||
MqttConfig mqtt;
|
||||
clearMqtt(mqtt);
|
||||
@ -789,6 +876,10 @@ void AmsConfiguration::clear() {
|
||||
clearUpgradeInformation(upinfo);
|
||||
EEPROM.put(CONFIG_UPGRADE_INFO_START, upinfo);
|
||||
|
||||
CloudConfig cloud;
|
||||
clearCloudConfig(cloud);
|
||||
EEPROM.put(CONFIG_CLOUD_START, cloud);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CLEARED_INDICATOR);
|
||||
EEPROM.commit();
|
||||
EEPROM.end();
|
||||
@ -808,46 +899,6 @@ bool AmsConfiguration::hasConfig() {
|
||||
}
|
||||
} else {
|
||||
switch(configVersion) {
|
||||
case 93:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig93()) {
|
||||
configVersion = 94;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 94:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig94()) {
|
||||
configVersion = 95;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 95:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig95()) {
|
||||
configVersion = 96;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 96:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig96()) {
|
||||
configVersion = 100;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 100:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig100()) {
|
||||
configVersion = 101;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 101:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig101()) {
|
||||
@ -864,6 +915,14 @@ bool AmsConfiguration::hasConfig() {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case 103:
|
||||
configVersion = -1; // Prevent loop
|
||||
if(relocateConfig103()) {
|
||||
configVersion = 104;
|
||||
} else {
|
||||
configVersion = 0;
|
||||
return false;
|
||||
}
|
||||
case EEPROM_CHECK_SUM:
|
||||
return true;
|
||||
default:
|
||||
@ -878,204 +937,18 @@ int AmsConfiguration::getConfigVersion() {
|
||||
return configVersion;
|
||||
}
|
||||
|
||||
void AmsConfiguration::loadTempSensors() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
TempSensorConfig* tempSensors[32];
|
||||
int address = EEPROM_TEMP_CONFIG_ADDRESS;
|
||||
int c = 0;
|
||||
int storedCount = EEPROM.read(address++);
|
||||
if(storedCount > 0 && storedCount <= 32) {
|
||||
for(int i = 0; i < storedCount; i++) {
|
||||
TempSensorConfig* tsc = new TempSensorConfig();
|
||||
EEPROM.get(address, *tsc);
|
||||
if(tsc->address[0] != 0xFF) {
|
||||
tempSensors[c++] = tsc;
|
||||
}
|
||||
address += sizeof(*tsc);
|
||||
}
|
||||
}
|
||||
this->tempSensors = new TempSensorConfig*[c];
|
||||
for(int i = 0; i < c; i++) {
|
||||
this->tempSensors[i] = tempSensors[i];
|
||||
}
|
||||
tempSensorCount = c;
|
||||
EEPROM.end();
|
||||
}
|
||||
|
||||
void AmsConfiguration::saveTempSensors() {
|
||||
int address = EEPROM_TEMP_CONFIG_ADDRESS;
|
||||
EEPROM.put(address++, tempSensorCount);
|
||||
for(int i = 0; i < tempSensorCount; i++) {
|
||||
TempSensorConfig* tsc = tempSensors[i];
|
||||
if(tsc->address[0] != 0xFF) {
|
||||
EEPROM.put(address, *tsc);
|
||||
address += sizeof(*tsc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig93() {
|
||||
MeterConfig95 meter;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_METER_START_93, meter);
|
||||
meter.wattageMultiplier = 0;
|
||||
meter.voltageMultiplier = 0;
|
||||
meter.amperageMultiplier = 0;
|
||||
meter.accumulatedMultiplier = 0;
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 94);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig94() {
|
||||
EnergyAccountingConfig eac;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_ENERGYACCOUNTING_START, eac);
|
||||
eac.hours = 1;
|
||||
EEPROM.put(CONFIG_ENERGYACCOUNTING_START, eac);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 95);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig95() {
|
||||
MeterConfig95 meter;
|
||||
MeterConfig95 meter95;
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.get(CONFIG_METER_START, meter);
|
||||
EEPROM.get(CONFIG_METER_START, meter95);
|
||||
meter.wattageMultiplier = meter95.wattageMultiplier;
|
||||
meter.voltageMultiplier = meter95.voltageMultiplier;
|
||||
meter.amperageMultiplier = meter95.amperageMultiplier;
|
||||
meter.accumulatedMultiplier = meter95.accumulatedMultiplier;
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 96);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig96() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
SystemConfig sys;
|
||||
EEPROM.get(CONFIG_SYSTEM_START, sys);
|
||||
|
||||
MeterConfig100 meter;
|
||||
EEPROM.get(CONFIG_METER_START, meter);
|
||||
meter.source = 1; // Serial
|
||||
meter.parser = 0; // Auto
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
|
||||
#if defined(ESP8266)
|
||||
GpioConfig gpio;
|
||||
EEPROM.get(CONFIG_GPIO_START, gpio);
|
||||
|
||||
switch(sys.boardType) {
|
||||
case 3: // Pow UART0 -- Now Pow-K UART0
|
||||
case 4: // Pow GPIO12 -- Now Pow-U UART0
|
||||
case 5: // Pow-K+ -- Now also Pow-K GPIO12
|
||||
case 7: // Pow-U+ -- Now also Pow-U GPIO12
|
||||
if(meter.baud == 2400 && meter.parity == 3) { // 3 == 8N1, assuming Pow-K
|
||||
if(gpio.hanPin == 3) { // UART0
|
||||
sys.boardType = 3;
|
||||
} else if(gpio.hanPin == 12) {
|
||||
sys.boardType = 5;
|
||||
}
|
||||
} else { // Assuming Pow-U
|
||||
if(gpio.hanPin == 3) { // UART0
|
||||
sys.boardType = 4;
|
||||
} else if(gpio.hanPin == 12) {
|
||||
sys.boardType = 7;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
sys.vendorConfigured = true;
|
||||
sys.userConfigured = true;
|
||||
sys.dataCollectionConsent = 0;
|
||||
strcpy(sys.country, "");
|
||||
EEPROM.put(CONFIG_SYSTEM_START, sys);
|
||||
|
||||
WiFiConfig wifi;
|
||||
EEPROM.get(CONFIG_WIFI_START, wifi);
|
||||
wifi.use11b = 1;
|
||||
wifi.autoreboot = true;
|
||||
EEPROM.put(CONFIG_WIFI_START, wifi);
|
||||
|
||||
NtpConfig ntp;
|
||||
NtpConfig96 ntp96;
|
||||
EEPROM.get(CONFIG_NTP_START, ntp96);
|
||||
ntp.enable = ntp96.enable;
|
||||
ntp.dhcp = ntp96.dhcp;
|
||||
if(ntp96.offset == 360 && ntp96.summerOffset == 360) {
|
||||
strcpy(ntp.timezone, "Europe/Oslo");
|
||||
} else {
|
||||
strcpy(ntp.timezone, "GMT");
|
||||
}
|
||||
strcpy(ntp.server, ntp96.server);
|
||||
EEPROM.put(CONFIG_NTP_START, ntp);
|
||||
|
||||
EntsoeConfig entsoe;
|
||||
EEPROM.get(CONFIG_ENTSOE_START, entsoe);
|
||||
entsoe.enabled = strlen(entsoe.token) > 0;
|
||||
EEPROM.put(CONFIG_ENTSOE_START, entsoe);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 100);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig100() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
MeterConfig100 meter100;
|
||||
EEPROM.get(CONFIG_METER_START, meter100);
|
||||
MeterConfig meter;
|
||||
meter.baud = meter100.baud;
|
||||
meter.parity = meter100.parity;
|
||||
meter.invert = meter100.invert;
|
||||
meter.distributionSystem = meter100.distributionSystem;
|
||||
meter.mainFuse = meter100.mainFuse;
|
||||
meter.productionCapacity = meter100.productionCapacity;
|
||||
memcpy(meter.encryptionKey, meter100.encryptionKey, 16);
|
||||
memcpy(meter.authenticationKey, meter100.authenticationKey, 16);
|
||||
meter.wattageMultiplier = meter100.wattageMultiplier;
|
||||
meter.voltageMultiplier = meter100.voltageMultiplier;
|
||||
meter.amperageMultiplier = meter100.amperageMultiplier;
|
||||
meter.accumulatedMultiplier = meter100.accumulatedMultiplier;
|
||||
meter.source = meter100.source;
|
||||
meter.parser = meter100.parser;
|
||||
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
|
||||
UiConfig ui;
|
||||
clearUiConfig(ui);
|
||||
EEPROM.put(CONFIG_UI_START, ui);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 101);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig101() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
EnergyAccountingConfig config;
|
||||
EnergyAccountingConfig101 config101;
|
||||
EEPROM.get(CONFIG_ENERGYACCOUNTING_START, config101);
|
||||
EEPROM.get(CONFIG_ENERGYACCOUNTING_START_103, config101);
|
||||
for(uint8_t i = 0; i < 9; i++) {
|
||||
config.thresholds[i] = config101.thresholds[i];
|
||||
}
|
||||
config.thresholds[9] = 0xFFFF;
|
||||
config.hours = config101.hours;
|
||||
EEPROM.put(CONFIG_ENERGYACCOUNTING_START, config);
|
||||
EEPROM.put(CONFIG_ENERGYACCOUNTING_START_103, config);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 102);
|
||||
bool ret = EEPROM.commit();
|
||||
@ -1086,19 +959,19 @@ bool AmsConfiguration::relocateConfig101() {
|
||||
bool AmsConfiguration::relocateConfig102() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
GpioConfig gpioConfig;
|
||||
EEPROM.get(CONFIG_GPIO_START, gpioConfig);
|
||||
GpioConfig103 gpioConfig;
|
||||
EEPROM.get(CONFIG_GPIO_START_103, gpioConfig);
|
||||
gpioConfig.hanPinPullup = true;
|
||||
EEPROM.put(CONFIG_GPIO_START, gpioConfig);
|
||||
EEPROM.put(CONFIG_GPIO_START_103, gpioConfig);
|
||||
|
||||
HomeAssistantConfig haconf;
|
||||
clearHomeAssistantConfig(haconf);
|
||||
EEPROM.put(CONFIG_HA_START, haconf);
|
||||
EEPROM.put(CONFIG_HA_START_103, haconf);
|
||||
|
||||
EntsoeConfig entsoe;
|
||||
EEPROM.get(CONFIG_ENTSOE_START, entsoe);
|
||||
EEPROM.get(CONFIG_ENTSOE_START_103, entsoe);
|
||||
entsoe.fixedPrice = 0;
|
||||
EEPROM.put(CONFIG_ENTSOE_START, entsoe);
|
||||
EEPROM.put(CONFIG_ENTSOE_START_103, entsoe);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 103);
|
||||
bool ret = EEPROM.commit();
|
||||
@ -1106,10 +979,89 @@ bool AmsConfiguration::relocateConfig102() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::relocateConfig103() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
|
||||
MeterConfig meter;
|
||||
UpgradeInformation upinfo;
|
||||
UiConfig ui;
|
||||
GpioConfig103 gpio103;
|
||||
EntsoeConfig entsoe;
|
||||
NetworkConfig wifi;
|
||||
EnergyAccountingConfig eac;
|
||||
WebConfig web;
|
||||
DebugConfig debug;
|
||||
DomoticzConfig domo;
|
||||
NtpConfig ntp;
|
||||
MqttConfig mqtt;
|
||||
HomeAssistantConfig ha;
|
||||
|
||||
EEPROM.get(CONFIG_METER_START_103, meter);
|
||||
EEPROM.get(CONFIG_UPGRADE_INFO_START_103, upinfo);
|
||||
EEPROM.get(CONFIG_UI_START_103, ui);
|
||||
EEPROM.get(CONFIG_GPIO_START_103, gpio103);
|
||||
EEPROM.get(CONFIG_ENTSOE_START_103, entsoe);
|
||||
EEPROM.get(CONFIG_WIFI_START_103, wifi);
|
||||
EEPROM.get(CONFIG_ENERGYACCOUNTING_START_103, eac);
|
||||
EEPROM.get(CONFIG_WEB_START_103, web);
|
||||
EEPROM.get(CONFIG_DEBUG_START_103, debug);
|
||||
EEPROM.get(CONFIG_DOMOTICZ_START_103, domo);
|
||||
EEPROM.get(CONFIG_NTP_START_103, ntp);
|
||||
EEPROM.get(CONFIG_MQTT_START_103, mqtt);
|
||||
EEPROM.get(CONFIG_HA_START_103, ha);
|
||||
|
||||
meter.rxPin = gpio103.hanPin;
|
||||
meter.txPin = 0xFF;
|
||||
meter.rxPinPullup = gpio103.hanPinPullup;
|
||||
wifi.mode = 1; // 1 == WiFi client
|
||||
|
||||
GpioConfig gpio = {
|
||||
gpio103.apPin,
|
||||
gpio103.ledPin,
|
||||
gpio103.ledInverted,
|
||||
gpio103.ledPinRed,
|
||||
gpio103.ledPinGreen,
|
||||
gpio103.ledPinBlue,
|
||||
gpio103.ledRgbInverted,
|
||||
gpio103.tempSensorPin,
|
||||
gpio103.tempAnalogSensorPin,
|
||||
gpio103.vccPin,
|
||||
gpio103.vccOffset,
|
||||
gpio103.vccMultiplier,
|
||||
gpio103.vccBootLimit,
|
||||
gpio103.vccResistorGnd,
|
||||
gpio103.vccResistorVcc,
|
||||
gpio103.ledDisablePin,
|
||||
gpio103.ledBehaviour
|
||||
};
|
||||
|
||||
EEPROM.put(CONFIG_UPGRADE_INFO_START, upinfo);
|
||||
EEPROM.put(CONFIG_NETWORK_START, wifi);
|
||||
EEPROM.put(CONFIG_METER_START, meter);
|
||||
EEPROM.put(CONFIG_GPIO_START, gpio);
|
||||
EEPROM.put(CONFIG_ENTSOE_START, entsoe);
|
||||
EEPROM.put(CONFIG_ENERGYACCOUNTING_START, eac);
|
||||
EEPROM.put(CONFIG_WEB_START, web);
|
||||
EEPROM.put(CONFIG_DEBUG_START, debug);
|
||||
EEPROM.put(CONFIG_NTP_START, ntp);
|
||||
EEPROM.put(CONFIG_MQTT_START, mqtt);
|
||||
EEPROM.put(CONFIG_DOMOTICZ_START, domo);
|
||||
EEPROM.put(CONFIG_HA_START, ha);
|
||||
EEPROM.put(CONFIG_UI_START, ui);
|
||||
|
||||
CloudConfig cloud;
|
||||
clearCloudConfig(cloud);
|
||||
EEPROM.put(CONFIG_CLOUD_START, cloud);
|
||||
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, 104);
|
||||
bool ret = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool AmsConfiguration::save() {
|
||||
EEPROM.begin(EEPROM_SIZE);
|
||||
EEPROM.put(EEPROM_CONFIG_ADDRESS, EEPROM_CHECK_SUM);
|
||||
saveTempSensors();
|
||||
bool success = EEPROM.commit();
|
||||
EEPROM.end();
|
||||
|
||||
@ -1117,60 +1069,6 @@ bool AmsConfiguration::save() {
|
||||
return success;
|
||||
}
|
||||
|
||||
uint8_t AmsConfiguration::getTempSensorCount() {
|
||||
return tempSensorCount;
|
||||
}
|
||||
|
||||
TempSensorConfig* AmsConfiguration::getTempSensorConfig(uint8_t address[8]) {
|
||||
if(tempSensors == NULL)
|
||||
return NULL;
|
||||
for(int x = 0; x < tempSensorCount; x++) {
|
||||
TempSensorConfig *conf = tempSensors[x];
|
||||
if(isSensorAddressEqual(conf->address, address)) {
|
||||
return conf;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void AmsConfiguration::updateTempSensorConfig(uint8_t address[8], const char name[32], bool common) {
|
||||
bool found = false;
|
||||
if(tempSensors != NULL) {
|
||||
for(int x = 0; x < tempSensorCount; x++) {
|
||||
TempSensorConfig *data = tempSensors[x];
|
||||
if(isSensorAddressEqual(data->address, address)) {
|
||||
found = true;
|
||||
strcpy(data->name, name);
|
||||
data->common = common;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
TempSensorConfig** tempSensors = new TempSensorConfig*[tempSensorCount+1];
|
||||
if(this->tempSensors != NULL) {
|
||||
for(int i = 0;i < tempSensorCount; i++) {
|
||||
tempSensors[i] = this->tempSensors[i];
|
||||
}
|
||||
}
|
||||
TempSensorConfig *data = new TempSensorConfig();
|
||||
memcpy(data->address, address, 8);
|
||||
strcpy(data->name, name);
|
||||
data->common = common;
|
||||
tempSensors[tempSensorCount++] = data;
|
||||
if(this->tempSensors != NULL) {
|
||||
delete this->tempSensors;
|
||||
}
|
||||
this->tempSensors = tempSensors;
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsConfiguration::isSensorAddressEqual(uint8_t a[8], uint8_t b[8]) {
|
||||
for(int i = 0; i < 8; i++) {
|
||||
if(a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AmsConfiguration::saveToFs() {
|
||||
|
||||
}
|
||||
@ -1186,21 +1084,32 @@ void AmsConfiguration::deleteFromFs(uint8_t version) {
|
||||
void AmsConfiguration::print(Print* debugger)
|
||||
{
|
||||
debugger->println(F("-----------------------------------------------"));
|
||||
WiFiConfig wifi;
|
||||
if(getWiFiConfig(wifi)) {
|
||||
debugger->println(F("--WiFi configuration--"));
|
||||
debugger->printf_P(PSTR("SSID: '%s'\r\n"), wifi.ssid);
|
||||
debugger->printf_P(PSTR("Psk: '%s'\r\n"), wifi.psk);
|
||||
if(strlen(wifi.ip) > 0) {
|
||||
debugger->printf_P(PSTR("IP: '%s'\r\n"), wifi.ip);
|
||||
debugger->printf_P(PSTR("Gateway: '%s'\r\n"), wifi.gateway);
|
||||
debugger->printf_P(PSTR("Subnet: '%s'\r\n"), wifi.subnet);
|
||||
debugger->printf_P(PSTR("DNS1: '%s'\r\n"), wifi.dns1);
|
||||
debugger->printf_P(PSTR("DNS2: '%s'\r\n"), wifi.dns2);
|
||||
NetworkConfig network;
|
||||
if(getNetworkConfig(network)) {
|
||||
debugger->println(F("--Network configuration--"));
|
||||
switch(network.mode) {
|
||||
case 1:
|
||||
debugger->printf_P(PSTR("Mode: 'WiFi client'\r\n"));
|
||||
break;
|
||||
case 2:
|
||||
debugger->printf_P(PSTR("Mode: 'WiFi AP'\r\n"));
|
||||
break;
|
||||
case 3:
|
||||
debugger->printf_P(PSTR("Mode: 'Ethernet'\r\n"));
|
||||
break;
|
||||
}
|
||||
debugger->printf_P(PSTR("Hostname: '%s'\r\n"), wifi.hostname);
|
||||
debugger->printf_P(PSTR("mDNS: '%s'\r\n"), wifi.mdns ? "Yes" : "No");
|
||||
debugger->printf_P(PSTR("802.11b: '%s'\r\n"), wifi.use11b ? "Yes" : "No");
|
||||
debugger->printf_P(PSTR("SSID: '%s'\r\n"), network.ssid);
|
||||
debugger->printf_P(PSTR("Psk: '%s'\r\n"), network.psk);
|
||||
if(strlen(network.ip) > 0) {
|
||||
debugger->printf_P(PSTR("IP: '%s'\r\n"), network.ip);
|
||||
debugger->printf_P(PSTR("Gateway: '%s'\r\n"), network.gateway);
|
||||
debugger->printf_P(PSTR("Subnet: '%s'\r\n"), network.subnet);
|
||||
debugger->printf_P(PSTR("DNS1: '%s'\r\n"), network.dns1);
|
||||
debugger->printf_P(PSTR("DNS2: '%s'\r\n"), network.dns2);
|
||||
}
|
||||
debugger->printf_P(PSTR("Hostname: '%s'\r\n"), network.hostname);
|
||||
debugger->printf_P(PSTR("mDNS: '%s'\r\n"), network.mdns ? "Yes" : "No");
|
||||
debugger->printf_P(PSTR("802.11b: '%s'\r\n"), network.use11b ? "Yes" : "No");
|
||||
debugger->println(F(""));
|
||||
delay(10);
|
||||
debugger->flush();
|
||||
@ -1246,6 +1155,8 @@ void AmsConfiguration::print(Print* debugger)
|
||||
MeterConfig meter;
|
||||
if(getMeterConfig(meter)) {
|
||||
debugger->println(F("--Meter configuration--"));
|
||||
debugger->printf_P(PSTR("HAN RX: %i\r\n"), meter.rxPin);
|
||||
debugger->printf_P(PSTR("HAN RX pullup %s\r\n"), meter.rxPinPullup ? "Yes" : "No");
|
||||
debugger->printf_P(PSTR("Baud: %i\r\n"), meter.baud);
|
||||
debugger->printf_P(PSTR("Parity: %i\r\n"), meter.parity);
|
||||
debugger->printf_P(PSTR("Invert serial: %s\r\n"), meter.invert ? "Yes" : "No");
|
||||
@ -1261,8 +1172,6 @@ void AmsConfiguration::print(Print* debugger)
|
||||
GpioConfig gpio;
|
||||
if(getGpioConfig(gpio)) {
|
||||
debugger->println(F("--GPIO configuration--"));
|
||||
debugger->printf_P(PSTR("HAN pin: %i\r\n"), gpio.hanPin);
|
||||
debugger->printf_P(PSTR("HAN pin pullup %s\r\n"), gpio.hanPinPullup ? "Yes" : "No");
|
||||
debugger->printf_P(PSTR("LED pin: %i\r\n"), gpio.ledPin);
|
||||
debugger->printf_P(PSTR("LED inverted: %s\r\n"), gpio.ledInverted ? "Yes" : "No");
|
||||
debugger->printf_P(PSTR("LED red pin: %i\r\n"), gpio.ledPinRed);
|
||||
@ -1273,6 +1182,8 @@ void AmsConfiguration::print(Print* debugger)
|
||||
debugger->printf_P(PSTR("Temperature pin: %i\r\n"), gpio.tempSensorPin);
|
||||
debugger->printf_P(PSTR("Temp analog pin: %i\r\n"), gpio.tempAnalogSensorPin);
|
||||
debugger->printf_P(PSTR("Vcc pin: %i\r\n"), gpio.vccPin);
|
||||
debugger->printf_P(PSTR("LED disable pin: %i\r\n"), gpio.ledDisablePin);
|
||||
debugger->printf_P(PSTR("LED behaviour: %i\r\n"), gpio.ledBehaviour);
|
||||
if(gpio.vccMultiplier > 0) {
|
||||
debugger->printf_P(PSTR("Vcc multiplier: %f\r\n"), gpio.vccMultiplier / 1000.0);
|
||||
}
|
||||
@ -1335,7 +1246,5 @@ void AmsConfiguration::print(Print* debugger)
|
||||
debugger->flush();
|
||||
}
|
||||
|
||||
debugger->printf_P(PSTR("Temp sensor count: %i\r\n"), this->getTempSensorCount());
|
||||
|
||||
debugger->println(F("-----------------------------------------------"));
|
||||
}
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "hexutils.h"
|
||||
|
||||
String toHex(uint8_t* in) {
|
||||
|
||||
@ -1,8 +1,15 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSDATA_H
|
||||
#define _AMSDATA_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <Timezone.h>
|
||||
#include "OBIScodes.h"
|
||||
|
||||
enum AmsType {
|
||||
AmsTypeAutodetect = 0x00,
|
||||
@ -21,6 +28,7 @@ public:
|
||||
AmsData();
|
||||
|
||||
void apply(AmsData& other);
|
||||
void apply(const OBIS_code_t obis, double value);
|
||||
|
||||
uint64_t getLastUpdateMillis();
|
||||
|
||||
@ -68,6 +76,7 @@ public:
|
||||
|
||||
bool isThreePhase();
|
||||
bool isTwoPhase();
|
||||
bool isCounterEstimated();
|
||||
|
||||
int8_t getLastError();
|
||||
void setLastError(int8_t);
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
#ifndef _AMSMQTTHANDLER_H
|
||||
#define _AMSMQTTHANDLER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <MQTT.h>
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
#include "EntsoeApi.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
class AmsMqttHandler {
|
||||
public:
|
||||
AmsMqttHandler(MQTTClient* mqtt, char* buf) {
|
||||
this->mqtt = mqtt;
|
||||
this->json = buf;
|
||||
};
|
||||
virtual ~AmsMqttHandler() {};
|
||||
|
||||
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
virtual bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
virtual bool publishPrices(EntsoeApi* eapi);
|
||||
virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*);
|
||||
|
||||
protected:
|
||||
MQTTClient* mqtt;
|
||||
char* json;
|
||||
uint16_t BufferSize = 2048;
|
||||
|
||||
bool loop();
|
||||
};
|
||||
|
||||
#endif
|
||||
79
lib/AmsData/include/OBIScodes.h
Normal file
79
lib/AmsData/include/OBIScodes.h
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _OBISCODES_H
|
||||
#define _OBISCODES_H
|
||||
|
||||
#include "lwip/def.h"
|
||||
|
||||
#define OBIS_MEDIUM_ABSTRACT 0
|
||||
#define OBIS_MEDIUM_ELECTRICITY 1
|
||||
|
||||
#define OBIS_CHAN_0 0
|
||||
#define OBIS_CHAN_1 1
|
||||
|
||||
#define OBIS_RANGE_NA 0xFF
|
||||
|
||||
struct OBIS_head_t {
|
||||
uint8_t medium;
|
||||
uint8_t channel;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct OBIS_code_t {
|
||||
uint8_t sensor;
|
||||
uint8_t gr;
|
||||
uint8_t tariff;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct OBIS_t {
|
||||
OBIS_head_t head;
|
||||
OBIS_code_t code;
|
||||
uint8_t range;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
const OBIS_code_t OBIS_NULL PROGMEM = { 0, 0, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_VERSION PROGMEM = { 0, 2, 129 };
|
||||
const OBIS_code_t OBIS_METER_MODEL PROGMEM = { 96, 1, 1 };
|
||||
const OBIS_code_t OBIS_METER_MODEL_2 PROGMEM = { 96, 1, 7 };
|
||||
const OBIS_code_t OBIS_METER_ID PROGMEM = { 96, 1, 0 };
|
||||
const OBIS_code_t OBIS_METER_ID_2 PROGMEM = { 0, 0, 5 };
|
||||
const OBIS_code_t OBIS_METER_TIMESTAMP PROGMEM = { 1, 0, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT PROGMEM = { 1, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_COUNT PROGMEM = { 1, 8, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT PROGMEM = { 2, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_COUNT PROGMEM = { 2, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT PROGMEM = { 3, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_IMPORT_COUNT PROGMEM = { 3, 8, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT PROGMEM = { 4, 7, 0 };
|
||||
const OBIS_code_t OBIS_REACTIVE_EXPORT_COUNT PROGMEM = { 4, 8, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_POWER_FACTOR PROGMEM = { 13, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_L1 PROGMEM = { 21, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_L1 PROGMEM = { 22, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_CURRENT_L1 PROGMEM = { 31, 7, 0 };
|
||||
const OBIS_code_t OBIS_VOLTAGE_L1 PROGMEM = { 32, 7, 0 };
|
||||
const OBIS_code_t OBIS_POWER_FACTOR_L1 PROGMEM = { 33, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_L2 PROGMEM = { 41, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_L2 PROGMEM = { 42, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_CURRENT_L2 PROGMEM = { 51, 7, 0 };
|
||||
const OBIS_code_t OBIS_VOLTAGE_L2 PROGMEM = { 52, 7, 0 };
|
||||
const OBIS_code_t OBIS_POWER_FACTOR_L2 PROGMEM = { 53, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_ACTIVE_IMPORT_L3 PROGMEM = { 61, 7, 0 };
|
||||
const OBIS_code_t OBIS_ACTIVE_EXPORT_L3 PROGMEM = { 62, 7, 0 };
|
||||
|
||||
const OBIS_code_t OBIS_CURRENT_L3 PROGMEM = { 71, 7, 0 };
|
||||
const OBIS_code_t OBIS_VOLTAGE_L3 PROGMEM = { 72, 7, 0 };
|
||||
const OBIS_code_t OBIS_POWER_FACTOR_L3 PROGMEM = { 73, 7, 0 };
|
||||
|
||||
#endif
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsData.h"
|
||||
|
||||
AmsData::AmsData() {}
|
||||
@ -82,6 +88,91 @@ void AmsData::apply(AmsData& other) {
|
||||
this->activeExportPower = other.getActiveExportPower();
|
||||
}
|
||||
|
||||
void AmsData::apply(OBIS_code_t obis, double value) {
|
||||
switch(obis.gr) {
|
||||
case 1:
|
||||
switch(obis.sensor) {
|
||||
case 7:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
activeImportPower = value;
|
||||
listType = max(listType, (uint8_t) 1);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
activeImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
switch(obis.sensor) {
|
||||
case 7:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
activeExportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
activeExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
switch(obis.sensor) {
|
||||
case 7:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
reactiveImportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
reactiveImportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
switch(obis.sensor) {
|
||||
case 7:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
reactiveExportPower = value;
|
||||
listType = max(listType, (uint8_t) 2);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 8:
|
||||
switch(obis.tariff) {
|
||||
case 0:
|
||||
reactiveExportCounter = value;
|
||||
listType = max(listType, (uint8_t) 3);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t AmsData::getLastUpdateMillis() {
|
||||
return this->lastUpdateMillis;
|
||||
}
|
||||
@ -218,6 +309,10 @@ bool AmsData::isTwoPhase() {
|
||||
return this->twoPhase;
|
||||
}
|
||||
|
||||
bool AmsData::isCounterEstimated() {
|
||||
return this->counterEstimated;
|
||||
}
|
||||
|
||||
int8_t AmsData::getLastError() {
|
||||
return lastErrorCount > 2 ? lastError : 0;
|
||||
}
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSDATASTORAGE_H
|
||||
#define _AMSDATASTORAGE_H
|
||||
#include "Arduino.h"
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsDataStorage.h"
|
||||
#include <lwip/apps/sntp.h>
|
||||
#include "LittleFS.h"
|
||||
@ -578,7 +584,6 @@ bool AmsDataStorage::isHappy() {
|
||||
|
||||
bool AmsDataStorage::isDayHappy() {
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Timezone is missing\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -586,11 +591,9 @@ bool AmsDataStorage::isDayHappy() {
|
||||
if(now < FirmwareVersion::BuildEpoch) return false;
|
||||
|
||||
if(now < day.lastMeterReadTime) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Day data timestamp %lu < %lu\n"), (int32_t) now, (int32_t) day.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
if(now-day.lastMeterReadTime > 3600) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Day data timestamp age %lu - %lu > 3600\n"), (int32_t) now, (int32_t) day.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -598,7 +601,6 @@ bool AmsDataStorage::isDayHappy() {
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(day.lastMeterReadTime), last);
|
||||
if(tm.Hour != last.Hour) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Day data hour of last timestamp %d > %d\n"), tm.Hour, last.Hour);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -607,7 +609,6 @@ bool AmsDataStorage::isDayHappy() {
|
||||
|
||||
bool AmsDataStorage::isMonthHappy() {
|
||||
if(tz == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Timezone is missing\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -616,19 +617,16 @@ bool AmsDataStorage::isMonthHappy() {
|
||||
tmElements_t tm, last;
|
||||
|
||||
if(now < month.lastMeterReadTime) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Month data timestamp %lu < %lu\n"), (int32_t) now, (int32_t) month.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
breakTime(tz->toLocal(now), tm);
|
||||
breakTime(tz->toLocal(month.lastMeterReadTime), last);
|
||||
if(tm.Day != last.Day) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(AmsDataStorage) Month data day of last timestamp %d > %d\n"), tm.Day, last.Day);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(now-month.lastMeterReadTime > 90100) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf("(AmsDataStorage) Month %lu - %lu > 3600\n", (int32_t) now, (int32_t) month.lastMeterReadTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _COSEM_H
|
||||
#define _COSEM_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATAPASERSER_H
|
||||
#define _DATAPASERSER_H
|
||||
|
||||
@ -10,6 +16,10 @@
|
||||
#define DATA_TAG_MBUS 0x68
|
||||
#define DATA_TAG_GBT 0xE0
|
||||
#define DATA_TAG_GCM 0xDB
|
||||
#define DATA_TAG_SNRM 0x81
|
||||
#define DATA_TAG_AARQ 0x60
|
||||
#define DATA_TAG_AARE 0x61
|
||||
#define DATA_TAG_RES 0xC4 // Get Response
|
||||
|
||||
#define DATA_PARSE_OK 0
|
||||
#define DATA_PARSE_FAIL -1
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DATAPASERSERS_H
|
||||
#define _DATAPASERSERS_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DLMSPARSER_H
|
||||
#define _DLMSPARSER_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DSMRPARSER_H
|
||||
#define _DSMRPARSER_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _GBTPARSER_H
|
||||
#define _GBTPARSER_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _GCMPARSER_H
|
||||
#define _GCMPARSER_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HDLCPARSER_H
|
||||
#define _HDLCPARSER_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _LLCPARSER_H
|
||||
#define _LLCPARSER_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _MBUSPARSER_H
|
||||
#define _MBUSPARSER_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _CRC_H
|
||||
#define _CRC_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _NTOHLL_H
|
||||
#define _NTOHLL_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Cosem.h"
|
||||
#include "lwip/def.h"
|
||||
#include <TimeLib.h>
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DlmsParser.h"
|
||||
#include "Cosem.h"
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DsmrParser.h"
|
||||
#include "crc.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GbtParser.h"
|
||||
#include "lwip/def.h"
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GcmParser.h"
|
||||
#include "lwip/def.h"
|
||||
#if defined(ESP8266)
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HdlcParser.h"
|
||||
#include "lwip/def.h"
|
||||
#include "crc.h"
|
||||
@ -49,7 +55,10 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
ptr += 3;
|
||||
|
||||
// Exclude all of header and 3 byte footer
|
||||
ctx.length -= ptr-d+3;
|
||||
ctx.length -= ptr-d;
|
||||
if(ctx.length > 1) {
|
||||
ctx.length -= 3;
|
||||
}
|
||||
return ptr-d;
|
||||
}
|
||||
return DATA_PARSE_UNKNOWN_DATA;
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "LlcParser.h"
|
||||
|
||||
int8_t LLCParser::parse(uint8_t *buf, DataParserContext &ctx) {
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "MbusParser.h"
|
||||
|
||||
int8_t MBUSParser::parse(uint8_t *d, DataParserContext &ctx) {
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "crc.h"
|
||||
|
||||
uint16_t crc16_x25(const uint8_t* p, int len)
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ntohll.h"
|
||||
|
||||
uint64_t ntohll(uint64_t x) {
|
||||
|
||||
66
lib/AmsMqttHandler/include/AmsMqttHandler.h
Normal file
66
lib/AmsMqttHandler/include/AmsMqttHandler.h
Normal file
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSMQTTHANDLER_H
|
||||
#define _AMSMQTTHANDLER_H
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <MQTT.h>
|
||||
#include "AmsData.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
#include "EntsoeApi.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#endif
|
||||
|
||||
class AmsMqttHandler {
|
||||
public:
|
||||
AmsMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) {
|
||||
this->mqttConfig = mqttConfig;
|
||||
this->debugger = debugger;
|
||||
this->json = buf;
|
||||
mqtt.dropOverflow(true);
|
||||
};
|
||||
|
||||
void setCaVerification(bool);
|
||||
|
||||
bool connect();
|
||||
void disconnect();
|
||||
lwmqtt_err_t lastError();
|
||||
bool connected();
|
||||
bool loop();
|
||||
|
||||
virtual uint8_t getFormat() { return 0; };
|
||||
|
||||
virtual bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) { return false; };
|
||||
virtual bool publishTemperatures(AmsConfiguration*, HwTools*) { return false; };
|
||||
virtual bool publishPrices(EntsoeApi* eapi) { return false; };
|
||||
virtual bool publishSystem(HwTools*, EntsoeApi*, EnergyAccounting*) { return false; };
|
||||
virtual bool publishRaw(String data) { return false; };
|
||||
|
||||
virtual ~AmsMqttHandler() {
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
RemoteDebug* debugger;
|
||||
MqttConfig mqttConfig;
|
||||
MQTTClient mqtt = MQTTClient(256);
|
||||
unsigned long lastMqttRetry = -10000;
|
||||
bool caVerification = true;
|
||||
WiFiClient *mqttClient = NULL;
|
||||
WiFiClientSecure *mqttSecureClient = NULL;
|
||||
char* json;
|
||||
uint16_t BufferSize = 2048;
|
||||
};
|
||||
|
||||
#endif
|
||||
171
lib/AmsMqttHandler/src/AmsMqttHandler.cpp
Normal file
171
lib/AmsMqttHandler/src/AmsMqttHandler.cpp
Normal file
@ -0,0 +1,171 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsMqttHandler.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "AmsStorage.h"
|
||||
#include "LittleFS.h"
|
||||
|
||||
void AmsMqttHandler::setCaVerification(bool caVerification) {
|
||||
this->caVerification = caVerification;
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::connect() {
|
||||
if(millis() - lastMqttRetry < 10000) {
|
||||
yield();
|
||||
return false;
|
||||
}
|
||||
lastMqttRetry = millis();
|
||||
|
||||
time_t epoch = time(nullptr);
|
||||
|
||||
if(mqttConfig.ssl) {
|
||||
if(epoch < FirmwareVersion::BuildEpoch) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("NTP not ready for MQTT SSL\n"));
|
||||
return false;
|
||||
}
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("MQTT SSL is configured (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
if(mqttSecureClient == NULL) {
|
||||
mqttSecureClient = new WiFiClientSecure();
|
||||
#if defined(ESP8266)
|
||||
mqttSecureClient->setBufferSizes(512, 512);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("ESP8266 firmware does not have enough memory...\n"));
|
||||
return false;
|
||||
#endif
|
||||
|
||||
if(caVerification && LittleFS.begin()) {
|
||||
File file;
|
||||
|
||||
if(LittleFS.exists(FILE_MQTT_CA)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT CA file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CA, (char*) "r");
|
||||
#if defined(ESP8266)
|
||||
BearSSL::X509List *serverTrustedCA = new BearSSL::X509List(file);
|
||||
mqttSecureClient->setTrustAnchors(serverTrustedCA);
|
||||
#elif defined(ESP32)
|
||||
if(mqttSecureClient->loadCACert(file, file.size())) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("CA accepted\n"));
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf_P(PSTR("CA was rejected\n"));
|
||||
delete mqttSecureClient;
|
||||
mqttSecureClient = NULL;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
file.close();
|
||||
|
||||
if(LittleFS.exists(FILE_MQTT_CERT) && LittleFS.exists(FILE_MQTT_KEY)) {
|
||||
#if defined(ESP8266)
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT certificate file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
|
||||
BearSSL::X509List *serverCertList = new BearSSL::X509List(file);
|
||||
file.close();
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT key file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
|
||||
BearSSL::PrivateKey *serverPrivKey = new BearSSL::PrivateKey(file);
|
||||
file.close();
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Setting client certificates (%dkb free heap)"), ESP.getFreeHeap());
|
||||
mqttSecureClient->setClientRSACert(serverCertList, serverPrivKey);
|
||||
#elif defined(ESP32)
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT certificate file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_CERT, (char*) "r");
|
||||
mqttSecureClient->loadCertificate(file, file.size());
|
||||
file.close();
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Found MQTT key file (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
file = LittleFS.open(FILE_MQTT_KEY, (char*) "r");
|
||||
mqttSecureClient->loadPrivateKey(file, file.size());
|
||||
file.close();
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("No CA, disabling validation\n"));
|
||||
mqttSecureClient->setInsecure();
|
||||
}
|
||||
LittleFS.end();
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("CA verification disabled\n"));
|
||||
mqttSecureClient->setInsecure();
|
||||
}
|
||||
mqttClient = mqttSecureClient;
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("MQTT SSL setup complete (%dkb free heap)\n"), ESP.getFreeHeap());
|
||||
}
|
||||
}
|
||||
|
||||
if(mqttClient == NULL) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("No SSL, using client without SSL support\n"));
|
||||
mqttClient = new WiFiClient();
|
||||
}
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Connecting to MQTT %s:%d\n"), mqttConfig.host, mqttConfig.port);
|
||||
|
||||
mqtt.begin(mqttConfig.host, mqttConfig.port, *mqttClient);
|
||||
|
||||
#if defined(ESP8266)
|
||||
if(mqttSecureClient) {
|
||||
time_t epoch = time(nullptr);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Setting NTP time %lu for secure MQTT connection\n"), epoch);
|
||||
mqttSecureClient->setX509Time(epoch);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Connect to a unsecure or secure MQTT server
|
||||
if ((strlen(mqttConfig.username) == 0 && mqtt.connect(mqttConfig.clientId)) ||
|
||||
(strlen(mqttConfig.username) > 0 && mqtt.connect(mqttConfig.clientId, mqttConfig.username, mqttConfig.password))) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Successfully connected to MQTT!\n"));
|
||||
return true;
|
||||
} else {
|
||||
if (debugger->isActive(RemoteDebug::ERROR)) {
|
||||
debugger->printf_P(PSTR("Failed to connect to MQTT: %d\n"), mqtt.lastError());
|
||||
#if defined(ESP8266)
|
||||
if(mqttSecureClient) {
|
||||
mqttSecureClient->getLastSSLError((char*) json, BufferSize);
|
||||
debugger->println((char*) json);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void AmsMqttHandler::disconnect() {
|
||||
mqtt.disconnect();
|
||||
mqtt.loop();
|
||||
delay(10);
|
||||
yield();
|
||||
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
mqttClient = NULL;
|
||||
if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lwmqtt_err_t AmsMqttHandler::lastError() {
|
||||
return mqtt.lastError();
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::connected() {
|
||||
return mqtt.connected();
|
||||
}
|
||||
|
||||
bool AmsMqttHandler::loop() {
|
||||
bool ret = mqtt.loop();
|
||||
delay(10);
|
||||
yield();
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _CLOUDCONNECTOR_H
|
||||
#define _CLOUDCONNECTOR_H
|
||||
|
||||
@ -11,18 +17,28 @@
|
||||
#include "mbedtls/error.h"
|
||||
#include "mbedtls/certs.h"
|
||||
#include "mbedtls/rsa.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "AmsData.h"
|
||||
#include "EnergyAccounting.h"
|
||||
#include "HwTools.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#elif defined(ESP32) // ARDUINO_ARCH_ESP32
|
||||
#include <HTTPClient.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <WiFiUdp.h>
|
||||
#else
|
||||
#warning "Unsupported board type"
|
||||
#endif
|
||||
|
||||
const unsigned char PUBLIC_KEY[] = \
|
||||
"-----BEGIN PUBLIC KEY-----\n"\
|
||||
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDoIo0CSuuX3tAdF7KPssdlzJNX\n"\
|
||||
"QryhgVV1rQIFPhHv3SxzyKtRrRM9s0CVfymcibhnEBXxxg3pxlGmwI/R6k7HHXJN\n"\
|
||||
"lBsXzzDtZ/GHDVnw+xRakTfRT0Zt+xdJSH5xJNWq4EwpvJfjA22L1Nz4dKSpgWMx\n"\
|
||||
"VRndAaXf0s7Q1XBz2wIDAQAB\n"\
|
||||
"-----END PUBLIC KEY-----\0";
|
||||
#define CC_BUF_SIZE 1024
|
||||
|
||||
|
||||
//const unsigned char PUBLIC_KEY[] = { 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xe8, 0x22, 0x8d, 0x02, 0x4a, 0xeb, 0x97, 0xde, 0xd0, 0x1d, 0x17, 0xb2, 0x8f, 0xb2, 0xc7, 0x65, 0xcc, 0x93, 0x57, 0x42, 0xbc, 0xa1, 0x81, 0x55, 0x75, 0xad, 0x02, 0x05, 0x3e, 0x11, 0xef, 0xdd, 0x2c, 0x73, 0xc8, 0xab, 0x51, 0xad, 0x13, 0x3d, 0xb3, 0x40, 0x95, 0x7f, 0x29, 0x9c, 0x89, 0xb8, 0x67, 0x10, 0x15, 0xf1, 0xc6, 0x0d, 0xe9, 0xc6, 0x51, 0xa6, 0xc0, 0x8f, 0xd1, 0xea, 0x4e, 0xc7, 0x1d, 0x72, 0x4d, 0x94, 0x1b, 0x17, 0xcf, 0x30, 0xed, 0x67, 0xf1, 0x87, 0x0d, 0x59, 0xf0, 0xfb, 0x14, 0x5a, 0x91, 0x37, 0xd1, 0x4f, 0x46, 0x6d, 0xfb, 0x17, 0x49, 0x48, 0x7e, 0x71, 0x24, 0xd5, 0xaa, 0xe0, 0x4c, 0x29, 0xbc, 0x97, 0xe3, 0x03, 0x6d, 0x8b, 0xd4, 0xdc, 0xf8, 0x74, 0xa4, 0xa9, 0x81, 0x63, 0x31, 0x55, 0x19, 0xdd, 0x01, 0xa5, 0xdf, 0xd2, 0xce, 0xd0, 0xd5, 0x70, 0x73, 0xdb, 0x02, 0x03, 0x01, 0x00, 0x01};
|
||||
static const char CC_JSON_POWER[] PROGMEM = ",\"%s\":{\"P\":%lu,\"Q\":%lu}";
|
||||
static const char CC_JSON_POWER_LIST3[] PROGMEM = ",\"%s\":{\"P\":%lu,\"Q\":%lu,\"tP\":%.3f,\"tQ\":%.3f}";
|
||||
static const char CC_JSON_PHASE[] PROGMEM = "%s\"%d\":{\"u\":%.2f,\"i\":%.2f}";
|
||||
static const char CC_JSON_PHASE_LIST4[] PROGMEM = "%s\"%d\":{\"u\":%.2f,\"i\":%.2f,\"Pim\":%lu,\"Pex\":%lu,\"pf\":%.2f}";
|
||||
|
||||
struct CloudData {
|
||||
uint8_t type;
|
||||
@ -32,16 +48,41 @@ struct CloudData {
|
||||
class CloudConnector {
|
||||
public:
|
||||
CloudConnector(RemoteDebug*);
|
||||
void setup(const unsigned char * key);
|
||||
void send();
|
||||
void setup(CloudConfig& config, HwTools* hw);
|
||||
void update(AmsData& data, EnergyAccounting& ea);
|
||||
void forceUpdate();
|
||||
|
||||
private:
|
||||
RemoteDebug* debugger;
|
||||
HwTools* hw;
|
||||
CloudConfig config;
|
||||
HTTPClient http;
|
||||
WiFiUDP udp;
|
||||
bool initialized = false;
|
||||
unsigned long lastUpdate = 0;
|
||||
char mac[18];
|
||||
|
||||
unsigned char buf[4096];
|
||||
char clearBuffer[CC_BUF_SIZE];
|
||||
unsigned char encryptedBuffer[256];
|
||||
mbedtls_rsa_context* rsa = nullptr;
|
||||
mbedtls_ctr_drbg_context ctr_drbg;
|
||||
mbedtls_entropy_context entropy;
|
||||
char* pers = "amsreader";
|
||||
|
||||
bool init();
|
||||
void debugPrint(byte *buffer, int start, int length);
|
||||
|
||||
String meterManufacturer(uint8_t type) {
|
||||
switch(type) {
|
||||
case AmsTypeAidon: return F("Aidon");
|
||||
case AmsTypeKaifa: return F("Kaifa");
|
||||
case AmsTypeKamstrup: return F("Kamstrup");
|
||||
case AmsTypeIskra: return F("Iskra");
|
||||
case AmsTypeLandisGyr: return F("Landis+Gyr");
|
||||
case AmsTypeSagemcom: return F("Sagemcom");
|
||||
}
|
||||
return F("");
|
||||
}
|
||||
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
@ -1,44 +1,223 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "CloudConnector.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "crc.h"
|
||||
#include "Uptime.h"
|
||||
#include "hexutils.h"
|
||||
|
||||
CloudConnector::CloudConnector(RemoteDebug* debugger) {
|
||||
this->debugger = debugger;
|
||||
mbedtls_pk_context pk;
|
||||
mbedtls_pk_init(&pk);
|
||||
|
||||
int error_code = 0;
|
||||
if((error_code = mbedtls_pk_parse_public_key(&pk, PUBLIC_KEY, sizeof(PUBLIC_KEY))) == 0){
|
||||
debugger->printf("RSA public key OK\n");
|
||||
rsa = mbedtls_pk_rsa(pk);
|
||||
} else {
|
||||
debugger->printf("RSA public key read error: ");
|
||||
mbedtls_strerror(error_code, (char*) buf, 4096);
|
||||
debugger->printf("%s\n", buf);
|
||||
}
|
||||
debugger->flush();
|
||||
//send();
|
||||
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
http.setReuse(false);
|
||||
http.setTimeout(60000);
|
||||
http.setUserAgent("ams2mqtt/" + String(FirmwareVersion::VersionString));
|
||||
http.useHTTP10(true);
|
||||
|
||||
uint8_t mac[6];
|
||||
|
||||
#if defined(ESP8266)
|
||||
wifi_get_macaddr(STATION_IF, mac);
|
||||
#elif defined(ESP32)
|
||||
esp_wifi_get_mac((wifi_interface_t)ESP_IF_WIFI_STA, mac);
|
||||
#endif
|
||||
sprintf(this->mac, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
}
|
||||
|
||||
void CloudConnector::send() {
|
||||
if(rsa != nullptr && mbedtls_rsa_check_pubkey(rsa) == 0) {
|
||||
memset(buf, 0, 4096);
|
||||
|
||||
CloudData data = {65, 127};
|
||||
unsigned char toEncrypt[4096] = {0};
|
||||
|
||||
debugger->println("RSA clear data: ");
|
||||
debugPrint(toEncrypt, 0, 256);
|
||||
|
||||
mbedtls_rsa_rsaes_pkcs1_v15_encrypt(rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, 256, toEncrypt, buf);
|
||||
|
||||
//byte hashResult[32];
|
||||
//mbedtls_sha256(toEncrypt, strlen((char*) toEncrypt), hashResult, 0);
|
||||
//int success = mbedtls_rsa_rsassa_pkcs1_v15_sign(rsa, NULL, NULL, MBEDTLS_RSA_PUBLIC, MBEDTLS_MD_SHA256, strlen((char*) hashResult), hashResult, buf);
|
||||
debugger->println("RSA encrypted data: ");
|
||||
debugPrint(buf, 0, 256);
|
||||
} else {
|
||||
debugger->println("RSA key is invalid");
|
||||
}
|
||||
void CloudConnector::setup(CloudConfig& config, HwTools* hw) {
|
||||
this->config = config;
|
||||
this->hw = hw;
|
||||
}
|
||||
|
||||
bool CloudConnector::init() {
|
||||
if(config.enabled && strlen(config.hostname) > 0 && config.port > 0) {
|
||||
snprintf_P(clearBuffer, CC_BUF_SIZE, PSTR("http://%s/hub/cloud/public.key"), config.hostname);
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf("(CloudConnector) Downloading public key from %s\n", clearBuffer);
|
||||
#if defined(ESP8266)
|
||||
WiFiClient client;
|
||||
client.setTimeout(5000);
|
||||
if(http.begin(client, clearBuffer)) {
|
||||
#elif defined(ESP32)
|
||||
if(http.begin(clearBuffer)) {
|
||||
#endif
|
||||
int status = http.GET();
|
||||
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
|
||||
if(status == HTTP_CODE_OK) {
|
||||
String pub = http.getString();
|
||||
http.end();
|
||||
|
||||
memset(clearBuffer, 0, CC_BUF_SIZE);
|
||||
snprintf(clearBuffer, CC_BUF_SIZE, pub.c_str());
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("Cloud public key:\n%s\n", clearBuffer);
|
||||
|
||||
mbedtls_pk_context pk;
|
||||
mbedtls_pk_init(&pk);
|
||||
|
||||
int error_code = 0;
|
||||
if((error_code = mbedtls_pk_parse_public_key(&pk, (unsigned char*) clearBuffer, strlen((const char*) clearBuffer)+1)) == 0){
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("RSA public key OK\n");
|
||||
rsa = mbedtls_pk_rsa(pk);
|
||||
mbedtls_ctr_drbg_init(&ctr_drbg);
|
||||
mbedtls_entropy_init(&entropy);
|
||||
|
||||
int ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func,
|
||||
&entropy, (const unsigned char *) pers,
|
||||
strlen(pers));
|
||||
if(ret != 0) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("mbedtls_ctr_drbg_seed return code: %d\n", ret);
|
||||
}
|
||||
return ret == 0;
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("RSA public key read error: ");
|
||||
mbedtls_strerror(error_code, clearBuffer, CC_BUF_SIZE);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("%s\n", clearBuffer);
|
||||
}
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf_P(PSTR("(CloudConnector) Communication error, returned status: %d\n"), status);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf(http.errorToString(status).c_str());
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf(http.getString().c_str());
|
||||
|
||||
http.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CloudConnector::update(AmsData& data, EnergyAccounting& ea) {
|
||||
if(!config.enabled || strlen(config.hostname) == 0 || config.port == 0) return;
|
||||
unsigned long now = millis();
|
||||
if(now-lastUpdate < config.interval*1000) return;
|
||||
lastUpdate = now;
|
||||
if(strlen(config.clientId) == 0 || strlen(config.clientSecret) == 0) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf("(CloudConnector) Client ID and secret is missing\n");
|
||||
return;
|
||||
}
|
||||
if(data.getListType() < 2) return;
|
||||
|
||||
memset(clearBuffer, 0, CC_BUF_SIZE);
|
||||
|
||||
int pos = 0;
|
||||
if(initialized) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("{\"id\":\"%s\",\"secret\":\"%s\",\"data\":{\"clock\":%lu,\"up\":%lu,\"lastUpdate\":%lu,\"est\":%s"),
|
||||
config.clientId,
|
||||
config.clientSecret,
|
||||
(uint32_t) time(nullptr),
|
||||
(uint32_t) (millis64()/1000),
|
||||
(uint32_t) (data.getLastUpdateMillis()/1000),
|
||||
data.isCounterEstimated() ? "true" : "false"
|
||||
);
|
||||
if(data.getListType() > 2) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "import", data.getActiveImportPower(), data.getReactiveImportPower(), data.getActiveImportCounter(), data.getReactiveImportCounter());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "import", data.getActiveImportPower(), data.getReactiveImportPower());
|
||||
}
|
||||
if(data.getListType() > 2) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER_LIST3, "export", data.getActiveExportPower(), data.getReactiveExportPower(), data.getActiveExportCounter(), data.getReactiveExportCounter());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_POWER, "export", data.getActiveExportPower(), data.getReactiveExportPower());
|
||||
}
|
||||
|
||||
if(data.getListType() > 1) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"phases\":{"));
|
||||
bool first = true;
|
||||
if(data.getL1Voltage() > 0.0) {
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 1, data.getL1Voltage(), data.getL1Current(), data.getL1ActiveImportPower(), data.getL1ActiveExportPower(), data.getL1PowerFactor());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 1, data.getL1Voltage(), data.getL1Current());
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if(data.getL2Voltage() > 0.0) {
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 2, data.getL2Voltage(), data.getL2Current(), data.getL2ActiveImportPower(), data.getL2ActiveExportPower(), data.getL2PowerFactor());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 2, data.getL2Voltage(), data.getL2Current());
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if(data.getL3Voltage() > 0.0) {
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE_LIST4, first ? "" : ",", 3, data.getL3Voltage(), data.getL3Current(), data.getL3ActiveImportPower(), data.getL3ActiveExportPower(), data.getL3PowerFactor());
|
||||
} else {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, CC_JSON_PHASE, first ? "" : ",", 3, data.getL3Voltage(), data.getL3Current());
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
|
||||
}
|
||||
if(data.getListType() > 3) {
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"pf\":%.2f"), data.getPowerFactor());
|
||||
}
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"realtime\":{\"import\":%.3f,\"export\":%.3f}"), ea.getUseThisHour(), ea.getProducedThisHour());
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"vcc\":%.2f,\"temp\":%.2f,\"rssi\":%d,\"free\":%d"), hw->getVcc(), hw->getTemperature(), hw->getWifiRssi(), ESP.getFreeHeap());
|
||||
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("}"));
|
||||
} else {
|
||||
if(!init()) {
|
||||
if(debugger->isActive(RemoteDebug::WARNING)) debugger->printf("Unable to initialize cloud connector\n");
|
||||
return;
|
||||
}
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR("{\"id\":\"%s\",\"secret\":\"%s\",\"init\":{\"mac\":\"%s\",\"version\":\"%s\"},\"meter\":{\"manufacturer\":\"%s\",\"model\":\"%s\",\"id\":\"%s\"}"),
|
||||
config.clientId,
|
||||
config.clientSecret,
|
||||
mac,
|
||||
FirmwareVersion::VersionString,
|
||||
meterManufacturer(data.getMeterType()).c_str(),
|
||||
data.getMeterModel().c_str(),
|
||||
data.getMeterId().c_str()
|
||||
);
|
||||
initialized = true;
|
||||
}
|
||||
uint16_t crc = crc16((uint8_t*) clearBuffer, pos);
|
||||
pos += snprintf_P(clearBuffer+pos, CC_BUF_SIZE-pos, PSTR(",\"crc\":\"%04X\"}"), crc);
|
||||
|
||||
if(rsa == nullptr) return;
|
||||
int ret = mbedtls_rsa_check_pubkey(rsa);
|
||||
if(ret != 0) {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("mbedtls_rsa_pkcs1_encrypt return code: %d\n", ret);
|
||||
mbedtls_strerror(ret, clearBuffer, CC_BUF_SIZE);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("%s\n", clearBuffer);
|
||||
return;
|
||||
}
|
||||
memset(encryptedBuffer, 0, rsa->len);
|
||||
|
||||
int maxlen = 100 * (rsa->len/128);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf("(CloudConnector) Sending %d bytes and maxlen %d\n", pos, maxlen);
|
||||
udp.beginPacket(config.hostname,7443);
|
||||
for(int i = 0; i < pos; i += maxlen) {
|
||||
int size = min(maxlen, pos-i);
|
||||
int ret = mbedtls_rsa_pkcs1_encrypt(rsa, mbedtls_ctr_drbg_random, &ctr_drbg, MBEDTLS_RSA_PUBLIC, size, (unsigned char*) (clearBuffer+i), encryptedBuffer);
|
||||
if(ret == 0) {
|
||||
udp.write(encryptedBuffer, rsa->len);
|
||||
delay(1);
|
||||
} else {
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("mbedtls_rsa_pkcs1_encrypt return code: %d\n", ret);
|
||||
mbedtls_strerror(ret, clearBuffer, CC_BUF_SIZE);
|
||||
if(debugger->isActive(RemoteDebug::ERROR)) debugger->printf("%s\n", clearBuffer);
|
||||
}
|
||||
}
|
||||
udp.endPacket();
|
||||
}
|
||||
|
||||
void CloudConnector::forceUpdate() {
|
||||
lastUpdate = 0;
|
||||
}
|
||||
|
||||
void CloudConnector::debugPrint(byte *buffer, int start, int length) {
|
||||
for (int i = start; i < start + length; i++) {
|
||||
if (buffer[i] < 0x10)
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DOMOTICZMQTTHANDLER_H
|
||||
#define _DOMOTICZMQTTHANDLER_H
|
||||
|
||||
@ -6,13 +12,16 @@
|
||||
|
||||
class DomoticzMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
DomoticzMqttHandler(MQTTClient* mqtt, char* buf, DomoticzConfig config) : AmsMqttHandler(mqtt, buf) {
|
||||
DomoticzMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, DomoticzConfig config) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->config = config;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
|
||||
uint8_t getFormat();
|
||||
|
||||
private:
|
||||
DomoticzConfig config;
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DomoticzMqttHandler.h"
|
||||
#include "json/domoticz_json.h"
|
||||
|
||||
@ -14,7 +20,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyA
|
||||
config.elidx,
|
||||
val
|
||||
);
|
||||
ret = mqtt->publish(F("domoticz/in"), json);
|
||||
ret = mqtt.publish(F("domoticz/in"), json);
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,7 +34,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyA
|
||||
config.vl1idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt->publish(F("domoticz/in"), json);
|
||||
ret |= mqtt.publish(F("domoticz/in"), json);
|
||||
}
|
||||
|
||||
if (config.vl2idx > 0){
|
||||
@ -38,7 +44,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyA
|
||||
config.vl2idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt->publish(F("domoticz/in"), json);
|
||||
ret |= mqtt.publish(F("domoticz/in"), json);
|
||||
}
|
||||
|
||||
if (config.vl3idx > 0){
|
||||
@ -48,7 +54,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyA
|
||||
config.vl3idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt->publish(F("domoticz/in"), json);
|
||||
ret |= mqtt.publish(F("domoticz/in"), json);
|
||||
}
|
||||
|
||||
if (config.cl1idx > 0){
|
||||
@ -58,7 +64,7 @@ bool DomoticzMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyA
|
||||
config.cl1idx,
|
||||
val
|
||||
);
|
||||
ret |= mqtt->publish(F("domoticz/in"), json);
|
||||
ret |= mqtt.publish(F("domoticz/in"), json);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -74,3 +80,11 @@ bool DomoticzMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
bool DomoticzMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t DomoticzMqttHandler::getFormat() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
bool DomoticzMqttHandler::publishRaw(String data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ENERGYACCOUNTING_H
|
||||
#define _ENERGYACCOUNTING_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "EnergyAccounting.h"
|
||||
#include "LittleFS.h"
|
||||
#include "AmsStorage.h"
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _DNBCURRPARSER_H
|
||||
#define _DNBCURRPARSER_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ENTSOEA44PARSER_H
|
||||
#define _ENTSOEA44PARSER_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _ENTSOEAPI_H
|
||||
#define _ENTSOEAPI_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _PRICESCONTAINER_H
|
||||
#define _PRICESCONTAINER_H
|
||||
struct PricesContainer {
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DnbCurrParser.h"
|
||||
#include "Arduino.h"
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "EntsoeA44Parser.h"
|
||||
#include "HardwareSerial.h"
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "EntsoeApi.h"
|
||||
#include <EEPROM.h>
|
||||
#include "Uptime.h"
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _FIRMWARE_VERSION_h
|
||||
#define _FIRMWARE_VERSION_h
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "FirmwareVersion.h"
|
||||
#include "generated_version.h"
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HOMEASSISTANTMQTTHANDLER_H
|
||||
#define _HOMEASSISTANTMQTTHANDLER_H
|
||||
|
||||
@ -7,12 +13,12 @@
|
||||
|
||||
class HomeAssistantMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
HomeAssistantMqttHandler(MQTTClient* mqtt, char* buf, const char* clientId, const char* topic, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqtt, buf) {
|
||||
this->clientId = clientId;
|
||||
this->topic = String(topic);
|
||||
HomeAssistantMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, uint8_t boardType, HomeAssistantConfig config, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->hw = hw;
|
||||
l1Init = l2Init = l2eInit = l3Init = l3eInit = l4Init = l4eInit = rtInit = rteInit = pInit = sInit = false;
|
||||
|
||||
topic = String(mqttConfig.publishTopic);
|
||||
|
||||
if(strlen(config.discoveryNameTag) > 0) {
|
||||
snprintf_P(buf, 128, PSTR("AMS reader (%s)"), config.discoveryNameTag);
|
||||
deviceName = String(buf);
|
||||
@ -56,11 +62,13 @@ public:
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
|
||||
protected:
|
||||
bool loop();
|
||||
uint8_t getFormat();
|
||||
|
||||
private:
|
||||
String topic;
|
||||
|
||||
String deviceName;
|
||||
String deviceModel;
|
||||
String deviceUid;
|
||||
@ -74,8 +82,6 @@ private:
|
||||
bool tInit[32] = {false};
|
||||
bool prInit[38] = {false};
|
||||
|
||||
String clientId;
|
||||
String topic;
|
||||
HwTools* hw;
|
||||
|
||||
bool publishList1(AmsData* data, EnergyAccounting* ea);
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HOMEASSISTANTSTATIC_H
|
||||
#define _HOMEASSISTANTSTATIC_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HomeAssistantMqttHandler.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
@ -16,7 +22,7 @@
|
||||
#endif
|
||||
|
||||
bool HomeAssistantMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
if(data->getListType() >= 3) { // publish energy counts
|
||||
@ -45,7 +51,7 @@ bool HomeAssistantMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea)
|
||||
snprintf_P(json, BufferSize, HA1_JSON,
|
||||
data->getActiveImportPower()
|
||||
);
|
||||
return mqtt->publish(topic + "/power", json);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
|
||||
@ -66,7 +72,7 @@ bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea)
|
||||
data->getL2Voltage(),
|
||||
data->getL3Voltage()
|
||||
);
|
||||
return mqtt->publish(topic + "/power", json);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
|
||||
@ -79,7 +85,7 @@ bool HomeAssistantMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea)
|
||||
data->getReactiveExportCounter(),
|
||||
data->getMeterTimestamp()
|
||||
);
|
||||
return mqtt->publish(topic + "/energy", json);
|
||||
return mqtt.publish(topic + "/energy", json);
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
|
||||
@ -110,7 +116,7 @@ bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea)
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL2PowerFactor(),
|
||||
data->getPowerFactor() == 0 ? 1 : data->getL3PowerFactor()
|
||||
);
|
||||
return mqtt->publish(topic + "/power", json);
|
||||
return mqtt.publish(topic + "/power", json);
|
||||
}
|
||||
|
||||
String HomeAssistantMqttHandler::getMeterModel(AmsData* data) {
|
||||
@ -146,7 +152,7 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting*
|
||||
ea->getProducedThisMonth(),
|
||||
ea->getIncomeThisMonth()
|
||||
);
|
||||
return mqtt->publish(topic + "/realtime", json);
|
||||
return mqtt.publish(topic + "/realtime", json);
|
||||
}
|
||||
|
||||
|
||||
@ -174,13 +180,13 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT
|
||||
}
|
||||
char* pos = buf+strlen(buf);
|
||||
snprintf_P(count == 0 ? pos : pos-1, 8, PSTR("}}"));
|
||||
bool ret = mqtt->publish(topic + "/temperatures", buf);
|
||||
bool ret = mqtt.publish(topic + "/temperatures", buf);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
return false;
|
||||
@ -310,13 +316,13 @@ bool HomeAssistantMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
ts3hr,
|
||||
ts6hr
|
||||
);
|
||||
bool ret = mqtt->publish(topic + "/prices", json, true, 0);
|
||||
bool ret = mqtt.publish(topic + "/prices", json, true, 0);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
publishSystemSensors();
|
||||
@ -324,14 +330,14 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, Energ
|
||||
|
||||
snprintf_P(json, BufferSize, JSONSYS_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
clientId.c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
FirmwareVersion::VersionString
|
||||
);
|
||||
bool ret = mqtt->publish(topic + "/state", json);
|
||||
bool ret = mqtt.publish(topic + "/state", json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
@ -345,7 +351,7 @@ void HomeAssistantMqttHandler::publishSensor(const HomeAssistantSensor& sensor)
|
||||
snprintf_P(json, BufferSize, HADISCOVER_JSON,
|
||||
sensorNamePrefix.c_str(),
|
||||
sensor.name,
|
||||
topic.c_str(), sensor.topic,
|
||||
mqttConfig.publishTopic, sensor.topic,
|
||||
deviceUid.c_str(), uid.c_str(),
|
||||
deviceUid.c_str(), uid.c_str(),
|
||||
sensor.uom,
|
||||
@ -363,7 +369,7 @@ void HomeAssistantMqttHandler::publishSensor(const HomeAssistantSensor& sensor)
|
||||
strlen_P(sensor.stacl) > 0 ? (char *) FPSTR(sensor.stacl) : "",
|
||||
strlen_P(sensor.stacl) > 0 ? "\"" : ""
|
||||
);
|
||||
mqtt->publish(discoveryTopic + deviceUid + "_" + uid.c_str() + "/config", json, true, 0);
|
||||
mqtt.publish(discoveryTopic + deviceUid + "_" + uid.c_str() + "/config", json, true, 0);
|
||||
loop();
|
||||
}
|
||||
|
||||
@ -540,14 +546,10 @@ void HomeAssistantMqttHandler::publishSystemSensors() {
|
||||
sInit = true;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::loop() {
|
||||
bool ret = mqtt->loop();
|
||||
delay(10);
|
||||
yield();
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
return ret;
|
||||
uint8_t HomeAssistantMqttHandler::getFormat() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
bool HomeAssistantMqttHandler::publishRaw(String data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _HWTOOLS_H
|
||||
#define _HWTOOLS_H
|
||||
|
||||
@ -48,6 +54,7 @@ public:
|
||||
bool ledOn(uint8_t color);
|
||||
bool ledOff(uint8_t color);
|
||||
bool ledBlink(uint8_t color, uint8_t blink);
|
||||
void setBootSuccessful();
|
||||
|
||||
HwTools() {};
|
||||
private:
|
||||
@ -64,6 +71,8 @@ private:
|
||||
uint8_t sensorCount = 0;
|
||||
TempSensorData** tempSensors = NULL;
|
||||
|
||||
bool bootSuccessful = false;
|
||||
|
||||
bool writeLedPin(uint8_t color, uint8_t state);
|
||||
bool isSensorAddressEqual(uint8_t a[8], uint8_t b[8]);
|
||||
void getAdcChannel(uint8_t pin, AdcConfig&);
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "HwTools.h"
|
||||
|
||||
void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
@ -92,6 +98,22 @@ void HwTools::setup(GpioConfig* config, AmsConfiguration* amsConf) {
|
||||
} else {
|
||||
config->ledPinBlue = 0xFF;
|
||||
}
|
||||
if(config->ledDisablePin > 0 && config->ledDisablePin < 40) {
|
||||
pinMode(config->ledDisablePin, OUTPUT_OPEN_DRAIN);
|
||||
switch(config->ledBehaviour) {
|
||||
case LED_BEHAVIOUR_ERROR_ONLY:
|
||||
case LED_BEHAVIOUR_OFF:
|
||||
digitalWrite(config->ledDisablePin, LOW);
|
||||
break;
|
||||
case LED_BEHAVIOUR_BOOT:
|
||||
if(bootSuccessful) {
|
||||
digitalWrite(config->ledDisablePin, LOW);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
digitalWrite(config->ledDisablePin, HIGH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
|
||||
@ -358,8 +380,7 @@ float HwTools::getTemperature() {
|
||||
}
|
||||
for(int x = 0; x < sensorCount; x++) {
|
||||
TempSensorData data = *tempSensors[x];
|
||||
TempSensorConfig* conf = amsConf->getTempSensorConfig(data.address);
|
||||
if((conf == NULL || conf->common) && data.lastValidRead > -85) {
|
||||
if(data.lastValidRead > -85) {
|
||||
ret += data.lastValidRead;
|
||||
c++;
|
||||
}
|
||||
@ -380,7 +401,19 @@ int HwTools::getWifiRssi() {
|
||||
return isnan(rssi) ? -100.0 : rssi;
|
||||
}
|
||||
|
||||
void HwTools::setBootSuccessful() {
|
||||
if(bootSuccessful) return;
|
||||
bootSuccessful = true;
|
||||
if(config->ledBehaviour != LED_BEHAVIOUR_DEFAULT) {
|
||||
digitalWrite(config->ledDisablePin, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
bool HwTools::ledOn(uint8_t color) {
|
||||
if(config->ledBehaviour == LED_BEHAVIOUR_OFF) return false;
|
||||
if(config->ledBehaviour == LED_BEHAVIOUR_ERROR_ONLY && color != LED_RED) return false;
|
||||
if(config->ledBehaviour == LED_BEHAVIOUR_BOOT && color != LED_RED && bootSuccessful) return false;
|
||||
|
||||
if(color == LED_INTERNAL) {
|
||||
return writeLedPin(color, config->ledInverted ? LOW : HIGH);
|
||||
} else {
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _JSONMQTTHANDLER_H
|
||||
#define _JSONMQTTHANDLER_H
|
||||
|
||||
@ -5,22 +11,18 @@
|
||||
|
||||
class JsonMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
JsonMqttHandler(MQTTClient* mqtt, char* buf, const char* clientId, const char* topic, HwTools* hw) : AmsMqttHandler(mqtt, buf) {
|
||||
this->clientId = clientId;
|
||||
this->topic = String(topic);
|
||||
JsonMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf, HwTools* hw) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
this->hw = hw;
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
|
||||
protected:
|
||||
bool loop();
|
||||
uint8_t getFormat();
|
||||
|
||||
private:
|
||||
String clientId;
|
||||
String topic;
|
||||
HwTools* hw;
|
||||
|
||||
bool publishList1(AmsData* data, EnergyAccounting* ea);
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "JsonMqttHandler.h"
|
||||
#include "FirmwareVersion.h"
|
||||
#include "hexutils.h"
|
||||
@ -10,11 +16,18 @@
|
||||
#include "json/jsonprices_json.h"
|
||||
|
||||
bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
if(strlen(mqttConfig.publishTopic) == 0) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Unable to publish data, no publish topic\n"));
|
||||
return false;
|
||||
}
|
||||
if(!mqtt.connected()) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Unable to publish data, not connected\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = false;
|
||||
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Publishing list ID %d!\n"), data->getListType());
|
||||
if(data->getListType() == 1) {
|
||||
ret = publishList1(data, ea);
|
||||
} else if(data->getListType() == 2) {
|
||||
@ -31,7 +44,7 @@ bool JsonMqttHandler::publish(AmsData* data, AmsData* previousState, EnergyAccou
|
||||
bool JsonMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON1_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
clientId.c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
@ -45,13 +58,13 @@ bool JsonMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea) {
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON2_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
clientId.c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
@ -77,13 +90,13 @@ bool JsonMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea) {
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON3_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
clientId.c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
@ -114,13 +127,13 @@ bool JsonMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea) {
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
|
||||
snprintf_P(json, BufferSize, JSON4_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
clientId.c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
data->getPackageTimestamp(),
|
||||
hw->getVcc(),
|
||||
@ -161,7 +174,7 @@ bool JsonMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea) {
|
||||
ea->getProducedThisHour(),
|
||||
ea->getProducedToday()
|
||||
);
|
||||
return mqtt->publish(topic, json);
|
||||
return mqtt.publish(mqttConfig.publishTopic, json);
|
||||
}
|
||||
|
||||
String JsonMqttHandler::getMeterModel(AmsData* data) {
|
||||
@ -191,13 +204,13 @@ bool JsonMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
|
||||
}
|
||||
char* pos = json+strlen(json);
|
||||
snprintf_P(count == 0 ? pos : pos-1, 8, PSTR("}}"));
|
||||
bool ret = mqtt->publish(topic, json);
|
||||
bool ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
|
||||
return false;
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
return false;
|
||||
@ -325,37 +338,33 @@ bool JsonMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
ts3hr,
|
||||
ts6hr
|
||||
);
|
||||
bool ret = mqtt->publish(topic, json);
|
||||
bool ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
if(strlen(mqttConfig.publishTopic) == 0 || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
snprintf_P(json, BufferSize, JSONSYS_JSON,
|
||||
WiFi.macAddress().c_str(),
|
||||
clientId.c_str(),
|
||||
mqttConfig.clientId,
|
||||
(uint32_t) (millis64()/1000),
|
||||
hw->getVcc(),
|
||||
hw->getWifiRssi(),
|
||||
hw->getTemperature(),
|
||||
FirmwareVersion::VersionString
|
||||
);
|
||||
bool ret = mqtt->publish(topic, json);
|
||||
bool ret = mqtt.publish(mqttConfig.publishTopic, json);
|
||||
loop();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::loop() {
|
||||
bool ret = mqtt->loop();
|
||||
delay(10);
|
||||
yield();
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
return ret;
|
||||
uint8_t JsonMqttHandler::getFormat() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool JsonMqttHandler::publishRaw(String data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _RAWMQTTHANDLER_H
|
||||
#define _RAWMQTTHANDLER_H
|
||||
|
||||
@ -5,21 +11,21 @@
|
||||
|
||||
class RawMqttHandler : public AmsMqttHandler {
|
||||
public:
|
||||
RawMqttHandler(MQTTClient* mqtt, char* buf, const char* topic, bool full) : AmsMqttHandler(mqtt, buf) {
|
||||
this->topic = String(topic);
|
||||
this->full = full;
|
||||
RawMqttHandler(MqttConfig& mqttConfig, RemoteDebug* debugger, char* buf) : AmsMqttHandler(mqttConfig, debugger, buf) {
|
||||
full = mqttConfig.payloadFormat == 2;
|
||||
topic = String(mqttConfig.publishTopic);
|
||||
};
|
||||
bool publish(AmsData* data, AmsData* previousState, EnergyAccounting* ea, EntsoeApi* eapi);
|
||||
bool publishTemperatures(AmsConfiguration*, HwTools*);
|
||||
bool publishPrices(EntsoeApi*);
|
||||
bool publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea);
|
||||
bool publishRaw(String data);
|
||||
|
||||
protected:
|
||||
bool loop();
|
||||
uint8_t getFormat();
|
||||
|
||||
private:
|
||||
String topic;
|
||||
bool full;
|
||||
String topic;
|
||||
|
||||
bool publishList1(AmsData* data, AmsData* meterState);
|
||||
bool publishList2(AmsData* data, AmsData* meterState);
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "RawMqttHandler.h"
|
||||
#include "hexutils.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccounting* ea, EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
if(data->getPackageTimestamp() > 0) {
|
||||
mqtt->publish(topic + "/meter/dlms/timestamp", String(data->getPackageTimestamp()));
|
||||
mqtt.publish(topic + "/meter/dlms/timestamp", String(data->getPackageTimestamp()));
|
||||
}
|
||||
switch(data->getListType()) {
|
||||
case 4:
|
||||
@ -32,7 +38,7 @@ bool RawMqttHandler::publish(AmsData* data, AmsData* meterState, EnergyAccountin
|
||||
|
||||
bool RawMqttHandler::publishList1(AmsData* data, AmsData* meterState) {
|
||||
if(full || meterState->getActiveImportPower() != data->getActiveImportPower()) {
|
||||
mqtt->publish(topic + "/meter/import/active", String(data->getActiveImportPower()));
|
||||
mqtt.publish(topic + "/meter/import/active", String(data->getActiveImportPower()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -40,101 +46,101 @@ bool RawMqttHandler::publishList1(AmsData* data, AmsData* meterState) {
|
||||
bool RawMqttHandler::publishList2(AmsData* data, AmsData* meterState) {
|
||||
// Only send data if changed. ID and Type is sent on the 10s interval only if changed
|
||||
if(full || meterState->getMeterId() != data->getMeterId()) {
|
||||
mqtt->publish(topic + "/meter/id", data->getMeterId());
|
||||
mqtt.publish(topic + "/meter/id", data->getMeterId());
|
||||
}
|
||||
if(full || meterState->getMeterModel() != data->getMeterModel()) {
|
||||
mqtt->publish(topic + "/meter/type", data->getMeterModel());
|
||||
mqtt.publish(topic + "/meter/type", data->getMeterModel());
|
||||
}
|
||||
if(full || meterState->getL1Current() != data->getL1Current()) {
|
||||
mqtt->publish(topic + "/meter/l1/current", String(data->getL1Current(), 2));
|
||||
mqtt.publish(topic + "/meter/l1/current", String(data->getL1Current(), 2));
|
||||
}
|
||||
if(full || meterState->getL1Voltage() != data->getL1Voltage()) {
|
||||
mqtt->publish(topic + "/meter/l1/voltage", String(data->getL1Voltage(), 2));
|
||||
mqtt.publish(topic + "/meter/l1/voltage", String(data->getL1Voltage(), 2));
|
||||
}
|
||||
if(full || meterState->getL2Current() != data->getL2Current()) {
|
||||
mqtt->publish(topic + "/meter/l2/current", String(data->getL2Current(), 2));
|
||||
mqtt.publish(topic + "/meter/l2/current", String(data->getL2Current(), 2));
|
||||
}
|
||||
if(full || meterState->getL2Voltage() != data->getL2Voltage()) {
|
||||
mqtt->publish(topic + "/meter/l2/voltage", String(data->getL2Voltage(), 2));
|
||||
mqtt.publish(topic + "/meter/l2/voltage", String(data->getL2Voltage(), 2));
|
||||
}
|
||||
if(full || meterState->getL3Current() != data->getL3Current()) {
|
||||
mqtt->publish(topic + "/meter/l3/current", String(data->getL3Current(), 2));
|
||||
mqtt.publish(topic + "/meter/l3/current", String(data->getL3Current(), 2));
|
||||
}
|
||||
if(full || meterState->getL3Voltage() != data->getL3Voltage()) {
|
||||
mqtt->publish(topic + "/meter/l3/voltage", String(data->getL3Voltage(), 2));
|
||||
mqtt.publish(topic + "/meter/l3/voltage", String(data->getL3Voltage(), 2));
|
||||
}
|
||||
if(full || meterState->getReactiveExportPower() != data->getReactiveExportPower()) {
|
||||
mqtt->publish(topic + "/meter/export/reactive", String(data->getReactiveExportPower()));
|
||||
mqtt.publish(topic + "/meter/export/reactive", String(data->getReactiveExportPower()));
|
||||
}
|
||||
if(full || meterState->getActiveExportPower() != data->getActiveExportPower()) {
|
||||
mqtt->publish(topic + "/meter/export/active", String(data->getActiveExportPower()));
|
||||
mqtt.publish(topic + "/meter/export/active", String(data->getActiveExportPower()));
|
||||
}
|
||||
if(full || meterState->getReactiveImportPower() != data->getReactiveImportPower()) {
|
||||
mqtt->publish(topic + "/meter/import/reactive", String(data->getReactiveImportPower()));
|
||||
mqtt.publish(topic + "/meter/import/reactive", String(data->getReactiveImportPower()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishList3(AmsData* data, AmsData* meterState) {
|
||||
// ID and type belongs to List 2, but I see no need to send that every 10s
|
||||
mqtt->publish(topic + "/meter/id", data->getMeterId(), true, 0);
|
||||
mqtt->publish(topic + "/meter/type", data->getMeterModel(), true, 0);
|
||||
mqtt->publish(topic + "/meter/clock", String(data->getMeterTimestamp()));
|
||||
mqtt->publish(topic + "/meter/import/reactive/accumulated", String(data->getReactiveImportCounter(), 3), true, 0);
|
||||
mqtt->publish(topic + "/meter/import/active/accumulated", String(data->getActiveImportCounter(), 3), true, 0);
|
||||
mqtt->publish(topic + "/meter/export/reactive/accumulated", String(data->getReactiveExportCounter(), 3), true, 0);
|
||||
mqtt->publish(topic + "/meter/export/active/accumulated", String(data->getActiveExportCounter(), 3), true, 0);
|
||||
mqtt.publish(topic + "/meter/id", data->getMeterId(), true, 0);
|
||||
mqtt.publish(topic + "/meter/type", data->getMeterModel(), true, 0);
|
||||
mqtt.publish(topic + "/meter/clock", String(data->getMeterTimestamp()));
|
||||
mqtt.publish(topic + "/meter/import/reactive/accumulated", String(data->getReactiveImportCounter(), 3), true, 0);
|
||||
mqtt.publish(topic + "/meter/import/active/accumulated", String(data->getActiveImportCounter(), 3), true, 0);
|
||||
mqtt.publish(topic + "/meter/export/reactive/accumulated", String(data->getReactiveExportCounter(), 3), true, 0);
|
||||
mqtt.publish(topic + "/meter/export/active/accumulated", String(data->getActiveExportCounter(), 3), true, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishList4(AmsData* data, AmsData* meterState) {
|
||||
if(full || meterState->getL1ActiveImportPower() != data->getL1ActiveImportPower()) {
|
||||
mqtt->publish(topic + "/meter/import/l1", String(data->getL1ActiveImportPower(), 2));
|
||||
mqtt.publish(topic + "/meter/import/l1", String(data->getL1ActiveImportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getL2ActiveImportPower() != data->getL2ActiveImportPower()) {
|
||||
mqtt->publish(topic + "/meter/import/l2", String(data->getL2ActiveImportPower(), 2));
|
||||
mqtt.publish(topic + "/meter/import/l2", String(data->getL2ActiveImportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getL3ActiveImportPower() != data->getL3ActiveImportPower()) {
|
||||
mqtt->publish(topic + "/meter/import/l3", String(data->getL3ActiveImportPower(), 2));
|
||||
mqtt.publish(topic + "/meter/import/l3", String(data->getL3ActiveImportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getL1ActiveExportPower() != data->getL1ActiveExportPower()) {
|
||||
mqtt->publish(topic + "/meter/export/l1", String(data->getL1ActiveExportPower(), 2));
|
||||
mqtt.publish(topic + "/meter/export/l1", String(data->getL1ActiveExportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getL2ActiveExportPower() != data->getL2ActiveExportPower()) {
|
||||
mqtt->publish(topic + "/meter/export/l2", String(data->getL2ActiveExportPower(), 2));
|
||||
mqtt.publish(topic + "/meter/export/l2", String(data->getL2ActiveExportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getL3ActiveExportPower() != data->getL3ActiveExportPower()) {
|
||||
mqtt->publish(topic + "/meter/export/l3", String(data->getL3ActiveExportPower(), 2));
|
||||
mqtt.publish(topic + "/meter/export/l3", String(data->getL3ActiveExportPower(), 2));
|
||||
}
|
||||
if(full || meterState->getPowerFactor() != data->getPowerFactor()) {
|
||||
mqtt->publish(topic + "/meter/powerfactor", String(data->getPowerFactor(), 2));
|
||||
mqtt.publish(topic + "/meter/powerfactor", String(data->getPowerFactor(), 2));
|
||||
}
|
||||
if(full || meterState->getL1PowerFactor() != data->getL1PowerFactor()) {
|
||||
mqtt->publish(topic + "/meter/l1/powerfactor", String(data->getL1PowerFactor(), 2));
|
||||
mqtt.publish(topic + "/meter/l1/powerfactor", String(data->getL1PowerFactor(), 2));
|
||||
}
|
||||
if(full || meterState->getL2PowerFactor() != data->getL2PowerFactor()) {
|
||||
mqtt->publish(topic + "/meter/l2/powerfactor", String(data->getL2PowerFactor(), 2));
|
||||
mqtt.publish(topic + "/meter/l2/powerfactor", String(data->getL2PowerFactor(), 2));
|
||||
}
|
||||
if(full || meterState->getL3PowerFactor() != data->getL3PowerFactor()) {
|
||||
mqtt->publish(topic + "/meter/l3/powerfactor", String(data->getL3PowerFactor(), 2));
|
||||
mqtt.publish(topic + "/meter/l3/powerfactor", String(data->getL3PowerFactor(), 2));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishRealtime(EnergyAccounting* ea) {
|
||||
mqtt->publish(topic + "/realtime/import/hour", String(ea->getUseThisHour(), 3));
|
||||
mqtt->publish(topic + "/realtime/import/day", String(ea->getUseToday(), 2));
|
||||
mqtt->publish(topic + "/realtime/import/month", String(ea->getUseThisMonth(), 1));
|
||||
mqtt.publish(topic + "/realtime/import/hour", String(ea->getUseThisHour(), 3));
|
||||
mqtt.publish(topic + "/realtime/import/day", String(ea->getUseToday(), 2));
|
||||
mqtt.publish(topic + "/realtime/import/month", String(ea->getUseThisMonth(), 1));
|
||||
uint8_t peakCount = ea->getConfig()->hours;
|
||||
if(peakCount > 5) peakCount = 5;
|
||||
for(uint8_t i = 1; i <= peakCount; i++) {
|
||||
mqtt->publish(topic + "/realtime/import/peak/" + String(i, 10), String(ea->getPeak(i).value / 100.0, 10), true, 0);
|
||||
mqtt.publish(topic + "/realtime/import/peak/" + String(i, 10), String(ea->getPeak(i).value / 100.0, 10), true, 0);
|
||||
}
|
||||
mqtt->publish(topic + "/realtime/import/threshold", String(ea->getCurrentThreshold(), 10), true, 0);
|
||||
mqtt->publish(topic + "/realtime/import/monthmax", String(ea->getMonthMax(), 3), true, 0);
|
||||
mqtt->publish(topic + "/realtime/export/hour", String(ea->getProducedThisHour(), 3));
|
||||
mqtt->publish(topic + "/realtime/export/day", String(ea->getProducedToday(), 2));
|
||||
mqtt->publish(topic + "/realtime/export/month", String(ea->getProducedThisMonth(), 1));
|
||||
mqtt.publish(topic + "/realtime/import/threshold", String(ea->getCurrentThreshold(), 10), true, 0);
|
||||
mqtt.publish(topic + "/realtime/import/monthmax", String(ea->getMonthMax(), 3), true, 0);
|
||||
mqtt.publish(topic + "/realtime/export/hour", String(ea->getProducedThisHour(), 3));
|
||||
mqtt.publish(topic + "/realtime/export/day", String(ea->getProducedToday(), 2));
|
||||
mqtt.publish(topic + "/realtime/export/month", String(ea->getProducedThisMonth(), 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -144,7 +150,7 @@ bool RawMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
|
||||
TempSensorData* data = hw->getTempSensorData(i);
|
||||
if(data != NULL && data->lastValidRead > -85) {
|
||||
if(data->changed || full) {
|
||||
mqtt->publish(topic + "/temperature/" + toHex(data->address), String(data->lastValidRead, 2));
|
||||
mqtt.publish(topic + "/temperature/" + toHex(data->address), String(data->lastValidRead, 2));
|
||||
data->changed = false;
|
||||
}
|
||||
}
|
||||
@ -153,7 +159,7 @@ bool RawMqttHandler::publishTemperatures(AmsConfiguration* config, HwTools* hw)
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
if(eapi->getValueForHour(0) == ENTSOE_NO_VALUE)
|
||||
return false;
|
||||
@ -236,58 +242,54 @@ bool RawMqttHandler::publishPrices(EntsoeApi* eapi) {
|
||||
for(int i = 0; i < 34; i++) {
|
||||
float val = values[i];
|
||||
if(val == ENTSOE_NO_VALUE) {
|
||||
mqtt->publish(topic + "/price/" + String(i), "", true, 0);
|
||||
mqtt.publish(topic + "/price/" + String(i), "", true, 0);
|
||||
} else {
|
||||
mqtt->publish(topic + "/price/" + String(i), String(val, 4), true, 0);
|
||||
mqtt.publish(topic + "/price/" + String(i), String(val, 4), true, 0);
|
||||
}
|
||||
mqtt->loop();
|
||||
mqtt.loop();
|
||||
delay(10);
|
||||
}
|
||||
if(min != INT16_MAX) {
|
||||
mqtt->publish(topic + "/price/min", String(min, 4), true, 0);
|
||||
mqtt.publish(topic + "/price/min", String(min, 4), true, 0);
|
||||
}
|
||||
if(max != INT16_MIN) {
|
||||
mqtt->publish(topic + "/price/max", String(max, 4), true, 0);
|
||||
mqtt.publish(topic + "/price/max", String(max, 4), true, 0);
|
||||
}
|
||||
|
||||
if(min1hrIdx != -1) {
|
||||
mqtt->publish(topic + "/price/cheapest/1hr", String(ts1hr), true, 0);
|
||||
mqtt.publish(topic + "/price/cheapest/1hr", String(ts1hr), true, 0);
|
||||
}
|
||||
if(min3hrIdx != -1) {
|
||||
mqtt->publish(topic + "/price/cheapest/3hr", String(ts3hr), true, 0);
|
||||
mqtt.publish(topic + "/price/cheapest/3hr", String(ts3hr), true, 0);
|
||||
}
|
||||
if(min6hrIdx != -1) {
|
||||
mqtt->publish(topic + "/price/cheapest/6hr", String(ts6hr), true, 0);
|
||||
mqtt.publish(topic + "/price/cheapest/6hr", String(ts6hr), true, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccounting* ea) {
|
||||
if(topic.isEmpty() || !mqtt->connected())
|
||||
if(topic.isEmpty() || !mqtt.connected())
|
||||
return false;
|
||||
|
||||
mqtt->publish(topic + "/id", WiFi.macAddress(), true, 0);
|
||||
mqtt->publish(topic + "/uptime", String((uint32_t) (millis64()/1000)));
|
||||
mqtt.publish(topic + "/id", WiFi.macAddress(), true, 0);
|
||||
mqtt.publish(topic + "/uptime", String((uint32_t) (millis64()/1000)));
|
||||
float vcc = hw->getVcc();
|
||||
if(vcc > 0) {
|
||||
mqtt->publish(topic + "/vcc", String(vcc, 2));
|
||||
mqtt.publish(topic + "/vcc", String(vcc, 2));
|
||||
}
|
||||
mqtt->publish(topic + "/mem", String(ESP.getFreeHeap()));
|
||||
mqtt->publish(topic + "/rssi", String(hw->getWifiRssi()));
|
||||
mqtt.publish(topic + "/mem", String(ESP.getFreeHeap()));
|
||||
mqtt.publish(topic + "/rssi", String(hw->getWifiRssi()));
|
||||
if(hw->getTemperature() > -85) {
|
||||
mqtt->publish(topic + "/temperature", String(hw->getTemperature(), 2));
|
||||
mqtt.publish(topic + "/temperature", String(hw->getTemperature(), 2));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::loop() {
|
||||
bool ret = mqtt->loop();
|
||||
delay(10);
|
||||
yield();
|
||||
#if defined(ESP32)
|
||||
esp_task_wdt_reset();
|
||||
#elif defined(ESP8266)
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
return ret;
|
||||
uint8_t RawMqttHandler::getFormat() {
|
||||
return full ? 3 : 2;
|
||||
}
|
||||
|
||||
bool RawMqttHandler::publishRaw(String data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
31
lib/RealtimePlot/include/RealtimePlot.h
Normal file
31
lib/RealtimePlot/include/RealtimePlot.h
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _REALTIMEPLOT_H
|
||||
#define _REALTIMEPLOT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "AmsData.h"
|
||||
|
||||
#define REALTIME_SAMPLE 10000
|
||||
#define REALTIME_SIZE 360
|
||||
|
||||
class RealtimePlot {
|
||||
public:
|
||||
RealtimePlot();
|
||||
void update(AmsData& data);
|
||||
int32_t getValue(uint16_t req);
|
||||
int16_t getSize();
|
||||
|
||||
private:
|
||||
int8_t* values;
|
||||
uint8_t* scaling;
|
||||
|
||||
unsigned long lastMillis = 0;
|
||||
double lastReading = 0;
|
||||
uint16_t lastPos = 0;
|
||||
};
|
||||
#endif
|
||||
82
lib/RealtimePlot/src/RealtimePlot.cpp
Normal file
82
lib/RealtimePlot/src/RealtimePlot.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "RealtimePlot.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
RealtimePlot::RealtimePlot() {
|
||||
values = (int8_t*) malloc(REALTIME_SIZE);
|
||||
scaling = (uint8_t*) malloc(REALTIME_SIZE);
|
||||
memset(values, 0, REALTIME_SIZE);
|
||||
memset(scaling, 0, REALTIME_SIZE);
|
||||
}
|
||||
|
||||
void RealtimePlot::update(AmsData& data) {
|
||||
unsigned long now = millis();
|
||||
uint16_t pos = (now / REALTIME_SAMPLE) % REALTIME_SIZE;
|
||||
if(lastMillis == 0) {
|
||||
lastMillis = now;
|
||||
lastReading = data.getActiveImportCounter() - data.getActiveExportCounter();
|
||||
lastPos = pos;
|
||||
return;
|
||||
}
|
||||
if(pos == lastPos && data.isCounterEstimated()) return;
|
||||
|
||||
unsigned long ms = now - lastMillis;
|
||||
int32_t val; // A bit hacky this one, but just to avoid spikes at end of hour. Will mostly be correct
|
||||
if(data.isCounterEstimated()) {
|
||||
val = ((data.getActiveImportCounter() - data.getActiveExportCounter() - lastReading) * 1000) / (((float) ms) / 3600000.0);
|
||||
} else {
|
||||
val = data.getActiveImportPower() - data.getActiveExportPower();
|
||||
}
|
||||
uint8_t scale = 0;
|
||||
int32_t update = val / pow(10, scale);
|
||||
while(update > INT8_MAX || update < INT8_MIN) {
|
||||
update = val / pow(10, ++scale);
|
||||
}
|
||||
if(pos < lastPos) {
|
||||
for(uint16_t i = lastPos+1; i < REALTIME_SIZE; i++) {
|
||||
values[i] = update;
|
||||
scaling[i] = scale;
|
||||
}
|
||||
for(uint16_t i = 0; i <= pos; i++) {
|
||||
values[i] = update;
|
||||
scaling[i] = scale;
|
||||
}
|
||||
} else {
|
||||
for(uint16_t i = lastPos+1; i <= pos; i++) {
|
||||
values[i] = update;
|
||||
scaling[i] = scale;
|
||||
}
|
||||
}
|
||||
|
||||
lastMillis = now;
|
||||
lastReading = data.getActiveImportCounter() - data.getActiveExportCounter();
|
||||
lastPos = pos;
|
||||
}
|
||||
|
||||
int32_t RealtimePlot::getValue(uint16_t req) {
|
||||
if(req > REALTIME_SAMPLE) return 0;
|
||||
|
||||
unsigned long now = millis();
|
||||
if(req * REALTIME_SAMPLE > now) return 0;
|
||||
unsigned long reqTime = now - (req * REALTIME_SAMPLE);
|
||||
|
||||
uint16_t pos = (now / REALTIME_SAMPLE) % REALTIME_SIZE;
|
||||
uint16_t getPos;
|
||||
if(reqTime > lastMillis) {
|
||||
getPos = lastPos;
|
||||
} else if(req > pos) {
|
||||
getPos = REALTIME_SIZE + pos - req;
|
||||
} else {
|
||||
getPos = pos - req;
|
||||
}
|
||||
return values[getPos] * pow(10, scaling[getPos]);
|
||||
}
|
||||
|
||||
int16_t RealtimePlot::getSize() {
|
||||
return REALTIME_SIZE;
|
||||
}
|
||||
2
lib/SvelteUi/app/.gitignore
vendored
2
lib/SvelteUi/app/.gitignore
vendored
@ -10,6 +10,8 @@ lerna-debug.log*
|
||||
node_modules
|
||||
*.local
|
||||
|
||||
vite.config.local.js
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
||||
2
lib/SvelteUi/app/dist/index.css
vendored
2
lib/SvelteUi/app/dist/index.css
vendored
File diff suppressed because one or more lines are too long
2
lib/SvelteUi/app/dist/index.html
vendored
2
lib/SvelteUi/app/dist/index.html
vendored
@ -9,7 +9,7 @@
|
||||
<script type="module" crossorigin src="/index.js"></script>
|
||||
<link rel="stylesheet" href="/index.css">
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<body class="bg-gray-100 dark:bg-gray-900">
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
|
||||
30
lib/SvelteUi/app/dist/index.js
vendored
30
lib/SvelteUi/app/dist/index.js
vendored
File diff suppressed because one or more lines are too long
@ -7,7 +7,7 @@
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#000000">
|
||||
<title>AMS reader</title>
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<body class="bg-gray-100 dark:bg-gray-900">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
||||
1891
lib/SvelteUi/app/package-lock.json
generated
1891
lib/SvelteUi/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -5,23 +5,25 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"local": "vite --config vite.config.local.js",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
||||
"@tailwindcss/forms": "^0.5.2",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"http-proxy-middleware": "^2.0.1",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.1.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"postcss": "^8.4.31",
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"svelte": "^3.49.0",
|
||||
"svelte": "^3.58.0",
|
||||
"svelte-navigator": "^3.2.2",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"tailwindcss": "^3.1.5",
|
||||
"vite": "^3.2.7"
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"svelte-qrcode": "^1.0.0",
|
||||
"tailwindcss": "^3.3.1",
|
||||
"vite": "^4.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssnano": "^5.1.14"
|
||||
"cssnano": "^5.1.15"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
<script>
|
||||
import { Router, Route, navigate } from "svelte-navigator";
|
||||
import { getSysinfo, sysinfoStore, dataStore } from './lib/DataStores.js';
|
||||
@ -21,6 +27,22 @@
|
||||
} else if(sysinfo.fwconsent === 0) {
|
||||
navigate("/consent");
|
||||
}
|
||||
|
||||
if(sysinfo.ui.k === 1) {
|
||||
console.log("dark");
|
||||
document.documentElement.classList.add('dark')
|
||||
} else if (sysinfo.ui.k === 0) {
|
||||
console.log("light");
|
||||
document.documentElement.classList.remove('dark')
|
||||
} else {
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
console.log("dark auto");
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
console.log("light auto");
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
}
|
||||
});
|
||||
getSysinfo();
|
||||
let data = {};
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
}
|
||||
|
||||
.cnt {
|
||||
@apply bg-white m-2 p-2 rounded shadow-lg;
|
||||
@apply m-2 p-2 rounded dark:bg-gray-800 bg-white dark:text-white shadow-lg dark:shadow-xl dark:shadow-gray-900 dark:drop-shadow-md;
|
||||
min-height: 268px;
|
||||
}
|
||||
|
||||
@ -17,27 +17,28 @@
|
||||
}
|
||||
|
||||
.in-pre {
|
||||
@apply flex items-center bg-gray-100 rounded-l-md border border-r-0 border-gray-300 px-3 whitespace-nowrap text-sm
|
||||
@apply flex items-center bg-gray-100 dark:bg-gray-600 rounded-l-md border border-r-0 dark:border-gray-800 border-gray-300 px-3 whitespace-nowrap text-sm
|
||||
}
|
||||
|
||||
.in-post {
|
||||
@apply flex items-center bg-gray-100 rounded-r-md border border-l-0 border-gray-300 px-3 whitespace-nowrap text-sm
|
||||
@apply flex items-center bg-gray-100 dark:bg-gray-600 rounded-r-md border border-l-0 dark:border-gray-800 border-gray-300 px-3 whitespace-nowrap text-sm
|
||||
}
|
||||
|
||||
.in-txt {
|
||||
@apply h-10 shadow-sm border-gray-300 disabled:bg-gray-200
|
||||
@apply h-10 shadow-sm border-gray-300 dark:border-gray-800 disabled:bg-gray-200 dark:disabled:bg-gray-700 disabled:text-white
|
||||
disabled:cursor-not-allowed dark:text-white dark:bg-gray-700 dark:shadow-lg dark:border dark:focus:ring-4 dark:drop-shadow-lg
|
||||
}
|
||||
.in-f {
|
||||
@apply in-txt rounded-l-md
|
||||
@apply in-txt rounded-l-md dark:placeholder:text-gray-400 dark:default:text-white disabled:text-white default:text-white
|
||||
}
|
||||
.in-m {
|
||||
@apply in-txt border-l-0
|
||||
}
|
||||
.in-l {
|
||||
@apply in-txt border-l-0 rounded-r-md
|
||||
@apply in-txt border-l-0 rounded-r-md dark:placeholder-white
|
||||
}
|
||||
.in-s {
|
||||
@apply in-txt rounded-md w-full
|
||||
@apply in-txt rounded-md w-full dark:text-white placeholder:text-gray-400
|
||||
}
|
||||
.tr {
|
||||
@apply text-right
|
||||
@ -82,6 +83,7 @@
|
||||
.pl-unt {
|
||||
font-size: 1.0rem;
|
||||
color: grey;
|
||||
@apply dark:text-gray-200
|
||||
}
|
||||
.pl-sub {
|
||||
padding-top: 10px;
|
||||
@ -90,6 +92,7 @@
|
||||
.pl-snt {
|
||||
font-size: 0.7rem;
|
||||
color: grey;
|
||||
@apply dark:text-gray-200
|
||||
}
|
||||
.pl-lab {
|
||||
font-size: 1.0rem;
|
||||
@ -110,6 +113,7 @@ svg {
|
||||
font-family: Helvetica, Arial;
|
||||
font-size: 0.85em;
|
||||
font-weight: 200;
|
||||
@apply dark:fill-white text-white
|
||||
}
|
||||
|
||||
.tick line {
|
||||
@ -120,6 +124,7 @@ svg {
|
||||
.tick text {
|
||||
fill: #999;
|
||||
text-anchor: start;
|
||||
@apply dark:fill-white
|
||||
}
|
||||
|
||||
.tick.tick-0 line {
|
||||
|
||||
@ -27,6 +27,9 @@
|
||||
<option value={201}>{boardtype(chip, 201)}</option>
|
||||
<option value={202}>{boardtype(chip, 202)}</option>
|
||||
<option value={203}>{boardtype(chip, 203)}</option>
|
||||
<option value={241}>{boardtype(chip, 241)}</option>
|
||||
<option value={242}>{boardtype(chip, 242)}</option>
|
||||
<option value={243}>{boardtype(chip, 243)}</option>
|
||||
<option value={200}>{boardtype(chip, 200)}</option>
|
||||
</optgroup>
|
||||
{/if}
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
import { Link, navigate } from 'svelte-navigator';
|
||||
import SubnetOptions from './SubnetOptions.svelte';
|
||||
import TrashIcon from './TrashIcon.svelte';
|
||||
import QrCode from 'svelte-qrcode';
|
||||
|
||||
|
||||
export let sysinfo = {}
|
||||
@ -35,6 +36,9 @@
|
||||
},{
|
||||
name: 'Peaks',
|
||||
key: 't'
|
||||
},{
|
||||
name: 'Realtime plot',
|
||||
key: 'l'
|
||||
},{
|
||||
name: 'Price',
|
||||
key: 'p'
|
||||
@ -47,6 +51,9 @@
|
||||
},{
|
||||
name: 'Temperature plot',
|
||||
key: 's'
|
||||
},{
|
||||
name: 'Dark mode',
|
||||
key: 'k'
|
||||
}];
|
||||
|
||||
let loading = true;
|
||||
@ -93,11 +100,15 @@
|
||||
a: null,
|
||||
l: { p: null, i: false },
|
||||
r: { r: null, g: null, b: null, i: false },
|
||||
d: { d: null, b: 0 },
|
||||
t: { d: null, a: null },
|
||||
v: { p: null, d: { v: null, g: null }, o: null, m: null, b: null }
|
||||
},
|
||||
h: {
|
||||
t: '', h: '', n: ''
|
||||
},
|
||||
c: {
|
||||
e: false, i: null, s: null, es: null
|
||||
}
|
||||
};
|
||||
configurationStore.subscribe(update => {
|
||||
@ -329,6 +340,14 @@
|
||||
<strong class="text-sm">Meter</strong>
|
||||
<a href="{wiki('Meter-configuration')}" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
<input type="hidden" name="m" value="true"/>
|
||||
<input type="hidden" name="mo" value="1"/>
|
||||
<div class="my-1">
|
||||
Communication<br/>
|
||||
<select name="ma" bind:value={configuration.m.a} class="in-s">
|
||||
<option value={0}>Passive (Push)</option>
|
||||
<option value={9}>Kamstrup (Pull)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
<span class="float-right">Buffer size</span>
|
||||
<span>Serial conf.</span>
|
||||
@ -412,41 +431,50 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">WiFi</strong>
|
||||
<strong class="text-sm">Connection</strong>
|
||||
<a href="{wiki('WiFi-configuration')}" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
<input type="hidden" name="w" value="true"/>
|
||||
<div class="my-1">
|
||||
SSID<br/>
|
||||
<input name="ws" bind:value={configuration.w.s} type="text" class="in-s"/>
|
||||
Connection<br/>
|
||||
<select name="nc" class="in-s" bind:value={configuration.n.c}>
|
||||
<option value={1}>WiFi</option>
|
||||
<option value={2}>Access point</option>
|
||||
{#if sysinfo.if && sysinfo.if.eth}
|
||||
<option value={3}>Ethernet</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
Password<br/>
|
||||
<input name="wp" bind:value={configuration.w.p} type="password" class="in-s"/>
|
||||
</div>
|
||||
<div class="my-1 flex">
|
||||
<div class="w-1/2">
|
||||
Power saving<br/>
|
||||
<select name="wz" bind:value={configuration.w.z} class="in-s">
|
||||
<option value={255}>Default</option>
|
||||
<option value={0}>Off</option>
|
||||
<option value={1}>Minimum</option>
|
||||
<option value={2}>Maximum</option>
|
||||
</select>
|
||||
{#if configuration.n.c == 1 || configuration.n.c == 2}
|
||||
<div class="my-1">
|
||||
SSID<br/>
|
||||
<input name="ws" bind:value={configuration.w.s} type="text" class="in-s"/>
|
||||
</div>
|
||||
<div class="ml-2 w-1/2">
|
||||
Power<br/>
|
||||
<div class="flex">
|
||||
<input name="ww" bind:value={configuration.w.w} type="number" min="0" max="20.5" step="0.5" class="in-f tr w-full"/>
|
||||
<span class="in-post">dBm</span>
|
||||
<div class="my-1">
|
||||
Password<br/>
|
||||
<input name="wp" bind:value={configuration.w.p} type="password" class="in-s"/>
|
||||
</div>
|
||||
<div class="my-1 flex">
|
||||
<div class="w-1/2">
|
||||
Power saving<br/>
|
||||
<select name="wz" bind:value={configuration.w.z} class="in-s">
|
||||
<option value={255}>Default</option>
|
||||
<option value={0}>Off</option>
|
||||
<option value={1}>Minimum</option>
|
||||
<option value={2}>Maximum</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ml-2 w-1/2">
|
||||
Power<br/>
|
||||
<div class="flex">
|
||||
<input name="ww" bind:value={configuration.w.w} type="number" min="0" max="20.5" step="0.5" class="in-f tr w-full"/>
|
||||
<span class="in-post">dBm</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label><input type="checkbox" name="wa" value="true" bind:checked={configuration.w.a} class="rounded mb-1"/> Auto reboot on connection problem</label>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label><input type="checkbox" name="wb" value="true" bind:checked={configuration.w.b} class="rounded mb-1"/> Allow 802.11b legacy rates</label>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label><input type="checkbox" name="wb" value="true" bind:checked={configuration.w.b} class="rounded mb-1"/> Allow 802.11b legacy rates</label>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Network</strong>
|
||||
@ -607,6 +635,36 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if configuration.c.es != null}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Cloud connections</strong>
|
||||
<a href="{wiki('Cloud')}" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
<input type="hidden" name="c" value="true"/>
|
||||
<div class="my-1">
|
||||
<label><input type="checkbox" name="ce" value="true" bind:checked={configuration.c.e} class="rounded mb-1"/> Enable cloud upload</label>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
Client ID<br/>
|
||||
<input name="ci" bind:value={configuration.c.i} type="text" class="in-s" pattern={configuration.c.e ? '[A-Z0-9]{16}' : '.*'} required={configuration.c.e}/>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
Client secret<br/>
|
||||
<input name="cs" bind:value={configuration.c.s} type="text" class="in-s" pattern={configuration.c.e && configuration.c.s != '***' ? '[A-Z0-9]{16}' : '.*'}/>
|
||||
</div>
|
||||
<div class="my-1">
|
||||
<label><input type="checkbox" class="rounded mb-1" name="ces" value="true" bind:checked={configuration.c.es}/> Energy Speedometer</label>
|
||||
{#if configuration.c.es}
|
||||
<div class="pl-5">MAC: {sysinfo.mac}</div>
|
||||
<div class="pl-5">Meter ID: {sysinfo.meter.id ? sysinfo.meter.id : "missing, required"}</div>
|
||||
{#if sysinfo.mac && sysinfo.meter.id}
|
||||
<div class="pl-2">
|
||||
<QrCode value='{'{'}"mac":"{sysinfo.mac}","meter":"{sysinfo.meter.id}"{'}'}'/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if configuration.p.r.startsWith("10YNO") || configuration.p.r == '10Y1001A1001A48H'}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Tariff thresholds</strong>
|
||||
@ -637,15 +695,15 @@
|
||||
<div class="w-1/2">
|
||||
{el.name}<br/>
|
||||
<select name="u{el.key}" bind:value={configuration.u[el.key]} class="in-s">
|
||||
<option value={0}>Hide</option>
|
||||
<option value={1}>Show</option>
|
||||
<option value={2}>Dynamic</option>
|
||||
<option value={0}>Disable</option>
|
||||
<option value={1}>Enable</option>
|
||||
<option value={2}>Auto</option>
|
||||
</select>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{#if sysinfo.board > 20 || sysinfo.chip == 'esp8266'}
|
||||
{#if sysinfo.board > 20 || sysinfo.chip == 'esp8266' || configuration.i.d.d > 0}
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Hardware</strong>
|
||||
<a href="{wiki('GPIO-configuration')}" target="_blank" class="float-right"><HelpIcon/></a>
|
||||
@ -653,21 +711,35 @@
|
||||
<input type="hidden" name="i" value="true"/>
|
||||
<div class="flex flex-wrap">
|
||||
<div class="w-1/3">
|
||||
HAN<label class="ml-2"><input name="ihu" value="true" bind:checked={configuration.i.h.u} type="checkbox" class="rounded mb-1"/> pullup</label><br/>
|
||||
HAN RX<br/>
|
||||
<select name="ihp" bind:value={configuration.i.h.p} class="in-f w-full">
|
||||
<UartSelectOptions chip={sysinfo.chip}/>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
AP button<br/>
|
||||
<input name="ia" bind:value={configuration.i.a} type="number" min="0" max={gpioMax} class="in-m tr w-full"/>
|
||||
HAN TX<br/>
|
||||
<select name="iht" bind:value={configuration.i.h.t} class="in-l w-full">
|
||||
<UartSelectOptions chip={sysinfo.chip}/>
|
||||
</select>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
LED<label class="ml-4"><input name="ili" value="true" bind:checked={configuration.i.l.i} type="checkbox" class="rounded mb-1"/> inv</label><br/>
|
||||
<label class="ml-2"><input name="ihu" value="true" bind:checked={configuration.i.h.u} type="checkbox" class="rounded mb-1"/> pullup</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap">
|
||||
<div class="w-1/3">
|
||||
AP button<br/>
|
||||
<input name="ia" bind:value={configuration.i.a} type="number" min="0" max={gpioMax} class="in-f tr w-full"/>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
LED<br/>
|
||||
<div class="flex">
|
||||
<input name="ilp" bind:value={configuration.i.l.p} type="number" min="0" max={gpioMax} class="in-l tr w-full"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/3">
|
||||
<label class="ml-4"><input name="ili" value="true" bind:checked={configuration.i.l.i} type="checkbox" class="rounded mb-1"/> inverted</label>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
RGB<label class="ml-4"><input name="iri" value="true" bind:checked={configuration.i.r.i} type="checkbox" class="rounded mb-1"/> inverted</label><br/>
|
||||
<div class="flex">
|
||||
@ -676,6 +748,12 @@
|
||||
<input name="irb" bind:value={configuration.i.r.b} type="number" min="0" max={gpioMax} class="in-l tr w-1/3"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div class="my-1 pr-1 w-1/3">
|
||||
LED dis. GPIO
|
||||
<input name="idd" bind:value={configuration.i.d.d} type="number" min="0" max={gpioMax} class="in-s tr"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-1 w-1/3">
|
||||
Temperature<br/>
|
||||
<input name="itd" bind:value={configuration.i.t.d} type="number" min="0" max={gpioMax} class="in-f tr w-full"/>
|
||||
@ -701,6 +779,15 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if configuration.i.d.d > 0}
|
||||
<div class="my-1 w-full">
|
||||
LED behaviour
|
||||
<select name="idb" bind:value={configuration.i.d.b} class="in-s">
|
||||
<option value={0}>Enabled</option>
|
||||
<option value={1}>Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
{#if sysinfo.chip == 'esp8266'}
|
||||
<input type="hidden" name="iv" value="true"/>
|
||||
<div class="my-1 flex flex-wrap">
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
import MonthPlot from './MonthPlot.svelte';
|
||||
import TemperaturePlot from './TemperaturePlot.svelte';
|
||||
import TariffPeakChart from './TariffPeakChart.svelte';
|
||||
import RealtimePlot from './RealtimePlot.svelte';
|
||||
|
||||
export let data = {}
|
||||
export let sysinfo = {}
|
||||
@ -80,6 +81,11 @@
|
||||
<TariffPeakChart />
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.l)}
|
||||
<div class="cnt gwf">
|
||||
<RealtimePlot/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if uiVisibility(sysinfo.ui.p, data.pe && !Number.isNaN(data.p))}
|
||||
<div class="cnt gwf">
|
||||
<PricePlot json={prices} sysinfo={sysinfo}/>
|
||||
|
||||
@ -218,3 +218,15 @@ export async function getGitHubReleases() {
|
||||
releases = (await response.json())
|
||||
gitHubReleaseStore.set(releases);
|
||||
};
|
||||
|
||||
let realtime = {};
|
||||
export async function getRealtime() {
|
||||
const response = await fetchWithTimeout("/realtime.json");
|
||||
realtime = (await response.json())
|
||||
realtimeStore.set(realtime);
|
||||
}
|
||||
|
||||
export const realtimeStore = writable(realtime, (set) => {
|
||||
getRealtime();
|
||||
return function stop() {}
|
||||
});
|
||||
|
||||
@ -41,7 +41,7 @@ export function metertype(mt) {
|
||||
case 10:
|
||||
return "Sagemcom";
|
||||
default:
|
||||
return "";
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,6 +79,12 @@ export function boardtype(c, b) {
|
||||
return "Adafruit HUZZAH32";
|
||||
case 203:
|
||||
return "DevKitC";
|
||||
case 241:
|
||||
return "LilyGO T-ETH-POE";
|
||||
case 242:
|
||||
return "M5 PoESP32";
|
||||
case 243:
|
||||
return "WT32-ETH01";
|
||||
case 200:
|
||||
return "Generic ESP32";
|
||||
case 2:
|
||||
|
||||
@ -5,9 +5,9 @@
|
||||
|
||||
{#if active}
|
||||
<div class="z-50" aria-modal="true">
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-50 flex items-center justify-center">
|
||||
<div class="fixed inset-0 bg-gray-500 dark:bg-gray-900 bg-opacity-50 flex items-center justify-center">
|
||||
{#if message}
|
||||
<div class="bg-white m-2 p-3 rounded-md shadow-lg pb-4 text-gray-700 w-96">{message}</div>
|
||||
<div class="bg-white dark:bg-gray-600 m-2 p-3 rounded-md shadow-lg pb-4 text-gray-700 dark:text-white w-96">{message}</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -26,6 +26,6 @@
|
||||
</script>
|
||||
|
||||
<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg" height="100%">
|
||||
<path d="{ describeArc(150, 150, 115, 210, 510) }" stroke="#eee" fill="none" stroke-width="55"/>
|
||||
<path d="{ describeArc(150, 150, 115, 210, 510) }" stroke="rgba(128, 128, 128, 0.15)" fill="none" stroke-width="55"/>
|
||||
<path d="{ describeArc(150, 150, 115, 210, 210 + (300*pct/100)) }" stroke={color} fill="none" stroke-width="55"/>
|
||||
</svg>
|
||||
|
||||
145
lib/SvelteUi/app/src/lib/RealtimePlot.svelte
Normal file
145
lib/SvelteUi/app/src/lib/RealtimePlot.svelte
Normal file
@ -0,0 +1,145 @@
|
||||
<script>
|
||||
import { dataStore, realtimeStore } from './DataStores.js';
|
||||
|
||||
let realtime;
|
||||
realtimeStore.subscribe(update => {
|
||||
realtime = update;
|
||||
});
|
||||
|
||||
let blankTimeout;
|
||||
let lastUp = 0;
|
||||
|
||||
function setBlank() {
|
||||
realtime.data.unshift(0);
|
||||
realtime.data = realtime.data.slice(0,realtime.size);
|
||||
lastUp += 10;
|
||||
blankTimeout = setTimeout(setBlank, 10000);
|
||||
}
|
||||
|
||||
dataStore.subscribe(update => {
|
||||
if(lastUp > 0) {
|
||||
if(realtime.data && update.u-lastUp >= 10) {
|
||||
if(blankTimeout) clearTimeout(blankTimeout);
|
||||
realtime.data.unshift(update.i-update.e);
|
||||
realtime.data = realtime.data.slice(0,realtime.size);
|
||||
lastUp += 10;
|
||||
blankTimeout = setTimeout(setBlank, 10000);
|
||||
}
|
||||
} else {
|
||||
lastUp = update.u;
|
||||
}
|
||||
});
|
||||
|
||||
let max;
|
||||
let min;
|
||||
|
||||
let width;
|
||||
let height;
|
||||
let heightAvailable;
|
||||
let widthAvailable;
|
||||
let points;
|
||||
let yScale;
|
||||
let xScale;
|
||||
|
||||
let yTicks;
|
||||
let xTicks;
|
||||
let barWidth;
|
||||
let labelSpacing = 12;
|
||||
let unit
|
||||
|
||||
$:{
|
||||
heightAvailable = parseInt(height) - 50;
|
||||
widthAvailable = width - 35;
|
||||
barWidth = widthAvailable / realtime.size;
|
||||
|
||||
min = 0;
|
||||
max = 0;
|
||||
|
||||
if(realtime.data) {
|
||||
for(let p in realtime.data) {
|
||||
let val = realtime.data[p];
|
||||
max = Math.max(Math.ceil(val/1000.0)*1000, max);
|
||||
min = Math.min(Math.ceil(val/1000.0)*1000, min);
|
||||
}
|
||||
|
||||
unit = max > 2500 ? 'kW' : 'W';
|
||||
|
||||
yTicks = [];
|
||||
for(let i = min; i < max; i+= max/5) {
|
||||
yTicks.push({
|
||||
value: i,
|
||||
label: max > 2500 ? (i / 1000).toFixed(1) : i
|
||||
});
|
||||
}
|
||||
|
||||
xTicks = [];
|
||||
for(let i = min; i < realtime.size; i+=realtime.size/labelSpacing) {
|
||||
xTicks.push({
|
||||
value: i,
|
||||
label: '-'+Math.round((realtime.size - i) / 6)+' min'
|
||||
});
|
||||
}
|
||||
|
||||
yScale = function(val) {
|
||||
return Math.ceil(heightAvailable - ((val/max)* heightAvailable) ) - 25;
|
||||
};
|
||||
xScale = function(val) {
|
||||
return 30 + Math.ceil((val/realtime.size) * (widthAvailable-35));
|
||||
};
|
||||
|
||||
let i = realtime.size;
|
||||
points = xScale(realtime.size)+","+yScale(0)+" "+xScale(1)+","+yScale(0);
|
||||
for(let p in realtime.data) {
|
||||
if(i < 0) {
|
||||
break;
|
||||
}
|
||||
let val = realtime.data[p];
|
||||
if(val != 0) {
|
||||
points = xScale(i--)+","+yScale(val)+" "+points;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div class="chart" bind:clientWidth={width} bind:clientHeight={height}>
|
||||
<strong class="text-sm">Realtime ({unit})</strong>
|
||||
{#if yTicks}
|
||||
<svg viewBox="0 0 {widthAvailable} {heightAvailable}" height="100%">
|
||||
|
||||
<!-- y axis -->
|
||||
<g class="axis y-axis">
|
||||
{#each yTicks as tick}
|
||||
{#if !isNaN(yScale(tick.value))}
|
||||
<g class="tick tick-{tick.value}" transform="translate(0, {yScale(tick.value)})">
|
||||
<line x2="100%"></line>
|
||||
<text y="-4">{tick.label}</text>
|
||||
</g>
|
||||
{/if}
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<!-- x axis -->
|
||||
<g class="axis x-axis">
|
||||
{#each xTicks as point, i}
|
||||
{#if !isNaN(xScale(point.value))}
|
||||
{#if i%Math.round(6/barWidth) == 0}
|
||||
<g class="tick" transform="translate({40+xScale(point.value)},{heightAvailable})">
|
||||
<text x="{barWidth/2}" y="-4">{point.label}</text>
|
||||
</g>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<!-- Line -->
|
||||
<polyline
|
||||
opacity="0.9"
|
||||
fill="#7c3aed"
|
||||
stroke="#7c3aed"
|
||||
stroke-width="1"
|
||||
points={points}/>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
@ -6,6 +6,7 @@
|
||||
export let sysinfo = {}
|
||||
|
||||
let staticIp = false;
|
||||
let connectionMode = 1;
|
||||
let loadingOrSaving = false;
|
||||
|
||||
let tries = 0;
|
||||
@ -87,13 +88,25 @@
|
||||
<input type="hidden" name="s" value="true"/>
|
||||
<strong class="text-sm">Setup</strong>
|
||||
<div class="my-3">
|
||||
SSID<br/>
|
||||
<input name="ss" type="text" class="in-s" required/>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
PSK<br/>
|
||||
<input name="sp" type="password" class="in-s" autocomplete="off"/>
|
||||
Connection<br/>
|
||||
<select name="sc" class="in-s" bind:value={connectionMode}>
|
||||
<option value={1}>Connect to WiFi</option>
|
||||
<option value={2}>Standalone access point</option>
|
||||
{#if sysinfo.if && sysinfo.if.eth}
|
||||
<option value={3}>Ethernet</option>
|
||||
{/if}
|
||||
</select>
|
||||
</div>
|
||||
{#if connectionMode == 1 || connectionMode == 2}
|
||||
<div class="my-3">
|
||||
SSID<br/>
|
||||
<input name="ss" type="text" class="in-s" required={connectionMode == 1 || connectionMode == 2}/>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
PSK<br/>
|
||||
<input name="sp" type="password" class="in-s" autocomplete="off" required={connectionMode == 2}/>
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
Hostname
|
||||
<input name="sh" bind:value={sysinfo.hostname} type="text" class="in-s" maxlength="32" pattern="[a-z0-9_-]+" placeholder="Optional, ex.: ams-reader" autocomplete="off"/>
|
||||
|
||||
@ -126,10 +126,10 @@
|
||||
Manufacturer: {metertype(sysinfo.meter.mfg)}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
Model: {sysinfo.meter.model}
|
||||
Model: {sysinfo.meter.model ? sysinfo.meter.model : "unknown"}
|
||||
</div>
|
||||
<div class="my-2">
|
||||
ID: {sysinfo.meter.id}
|
||||
ID: {sysinfo.meter.id ? sysinfo.meter.id : "unknown"}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import "./app.postcss";
|
||||
import App from "./App.svelte";
|
||||
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("app"),
|
||||
});
|
||||
|
||||
@ -8,6 +8,8 @@ const config = {
|
||||
plugins: [
|
||||
require('@tailwindcss/forms')
|
||||
],
|
||||
|
||||
darkMode: 'class'
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
static const char HEADER_CACHE_CONTROL[] PROGMEM = "Cache-Control";
|
||||
static const char HEADER_CONTENT_ENCODING[] PROGMEM = "Content-Encoding";
|
||||
static const char HEADER_PRAGMA[] PROGMEM = "Pragma";
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _AMSWEBSERVER_h
|
||||
#define _AMSWEBSERVER_h
|
||||
|
||||
#include "Arduino.h"
|
||||
#include <MQTT.h>
|
||||
#include "AmsMqttHandler.h"
|
||||
#include "AmsConfiguration.h"
|
||||
#include "HwTools.h"
|
||||
#include "AmsData.h"
|
||||
@ -12,6 +18,7 @@
|
||||
#include "Uptime.h"
|
||||
#include "RemoteDebug.h"
|
||||
#include "EntsoeApi.h"
|
||||
#include "RealtimePlot.h"
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
@ -32,29 +39,34 @@
|
||||
class AmsWebServer {
|
||||
public:
|
||||
AmsWebServer(uint8_t* buf, RemoteDebug* Debug, HwTools* hw);
|
||||
void setup(AmsConfiguration*, GpioConfig*, MeterConfig*, AmsData*, AmsDataStorage*, EnergyAccounting*);
|
||||
void setup(AmsConfiguration*, GpioConfig*, AmsData*, AmsDataStorage*, EnergyAccounting*, RealtimePlot*);
|
||||
void loop();
|
||||
void setMqtt(MQTTClient* mqtt);
|
||||
void setTimezone(Timezone* tz);
|
||||
void setMqttEnabled(bool);
|
||||
void setEntsoeApi(EntsoeApi* eapi);
|
||||
void setPriceSettings(String region, String currency);
|
||||
void setMeterConfig(uint8_t distributionSystem, uint16_t mainFuse, uint16_t productionCapacity);
|
||||
void setMqttHandler(AmsMqttHandler* mqttHandler);
|
||||
|
||||
private:
|
||||
RemoteDebug* debugger;
|
||||
bool mqttEnabled = false;
|
||||
int maxPwr = 0;
|
||||
uint8_t distributionSystem = 0;
|
||||
uint16_t mainFuse = 0, productionCapacity = 0;
|
||||
|
||||
HwTools* hw;
|
||||
Timezone* tz;
|
||||
EntsoeApi* eapi = NULL;
|
||||
AmsConfiguration* config;
|
||||
GpioConfig* gpioConfig;
|
||||
MeterConfig* meterConfig;
|
||||
WebConfig webConfig;
|
||||
AmsData* meterState;
|
||||
AmsDataStorage* ds;
|
||||
EnergyAccounting* ea = NULL;
|
||||
MQTTClient* mqtt = NULL;
|
||||
RealtimePlot* rtp = NULL;
|
||||
AmsMqttHandler* mqttHandler = NULL;
|
||||
bool uploading = false;
|
||||
File file;
|
||||
bool performRestart = false;
|
||||
@ -92,6 +104,7 @@ private:
|
||||
void energyPriceJson();
|
||||
void temperatureJson();
|
||||
void tariffJson();
|
||||
void realtimeJson();
|
||||
|
||||
void configurationJson();
|
||||
void handleSave();
|
||||
|
||||
6
lib/SvelteUi/json/conf_cloud.json
Normal file
6
lib/SvelteUi/json/conf_cloud.json
Normal file
@ -0,0 +1,6 @@
|
||||
"c": {
|
||||
"e" : %s,
|
||||
"i" : "%s",
|
||||
"s" : "%s",
|
||||
"es": %s
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
"i": {
|
||||
"h": {
|
||||
"p": %s,
|
||||
"u": %s
|
||||
"u": %s,
|
||||
"t": %s
|
||||
},
|
||||
"a": %s,
|
||||
"l": {
|
||||
@ -14,6 +15,10 @@
|
||||
"b": %s,
|
||||
"i": %s
|
||||
},
|
||||
"d": {
|
||||
"d": %s,
|
||||
"b": %d
|
||||
},
|
||||
"t": {
|
||||
"d": %s,
|
||||
"a": %s
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
"t" : "%s",
|
||||
"h" : "%s",
|
||||
"n" : "%s"
|
||||
}
|
||||
},
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
"m": {
|
||||
"o": %d,
|
||||
"a": %d,
|
||||
"b": %d,
|
||||
"p": %d,
|
||||
"i": %s,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
"n": {
|
||||
"c": %d,
|
||||
"m": "%s",
|
||||
"i": "%s",
|
||||
"s": "%s",
|
||||
|
||||
@ -9,5 +9,7 @@
|
||||
"p": %d,
|
||||
"d": %d,
|
||||
"m": %d,
|
||||
"s": %d
|
||||
"s": %d,
|
||||
"l": %d,
|
||||
"k": %d
|
||||
},
|
||||
@ -3,6 +3,5 @@
|
||||
"p": "%s",
|
||||
"w": %.1f,
|
||||
"z": %d,
|
||||
"a": %s,
|
||||
"b": %s
|
||||
},
|
||||
|
||||
@ -19,6 +19,9 @@
|
||||
"dns1": "%s",
|
||||
"dns2": "%s"
|
||||
},
|
||||
"if": {
|
||||
"eth": %s
|
||||
},
|
||||
"meter": {
|
||||
"mfg": %d,
|
||||
"model": "%s",
|
||||
@ -35,7 +38,9 @@
|
||||
"p": %d,
|
||||
"d": %d,
|
||||
"m": %d,
|
||||
"s": %d
|
||||
"s": %d,
|
||||
"l": %d,
|
||||
"k": %d
|
||||
},
|
||||
"security": %d,
|
||||
"boot_reason": %d,
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "AmsWebServer.h"
|
||||
#include "AmsWebHeaders.h"
|
||||
#include "FirmwareVersion.h"
|
||||
@ -30,12 +36,13 @@
|
||||
#include "html/conf_domoticz_json.h"
|
||||
#include "html/conf_ha_json.h"
|
||||
#include "html/conf_ui_json.h"
|
||||
#include "html/conf_cloud_json.h"
|
||||
#include "html/firmware_html.h"
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_clk.h>
|
||||
#include <esp32/clk.h>
|
||||
#endif
|
||||
|
||||
|
||||
@ -55,13 +62,13 @@ AmsWebServer::AmsWebServer(uint8_t* buf, RemoteDebug* Debug, HwTools* hw) {
|
||||
this->buf = (char*) buf;
|
||||
}
|
||||
|
||||
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, AmsDataStorage* ds, EnergyAccounting* ea) {
|
||||
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, AmsData* meterState, AmsDataStorage* ds, EnergyAccounting* ea, RealtimePlot* rtp) {
|
||||
this->config = config;
|
||||
this->gpioConfig = gpioConfig;
|
||||
this->meterConfig = meterConfig;
|
||||
this->meterState = meterState;
|
||||
this->ds = ds;
|
||||
this->ea = ea;
|
||||
this->rtp = rtp;
|
||||
|
||||
server.on(F("/"), HTTP_GET, std::bind(&AmsWebServer::indexHtml, this));
|
||||
snprintf_P(buf, 32, PSTR("/index-%s.js"), FirmwareVersion::VersionString);
|
||||
@ -87,6 +94,7 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
|
||||
server.on(F("/energyprice.json"), HTTP_GET, std::bind(&AmsWebServer::energyPriceJson, this));
|
||||
server.on(F("/temperature.json"), HTTP_GET, std::bind(&AmsWebServer::temperatureJson, this));
|
||||
server.on(F("/tariff.json"), HTTP_GET, std::bind(&AmsWebServer::tariffJson, this));
|
||||
server.on(F("/realtime.json"), HTTP_GET, std::bind(&AmsWebServer::realtimeJson, this));
|
||||
|
||||
server.on(F("/configuration.json"), HTTP_GET, std::bind(&AmsWebServer::configurationJson, this));
|
||||
server.on(F("/save"), HTTP_POST, std::bind(&AmsWebServer::handleSave, this));
|
||||
@ -124,11 +132,6 @@ void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, Meter
|
||||
mqttEnabled = strlen(mqttConfig.host) > 0;
|
||||
}
|
||||
|
||||
|
||||
void AmsWebServer::setMqtt(MQTTClient* mqtt) {
|
||||
this->mqtt = mqtt;
|
||||
}
|
||||
|
||||
void AmsWebServer::setTimezone(Timezone* tz) {
|
||||
this->tz = tz;
|
||||
}
|
||||
@ -136,28 +139,38 @@ void AmsWebServer::setTimezone(Timezone* tz) {
|
||||
void AmsWebServer::setMqttEnabled(bool enabled) {
|
||||
mqttEnabled = enabled;
|
||||
}
|
||||
void AmsWebServer::setMqttHandler(AmsMqttHandler* mqttHandler) {
|
||||
this->mqttHandler = mqttHandler;
|
||||
}
|
||||
|
||||
void AmsWebServer::setEntsoeApi(EntsoeApi* eapi) {
|
||||
this->eapi = eapi;
|
||||
}
|
||||
|
||||
void AmsWebServer::setMeterConfig(uint8_t distributionSystem, uint16_t mainFuse, uint16_t productionCapacity) {
|
||||
maxPwr = 0;
|
||||
this->distributionSystem = distributionSystem;
|
||||
this->mainFuse = mainFuse;
|
||||
this->productionCapacity = productionCapacity;
|
||||
}
|
||||
|
||||
void AmsWebServer::loop() {
|
||||
server.handleClient();
|
||||
|
||||
if(maxPwr == 0 && meterState->getListType() > 1 && meterConfig->mainFuse > 0 && meterConfig->distributionSystem > 0) {
|
||||
int voltage = meterConfig->distributionSystem == 2 ? 400 : 230;
|
||||
if(maxPwr == 0 && meterState->getListType() > 1 && mainFuse > 0 && distributionSystem > 0) {
|
||||
int voltage = distributionSystem == 2 ? 400 : 230;
|
||||
if(meterState->isThreePhase()) {
|
||||
maxPwr = meterConfig->mainFuse * sqrt(3) * voltage;
|
||||
maxPwr = mainFuse * sqrt(3) * voltage;
|
||||
} else if(meterState->isTwoPhase()) {
|
||||
maxPwr = meterConfig->mainFuse * voltage;
|
||||
maxPwr = mainFuse * voltage;
|
||||
} else {
|
||||
maxPwr = meterConfig->mainFuse * 230;
|
||||
maxPwr = mainFuse * 230;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AmsWebServer::checkSecurity(byte level, bool send401) {
|
||||
bool access = WiFi.getMode() == WIFI_AP || webConfig.security < level;
|
||||
bool access = WiFi.getMode() == WIFI_AP || WiFi.getMode() == WIFI_AP_STA || webConfig.security < level;
|
||||
if(!access && webConfig.security >= level && server.hasHeader(F("Authorization"))) {
|
||||
String expectedAuth = String(webConfig.username) + ":" + String(webConfig.password);
|
||||
|
||||
@ -218,9 +231,9 @@ void AmsWebServer::sysinfoJson() {
|
||||
|
||||
String hostname;
|
||||
if(sys.userConfigured) {
|
||||
WiFiConfig wifiConfig;
|
||||
config->getWiFiConfig(wifiConfig);
|
||||
hostname = String(wifiConfig.hostname);
|
||||
NetworkConfig networkConfig;
|
||||
config->getNetworkConfig(networkConfig);
|
||||
hostname = String(networkConfig.hostname);
|
||||
} else {
|
||||
hostname = "ams-"+chipIdStr;
|
||||
}
|
||||
@ -303,6 +316,7 @@ void AmsWebServer::sysinfoJson() {
|
||||
dns1 != INADDR_NONE ? dns1.toString().c_str() : "",
|
||||
dns2 != INADDR_NONE ? dns2.toString().c_str() : "",
|
||||
#endif
|
||||
sys.boardType > 240 && sys.boardType < 250 ? "true" : "false",
|
||||
meterState->getMeterType(),
|
||||
meterModel.c_str(),
|
||||
meterId.c_str(),
|
||||
@ -317,6 +331,8 @@ void AmsWebServer::sysinfoJson() {
|
||||
ui.showDayPlot,
|
||||
ui.showMonthPlot,
|
||||
ui.showTemperaturePlot,
|
||||
ui.showRealtimePlot,
|
||||
ui.darkMode,
|
||||
webConfig.security,
|
||||
#if defined(ESP32)
|
||||
rtc_get_reset_reason(0),
|
||||
@ -396,7 +412,7 @@ void AmsWebServer::dataJson() {
|
||||
uint8_t hanStatus;
|
||||
if(meterState->getLastError() != 0) {
|
||||
hanStatus = 3;
|
||||
} else if((meterConfig->baud == 0 || meterState->getLastUpdateMillis() == 0) && millis < 30000) {
|
||||
} else if(meterState->getLastUpdateMillis() == 0 && millis < 30000) {
|
||||
hanStatus = 0;
|
||||
} else if(millis - meterState->getLastUpdateMillis() < 15000) {
|
||||
hanStatus = 1;
|
||||
@ -418,9 +434,9 @@ void AmsWebServer::dataJson() {
|
||||
uint8_t mqttStatus;
|
||||
if(!mqttEnabled) {
|
||||
mqttStatus = 0;
|
||||
} else if(mqtt != NULL && mqtt->connected()) {
|
||||
} else if(mqttHandler != NULL && mqttHandler->connected()) {
|
||||
mqttStatus = 1;
|
||||
} else if(mqtt != NULL && mqtt->lastError() == 0) {
|
||||
} else if(mqttHandler != NULL && mqttHandler->lastError() == 0) {
|
||||
mqttStatus = 2;
|
||||
} else {
|
||||
mqttStatus = 3;
|
||||
@ -438,8 +454,8 @@ void AmsWebServer::dataJson() {
|
||||
|
||||
snprintf_P(buf, BufferSize, DATA_JSON,
|
||||
maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr,
|
||||
meterConfig->productionCapacity,
|
||||
meterConfig->mainFuse == 0 ? 40 : meterConfig->mainFuse,
|
||||
productionCapacity,
|
||||
mainFuse == 0 ? 40 : mainFuse,
|
||||
meterState->getActiveImportPower(),
|
||||
meterState->getActiveExportPower(),
|
||||
meterState->getReactiveImportPower(),
|
||||
@ -467,10 +483,10 @@ void AmsWebServer::dataJson() {
|
||||
hanStatus,
|
||||
wifiStatus,
|
||||
mqttStatus,
|
||||
mqtt == NULL ? 0 : (int) mqtt->lastError(),
|
||||
mqttHandler == NULL ? 0 : (int) mqttHandler->lastError(),
|
||||
price == ENTSOE_NO_VALUE ? "null" : String(price, 2).c_str(),
|
||||
meterState->getMeterType(),
|
||||
meterConfig->distributionSystem,
|
||||
distributionSystem,
|
||||
ea->getMonthMax(),
|
||||
peaks.c_str(),
|
||||
ea->getCurrentThreshold(),
|
||||
@ -728,13 +744,12 @@ void AmsWebServer::temperatureJson() {
|
||||
TempSensorData* data = hw->getTempSensorData(i);
|
||||
if(data == NULL) continue;
|
||||
|
||||
TempSensorConfig* conf = config->getTempSensorConfig(data->address);
|
||||
char* pos = buf+strlen(buf);
|
||||
snprintf_P(pos, 72, TEMPSENSOR_JSON,
|
||||
i,
|
||||
toHex(data->address, 8).c_str(),
|
||||
conf == NULL ? "" : String(conf->name).substring(0,16).c_str(),
|
||||
conf == NULL || conf->common ? 1 : 0,
|
||||
"",
|
||||
1,
|
||||
data->lastRead
|
||||
);
|
||||
yield();
|
||||
@ -791,14 +806,19 @@ void AmsWebServer::configurationJson() {
|
||||
if(!checkSecurity(1))
|
||||
return;
|
||||
|
||||
MeterConfig meterConfig;
|
||||
config->getMeterConfig(meterConfig);
|
||||
|
||||
SystemConfig sysConfig;
|
||||
config->getSystemConfig(sysConfig);
|
||||
NtpConfig ntpConfig;
|
||||
config->getNtpConfig(ntpConfig);
|
||||
WiFiConfig wifiConfig;
|
||||
config->getWiFiConfig(wifiConfig);
|
||||
NetworkConfig networkConfig;
|
||||
config->getNetworkConfig(networkConfig);
|
||||
|
||||
bool encen = false;
|
||||
for(uint8_t i = 0; i < 16; i++) {
|
||||
if(meterConfig->encryptionKey[i] > 0) {
|
||||
if(meterConfig.encryptionKey[i] > 0) {
|
||||
encen = true;
|
||||
}
|
||||
}
|
||||
@ -817,6 +837,8 @@ void AmsWebServer::configurationJson() {
|
||||
config->getUiConfig(ui);
|
||||
HomeAssistantConfig haconf;
|
||||
config->getHomeAssistantConfig(haconf);
|
||||
CloudConfig cloud;
|
||||
config->getCloudConfig(cloud);
|
||||
|
||||
bool qsc = false;
|
||||
bool qsr = false;
|
||||
@ -839,28 +861,30 @@ void AmsWebServer::configurationJson() {
|
||||
server.sendContent_P(PSTR("\","));
|
||||
snprintf_P(buf, BufferSize, CONF_GENERAL_JSON,
|
||||
ntpConfig.timezone,
|
||||
wifiConfig.hostname,
|
||||
networkConfig.hostname,
|
||||
webConfig.security,
|
||||
webConfig.username,
|
||||
strlen(webConfig.password) > 0 ? "***" : ""
|
||||
);
|
||||
server.sendContent(buf);
|
||||
snprintf_P(buf, BufferSize, CONF_METER_JSON,
|
||||
meterConfig->baud,
|
||||
meterConfig->parity,
|
||||
meterConfig->invert ? "true" : "false",
|
||||
meterConfig->bufferSize * 64,
|
||||
meterConfig->distributionSystem,
|
||||
meterConfig->mainFuse,
|
||||
meterConfig->productionCapacity,
|
||||
meterConfig.source,
|
||||
meterConfig.parser,
|
||||
meterConfig.baud,
|
||||
meterConfig.parity,
|
||||
meterConfig.invert ? "true" : "false",
|
||||
meterConfig.bufferSize * 64,
|
||||
meterConfig.distributionSystem,
|
||||
meterConfig.mainFuse,
|
||||
meterConfig.productionCapacity,
|
||||
encen ? "true" : "false",
|
||||
toHex(meterConfig->encryptionKey, 16).c_str(),
|
||||
toHex(meterConfig->authenticationKey, 16).c_str(),
|
||||
meterConfig->wattageMultiplier > 1 || meterConfig->voltageMultiplier > 1 || meterConfig->amperageMultiplier > 1 || meterConfig->accumulatedMultiplier > 1 ? "true" : "false",
|
||||
meterConfig->wattageMultiplier / 1000.0,
|
||||
meterConfig->voltageMultiplier / 1000.0,
|
||||
meterConfig->amperageMultiplier / 1000.0,
|
||||
meterConfig->accumulatedMultiplier / 1000.0
|
||||
toHex(meterConfig.encryptionKey, 16).c_str(),
|
||||
toHex(meterConfig.authenticationKey, 16).c_str(),
|
||||
meterConfig.wattageMultiplier > 1 || meterConfig.voltageMultiplier > 1 || meterConfig.amperageMultiplier > 1 || meterConfig.accumulatedMultiplier > 1 ? "true" : "false",
|
||||
meterConfig.wattageMultiplier / 1000.0,
|
||||
meterConfig.voltageMultiplier / 1000.0,
|
||||
meterConfig.amperageMultiplier / 1000.0,
|
||||
meterConfig.accumulatedMultiplier / 1000.0
|
||||
);
|
||||
server.sendContent(buf);
|
||||
|
||||
@ -879,22 +903,22 @@ void AmsWebServer::configurationJson() {
|
||||
);
|
||||
server.sendContent(buf);
|
||||
snprintf_P(buf, BufferSize, CONF_WIFI_JSON,
|
||||
wifiConfig.ssid,
|
||||
strlen(wifiConfig.psk) > 0 ? "***" : "",
|
||||
wifiConfig.power / 10.0,
|
||||
wifiConfig.sleep,
|
||||
wifiConfig.autoreboot ? "true" : "false",
|
||||
wifiConfig.use11b ? "true" : "false"
|
||||
networkConfig.ssid,
|
||||
strlen(networkConfig.psk) > 0 ? "***" : "",
|
||||
networkConfig.power / 10.0,
|
||||
networkConfig.sleep,
|
||||
networkConfig.use11b ? "true" : "false"
|
||||
);
|
||||
server.sendContent(buf);
|
||||
snprintf_P(buf, BufferSize, CONF_NET_JSON,
|
||||
strlen(wifiConfig.ip) > 0 ? "static" : "dhcp",
|
||||
wifiConfig.ip,
|
||||
wifiConfig.subnet,
|
||||
wifiConfig.gateway,
|
||||
wifiConfig.dns1,
|
||||
wifiConfig.dns2,
|
||||
wifiConfig.mdns ? "true" : "false",
|
||||
networkConfig.mode,
|
||||
strlen(networkConfig.ip) > 0 ? "static" : "dhcp",
|
||||
networkConfig.ip,
|
||||
networkConfig.subnet,
|
||||
networkConfig.gateway,
|
||||
networkConfig.dns1,
|
||||
networkConfig.dns2,
|
||||
networkConfig.mdns ? "true" : "false",
|
||||
ntpConfig.server,
|
||||
ntpConfig.dhcp ? "true" : "false"
|
||||
);
|
||||
@ -929,8 +953,9 @@ void AmsWebServer::configurationJson() {
|
||||
);
|
||||
server.sendContent(buf);
|
||||
snprintf_P(buf, BufferSize, CONF_GPIO_JSON,
|
||||
gpioConfig->hanPin == 0xff ? "null" : String(gpioConfig->hanPin, 10).c_str(),
|
||||
gpioConfig->hanPinPullup ? "true" : "false",
|
||||
meterConfig.rxPin == 0xff ? "null" : String(meterConfig.rxPin, 10).c_str(),
|
||||
meterConfig.rxPinPullup ? "true" : "false",
|
||||
meterConfig.txPin == 0xff ? "null" : String(meterConfig.txPin, 10).c_str(),
|
||||
gpioConfig->apPin == 0xff ? "null" : String(gpioConfig->apPin, 10).c_str(),
|
||||
gpioConfig->ledPin == 0xff ? "null" : String(gpioConfig->ledPin, 10).c_str(),
|
||||
gpioConfig->ledInverted ? "true" : "false",
|
||||
@ -938,6 +963,8 @@ void AmsWebServer::configurationJson() {
|
||||
gpioConfig->ledPinGreen == 0xff ? "null" : String(gpioConfig->ledPinGreen, 10).c_str(),
|
||||
gpioConfig->ledPinBlue == 0xff ? "null" : String(gpioConfig->ledPinBlue, 10).c_str(),
|
||||
gpioConfig->ledRgbInverted ? "true" : "false",
|
||||
gpioConfig->ledDisablePin == 0xff ? "null" : String(gpioConfig->ledDisablePin, 10).c_str(),
|
||||
gpioConfig->ledBehaviour,
|
||||
gpioConfig->tempSensorPin == 0xff ? "null" : String(gpioConfig->tempSensorPin, 10).c_str(),
|
||||
gpioConfig->tempAnalogSensorPin == 0xff ? "null" : String(gpioConfig->tempAnalogSensorPin, 10).c_str(),
|
||||
gpioConfig->vccPin == 0xff ? "null" : String(gpioConfig->vccPin, 10).c_str(),
|
||||
@ -959,7 +986,9 @@ void AmsWebServer::configurationJson() {
|
||||
ui.showPricePlot,
|
||||
ui.showDayPlot,
|
||||
ui.showMonthPlot,
|
||||
ui.showTemperaturePlot
|
||||
ui.showTemperaturePlot,
|
||||
ui.showRealtimePlot,
|
||||
ui.darkMode
|
||||
);
|
||||
server.sendContent(buf);
|
||||
snprintf_P(buf, BufferSize, CONF_DOMOTICZ_JSON,
|
||||
@ -976,6 +1005,17 @@ void AmsWebServer::configurationJson() {
|
||||
haconf.discoveryNameTag
|
||||
);
|
||||
server.sendContent(buf);
|
||||
snprintf_P(buf, BufferSize, CONF_CLOUD_JSON,
|
||||
cloud.enabled ? "true" : "false",
|
||||
cloud.clientId,
|
||||
strlen(cloud.clientSecret) > 0 ? "***" : "",
|
||||
#if defined(ENERGY_SPEEDOMETER_PASS)
|
||||
sysConfig.energyspeedometer == 7 ? "true" : "false"
|
||||
#else
|
||||
"null"
|
||||
#endif
|
||||
);
|
||||
server.sendContent(buf);
|
||||
server.sendContent_P(PSTR("}"));
|
||||
}
|
||||
|
||||
@ -992,13 +1032,16 @@ void AmsWebServer::handleSave() {
|
||||
config->clear();
|
||||
}
|
||||
|
||||
MeterConfig meterConfig;
|
||||
config->getMeterConfig(meterConfig);
|
||||
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
switch(boardType) {
|
||||
case 5: // Pow-K+
|
||||
case 7: // Pow-U+
|
||||
case 6: // Pow-P1
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 16;
|
||||
meterConfig.rxPin = 16;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPinRed = 13;
|
||||
gpioConfig->ledPinGreen = 14;
|
||||
@ -1006,20 +1049,21 @@ void AmsWebServer::handleSave() {
|
||||
gpioConfig->vccPin = 10;
|
||||
gpioConfig->vccResistorGnd = 22;
|
||||
gpioConfig->vccResistorVcc = 33;
|
||||
gpioConfig->ledDisablePin = 6;
|
||||
break;
|
||||
case 51: // Wemos S2 mini
|
||||
gpioConfig->ledPin = 15;
|
||||
gpioConfig->ledInverted = false;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->hanPin = hanPin > 0 ? hanPin : 18;
|
||||
if(gpioConfig->hanPin != 18) {
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 18;
|
||||
if(meterConfig.rxPin != 18) {
|
||||
gpioConfig->vccPin = 18;
|
||||
gpioConfig->vccResistorGnd = 45;
|
||||
gpioConfig->vccResistorVcc = 10;
|
||||
}
|
||||
break;
|
||||
case 50: // Generic ESP32-S2
|
||||
gpioConfig->hanPin = hanPin > 0 ? hanPin : 18;
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 18;
|
||||
break;
|
||||
default:
|
||||
success = false;
|
||||
@ -1027,8 +1071,8 @@ void AmsWebServer::handleSave() {
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
switch(boardType) {
|
||||
case 8: // dbeinder: HAN mosquito
|
||||
gpioConfig->hanPin = 7;
|
||||
gpioConfig->hanPinPullup = false;
|
||||
meterConfig.rxPin = 7;
|
||||
meterConfig.rxPinPullup = false;
|
||||
gpioConfig->apPin = 9;
|
||||
gpioConfig->ledRgbInverted = true;
|
||||
gpioConfig->ledPinRed = 5;
|
||||
@ -1038,15 +1082,27 @@ void AmsWebServer::handleSave() {
|
||||
case 71: // ESP32-C3-DevKitM-1
|
||||
gpioConfig->apPin = 9;
|
||||
case 70: // Generic ESP32-C3
|
||||
gpioConfig->hanPin = hanPin > 0 ? hanPin : 7;
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 7;
|
||||
break;
|
||||
default:
|
||||
success = false;
|
||||
}
|
||||
#elif defined(ESP32)
|
||||
switch(boardType) {
|
||||
case 241: // LilyGO T-ETH-POE
|
||||
gpioConfig->apPin = 0;
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 39;
|
||||
gpioConfig->ledPin = 2;
|
||||
gpioConfig->ledInverted = true;
|
||||
break;
|
||||
case 242: // M5 PoESP32
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 16;
|
||||
break;
|
||||
case 243: // WT32-ETH01
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 39;
|
||||
break;
|
||||
case 201: // D32
|
||||
gpioConfig->hanPin = hanPin > 0 ? hanPin : 16;
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 16;
|
||||
gpioConfig->apPin = 4;
|
||||
gpioConfig->ledPin = 5;
|
||||
gpioConfig->ledInverted = true;
|
||||
@ -1054,7 +1110,7 @@ void AmsWebServer::handleSave() {
|
||||
case 202: // Feather
|
||||
case 203: // DevKitC
|
||||
case 200: // ESP32
|
||||
gpioConfig->hanPin = hanPin > 0 ? hanPin : 16;
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 16;
|
||||
gpioConfig->ledPin = 2;
|
||||
gpioConfig->ledInverted = false;
|
||||
break;
|
||||
@ -1066,7 +1122,7 @@ void AmsWebServer::handleSave() {
|
||||
case 2: // spenceme
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->vccBootLimit = 32;
|
||||
gpioConfig->hanPin = 3;
|
||||
meterConfig.rxPin = 3;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
gpioConfig->ledInverted = true;
|
||||
@ -1074,7 +1130,7 @@ void AmsWebServer::handleSave() {
|
||||
break;
|
||||
case 0: // roarfred
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 3;
|
||||
meterConfig.rxPin = 3;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
gpioConfig->ledInverted = true;
|
||||
@ -1084,7 +1140,7 @@ void AmsWebServer::handleSave() {
|
||||
case 3: // Pow-K UART0
|
||||
case 4: // Pow-U UART0
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 3;
|
||||
meterConfig.rxPin = 3;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
gpioConfig->ledInverted = true;
|
||||
@ -1095,7 +1151,7 @@ void AmsWebServer::handleSave() {
|
||||
case 5: // Pow-K GPIO12
|
||||
case 7: // Pow-U GPIO12
|
||||
config->clearGpio(*gpioConfig);
|
||||
gpioConfig->hanPin = 12;
|
||||
meterConfig.rxPin = 12;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->ledPin = 2;
|
||||
gpioConfig->ledInverted = true;
|
||||
@ -1104,14 +1160,14 @@ void AmsWebServer::handleSave() {
|
||||
gpioConfig->ledRgbInverted = true;
|
||||
break;
|
||||
case 101: // D1
|
||||
gpioConfig->hanPin = hanPin > 0 ? hanPin : 5;
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 5;
|
||||
gpioConfig->apPin = 4;
|
||||
gpioConfig->ledPin = 2;
|
||||
gpioConfig->ledInverted = true;
|
||||
gpioConfig->vccMultiplier = 1100;
|
||||
break;
|
||||
case 100: // ESP8266
|
||||
gpioConfig->hanPin = hanPin > 0 ? hanPin : 3;
|
||||
meterConfig.rxPin = hanPin > 0 ? hanPin : 3;
|
||||
gpioConfig->ledPin = 2;
|
||||
gpioConfig->ledInverted = true;
|
||||
break;
|
||||
@ -1130,69 +1186,72 @@ void AmsWebServer::handleSave() {
|
||||
}
|
||||
}
|
||||
|
||||
if(server.hasArg(F("s")) && server.arg(F("s")) == F("true") && server.hasArg(F("ss")) && !server.arg(F("ss")).isEmpty()) {
|
||||
if(server.hasArg(F("s")) && server.arg(F("s")) == F("true")) {
|
||||
SystemConfig sys;
|
||||
config->getSystemConfig(sys);
|
||||
MeterConfig meterConfig;
|
||||
config->getMeterConfig(meterConfig);
|
||||
|
||||
config->clear();
|
||||
|
||||
WiFiConfig wifi;
|
||||
config->clearWifi(wifi);
|
||||
NetworkConfig network;
|
||||
config->clearNetworkConfig(network);
|
||||
|
||||
strcpy(wifi.ssid, server.arg(F("ss")).c_str());
|
||||
strcpy(network.ssid, server.arg(F("ss")).c_str());
|
||||
|
||||
String psk = server.arg(F("sp"));
|
||||
if(!psk.equals("***")) {
|
||||
strcpy(wifi.psk, psk.c_str());
|
||||
strcpy(network.psk, psk.c_str());
|
||||
}
|
||||
network.mode = server.arg(F("sc")).toInt();
|
||||
if(network.mode > 3 || network.mode == 0) network.mode = 1; // WiFi Client
|
||||
|
||||
if(server.hasArg(F("sm")) && server.arg(F("sm")) == "static") {
|
||||
strcpy(wifi.ip, server.arg(F("si")).c_str());
|
||||
strcpy(wifi.gateway, server.arg(F("sg")).c_str());
|
||||
strcpy(wifi.subnet, server.arg(F("su")).c_str());
|
||||
strcpy(wifi.dns1, server.arg(F("sd")).c_str());
|
||||
}
|
||||
if(network.mode == 3 || strlen(network.ssid) > 0) {
|
||||
if(server.hasArg(F("sm")) && server.arg(F("sm")) == "static") {
|
||||
strcpy(network.ip, server.arg(F("si")).c_str());
|
||||
strcpy(network.gateway, server.arg(F("sg")).c_str());
|
||||
strcpy(network.subnet, server.arg(F("su")).c_str());
|
||||
strcpy(network.dns1, server.arg(F("sd")).c_str());
|
||||
}
|
||||
|
||||
if(server.hasArg(F("sh")) && !server.arg(F("sh")).isEmpty()) {
|
||||
strcpy(wifi.hostname, server.arg(F("sh")).c_str());
|
||||
wifi.mdns = true;
|
||||
} else {
|
||||
wifi.mdns = false;
|
||||
}
|
||||
|
||||
switch(sys.boardType) {
|
||||
case 6: // Pow-P1
|
||||
meterConfig->baud = 115200;
|
||||
meterConfig->parity = 3; // 8N1
|
||||
break;
|
||||
case 3: // Pow-K UART0
|
||||
case 5: // Pow-K+
|
||||
meterConfig->baud = 2400;
|
||||
meterConfig->parity = 3; // 8N1
|
||||
case 2: // spenceme
|
||||
case 50: // Generic ESP32-S2
|
||||
case 51: // Wemos S2 mini
|
||||
case 70: // Generic ESP32-C3
|
||||
case 71: // ESP32-C3-DevKitM-1
|
||||
wifi.sleep = 1; // Modem sleep
|
||||
break;
|
||||
case 4: // Pow-U UART0
|
||||
case 7: // Pow-U+
|
||||
wifi.sleep = 2; // Light sleep
|
||||
break;
|
||||
case 8: // dbeinder: HAN mosquito
|
||||
wifi.sleep = 1; // Modem sleep
|
||||
wifi.use11b = 0;
|
||||
break;
|
||||
}
|
||||
config->setWiFiConfig(wifi);
|
||||
config->setMeterConfig(*meterConfig);
|
||||
|
||||
sys.userConfigured = success;
|
||||
sys.dataCollectionConsent = 0;
|
||||
config->setSystemConfig(sys);
|
||||
if(server.hasArg(F("sh")) && !server.arg(F("sh")).isEmpty()) {
|
||||
strcpy(network.hostname, server.arg(F("sh")).c_str());
|
||||
network.mdns = true;
|
||||
} else {
|
||||
network.mdns = false;
|
||||
}
|
||||
|
||||
switch(sys.boardType) {
|
||||
case 6: // Pow-P1
|
||||
meterConfig.baud = 115200;
|
||||
meterConfig.parity = 3; // 8N1
|
||||
break;
|
||||
case 3: // Pow-K UART0
|
||||
case 5: // Pow-K+
|
||||
meterConfig.baud = 2400;
|
||||
meterConfig.parity = 3; // 8N1
|
||||
case 2: // spenceme
|
||||
case 8: // dbeinder: HAN mosquito
|
||||
case 50: // Generic ESP32-S2
|
||||
case 51: // Wemos S2 mini
|
||||
case 70: // Generic ESP32-C3
|
||||
case 71: // ESP32-C3-DevKitM-1
|
||||
network.sleep = 1; // Modem sleep
|
||||
break;
|
||||
case 4: // Pow-U UART0
|
||||
case 7: // Pow-U+
|
||||
network.sleep = 2; // Light sleep
|
||||
break;
|
||||
}
|
||||
config->setNetworkConfig(network);
|
||||
config->setMeterConfig(meterConfig);
|
||||
|
||||
sys.userConfigured = success;
|
||||
sys.dataCollectionConsent = 0;
|
||||
config->setSystemConfig(sys);
|
||||
|
||||
performRestart = true;
|
||||
performRestart = true;
|
||||
}
|
||||
} else if(server.hasArg(F("sf")) && !server.arg(F("sf")).isEmpty()) {
|
||||
SystemConfig sys;
|
||||
config->getSystemConfig(sys);
|
||||
@ -1202,76 +1261,80 @@ void AmsWebServer::handleSave() {
|
||||
|
||||
if(server.hasArg(F("m")) && server.arg(F("m")) == F("true")) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received meter config\n"));
|
||||
config->getMeterConfig(*meterConfig);
|
||||
meterConfig->baud = server.arg(F("mb")).toInt();
|
||||
meterConfig->parity = server.arg(F("mp")).toInt();
|
||||
meterConfig->invert = server.hasArg(F("mi")) && server.arg(F("mi")) == F("true");
|
||||
meterConfig->distributionSystem = server.arg(F("md")).toInt();
|
||||
meterConfig->mainFuse = server.arg(F("mf")).toInt();
|
||||
meterConfig->productionCapacity = server.arg(F("mr")).toInt();
|
||||
meterConfig->bufferSize = min((double) 64, ceil((server.arg(F("ms")).toInt()) / 64));
|
||||
MeterConfig meterConfig;
|
||||
config->getMeterConfig(meterConfig);
|
||||
meterConfig.source = server.arg(F("mo")).toInt();
|
||||
meterConfig.parser = server.arg(F("ma")).toInt();
|
||||
meterConfig.baud = server.arg(F("mb")).toInt();
|
||||
meterConfig.parity = server.arg(F("mp")).toInt();
|
||||
meterConfig.invert = server.hasArg(F("mi")) && server.arg(F("mi")) == F("true");
|
||||
meterConfig.distributionSystem = server.arg(F("md")).toInt();
|
||||
meterConfig.mainFuse = server.arg(F("mf")).toInt();
|
||||
meterConfig.productionCapacity = server.arg(F("mr")).toInt();
|
||||
meterConfig.bufferSize = min((double) 64, ceil((server.arg(F("ms")).toInt()) / 64));
|
||||
maxPwr = 0;
|
||||
|
||||
if(server.hasArg(F("me")) && server.arg(F("me")) == F("true")) {
|
||||
String encryptionKeyHex = server.arg(F("mek"));
|
||||
if(!encryptionKeyHex.isEmpty()) {
|
||||
encryptionKeyHex.replace(F("0x"), F(""));
|
||||
fromHex(meterConfig->encryptionKey, encryptionKeyHex, 16);
|
||||
fromHex(meterConfig.encryptionKey, encryptionKeyHex, 16);
|
||||
} else {
|
||||
memset(meterConfig->encryptionKey, 0, 16);
|
||||
memset(meterConfig.encryptionKey, 0, 16);
|
||||
}
|
||||
|
||||
String authenticationKeyHex = server.arg(F("mea"));
|
||||
if(!authenticationKeyHex.isEmpty()) {
|
||||
authenticationKeyHex.replace(F("0x"), F(""));
|
||||
fromHex(meterConfig->authenticationKey, authenticationKeyHex, 16);
|
||||
fromHex(meterConfig.authenticationKey, authenticationKeyHex, 16);
|
||||
} else {
|
||||
memset(meterConfig->authenticationKey, 0, 16);
|
||||
memset(meterConfig.authenticationKey, 0, 16);
|
||||
}
|
||||
} else {
|
||||
memset(meterConfig->encryptionKey, 0, 16);
|
||||
memset(meterConfig->authenticationKey, 0, 16);
|
||||
memset(meterConfig.encryptionKey, 0, 16);
|
||||
memset(meterConfig.authenticationKey, 0, 16);
|
||||
}
|
||||
|
||||
meterConfig->wattageMultiplier = server.arg(F("mmw")).toFloat() * 1000;
|
||||
meterConfig->voltageMultiplier = server.arg(F("mmv")).toFloat() * 1000;
|
||||
meterConfig->amperageMultiplier = server.arg(F("mma")).toFloat() * 1000;
|
||||
meterConfig->accumulatedMultiplier = server.arg(F("mmc")).toFloat() * 1000;
|
||||
config->setMeterConfig(*meterConfig);
|
||||
meterConfig.wattageMultiplier = server.arg(F("mmw")).toFloat() * 1000;
|
||||
meterConfig.voltageMultiplier = server.arg(F("mmv")).toFloat() * 1000;
|
||||
meterConfig.amperageMultiplier = server.arg(F("mma")).toFloat() * 1000;
|
||||
meterConfig.accumulatedMultiplier = server.arg(F("mmc")).toFloat() * 1000;
|
||||
config->setMeterConfig(meterConfig);
|
||||
}
|
||||
|
||||
if(server.hasArg(F("w")) && server.arg(F("w")) == F("true")) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received WiFi config\n"));
|
||||
WiFiConfig wifi;
|
||||
config->getWiFiConfig(wifi);
|
||||
strcpy(wifi.ssid, server.arg(F("ws")).c_str());
|
||||
NetworkConfig network;
|
||||
config->getNetworkConfig(network);
|
||||
strcpy(network.ssid, server.arg(F("ws")).c_str());
|
||||
String psk = server.arg(F("wp"));
|
||||
if(!psk.equals("***")) {
|
||||
strcpy(wifi.psk, psk.c_str());
|
||||
strcpy(network.psk, psk.c_str());
|
||||
}
|
||||
wifi.power = server.arg(F("ww")).toFloat() * 10;
|
||||
wifi.sleep = server.arg(F("wz")).toInt();
|
||||
wifi.autoreboot = server.hasArg(F("wa")) && server.arg(F("wa")) == F("true");
|
||||
wifi.use11b = server.hasArg(F("wb")) && server.arg(F("wb")) == F("true");
|
||||
config->setWiFiConfig(wifi);
|
||||
network.power = server.arg(F("ww")).toFloat() * 10;
|
||||
network.sleep = server.arg(F("wz")).toInt();
|
||||
network.use11b = server.hasArg(F("wb")) && server.arg(F("wb")) == F("true");
|
||||
network.mode = server.arg(F("nc")).toInt();
|
||||
if(network.mode > 3) network.mode = 1; // WiFi Client
|
||||
config->setNetworkConfig(network);
|
||||
|
||||
if(server.hasArg(F("nm"))) {
|
||||
if(server.arg(F("nm")) == "static") {
|
||||
strcpy(wifi.ip, server.arg(F("ni")).c_str());
|
||||
strcpy(wifi.gateway, server.arg(F("ng")).c_str());
|
||||
strcpy(wifi.subnet, server.arg(F("ns")).c_str());
|
||||
strcpy(wifi.dns1, server.arg(F("nd1")).c_str());
|
||||
strcpy(wifi.dns2, server.arg(F("nd2")).c_str());
|
||||
strcpy(network.ip, server.arg(F("ni")).c_str());
|
||||
strcpy(network.gateway, server.arg(F("ng")).c_str());
|
||||
strcpy(network.subnet, server.arg(F("ns")).c_str());
|
||||
strcpy(network.dns1, server.arg(F("nd1")).c_str());
|
||||
strcpy(network.dns2, server.arg(F("nd2")).c_str());
|
||||
} else if(server.arg(F("nm")) == "dhcp") {
|
||||
strcpy(wifi.ip, "");
|
||||
strcpy(wifi.gateway, "");
|
||||
strcpy(wifi.subnet, "");
|
||||
strcpy(wifi.dns1, "");
|
||||
strcpy(wifi.dns2, "");
|
||||
strcpy(network.ip, "");
|
||||
strcpy(network.gateway, "");
|
||||
strcpy(network.subnet, "");
|
||||
strcpy(network.dns1, "");
|
||||
strcpy(network.dns2, "");
|
||||
}
|
||||
}
|
||||
wifi.mdns = server.hasArg(F("nd")) && server.arg(F("nd")) == F("true");
|
||||
config->setWiFiConfig(wifi);
|
||||
network.mdns = server.hasArg(F("nd")) && server.arg(F("nd")) == F("true");
|
||||
config->setNetworkConfig(network);
|
||||
}
|
||||
|
||||
if(server.hasArg(F("ntp")) && server.arg(F("ntp")) == F("true")) {
|
||||
@ -1353,12 +1416,12 @@ void AmsWebServer::handleSave() {
|
||||
}
|
||||
config->setWebConfig(webConfig);
|
||||
|
||||
WiFiConfig wifi;
|
||||
config->getWiFiConfig(wifi);
|
||||
NetworkConfig network;
|
||||
config->getNetworkConfig(network);
|
||||
if(server.hasArg(F("gh")) && !server.arg(F("gh")).isEmpty()) {
|
||||
strcpy(wifi.hostname, server.arg(F("gh")).c_str());
|
||||
strcpy(network.hostname, server.arg(F("gh")).c_str());
|
||||
}
|
||||
config->setWiFiConfig(wifi);
|
||||
config->setNetworkConfig(network);
|
||||
|
||||
NtpConfig ntp;
|
||||
config->getNtpConfig(ntp);
|
||||
@ -1368,8 +1431,13 @@ void AmsWebServer::handleSave() {
|
||||
|
||||
if(server.hasArg(F("i")) && server.arg(F("i")) == F("true")) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received GPIO config\n"));
|
||||
gpioConfig->hanPin = server.hasArg(F("ihp")) && !server.arg(F("ihp")).isEmpty() ? server.arg(F("ihp")).toInt() : 3;
|
||||
gpioConfig->hanPinPullup = server.hasArg(F("ihu")) && server.arg(F("ihu")) == F("true");
|
||||
MeterConfig meterConfig;
|
||||
config->getMeterConfig(meterConfig);
|
||||
meterConfig.rxPin = server.hasArg(F("ihp")) && !server.arg(F("ihp")).isEmpty() ? server.arg(F("ihp")).toInt() : 3;
|
||||
meterConfig.rxPinPullup = server.hasArg(F("ihu")) && server.arg(F("ihu")) == F("true");
|
||||
meterConfig.txPin = server.hasArg(F("iht")) && !server.arg(F("iht")).isEmpty() ? server.arg(F("iht")).toInt() : 1;
|
||||
config->setMeterConfig(meterConfig);
|
||||
|
||||
gpioConfig->ledPin = server.hasArg(F("ilp")) && !server.arg(F("ilp")).isEmpty() ? server.arg(F("ilp")).toInt() : 0xFF;
|
||||
gpioConfig->ledInverted = server.hasArg(F("ili")) && server.arg(F("ili")) == F("true");
|
||||
gpioConfig->ledPinRed = server.hasArg(F("irr")) && !server.arg(F("irr")).isEmpty() ? server.arg(F("irr")).toInt() : 0xFF;
|
||||
@ -1382,6 +1450,12 @@ void AmsWebServer::handleSave() {
|
||||
gpioConfig->vccPin = server.hasArg(F("ivp")) && !server.arg(F("ivp")).isEmpty() ? server.arg(F("ivp")).toInt() : 0xFF;
|
||||
gpioConfig->vccResistorGnd = server.hasArg(F("ivdg")) && !server.arg(F("ivdg")).isEmpty() ? server.arg(F("ivdg")).toInt() : 0;
|
||||
gpioConfig->vccResistorVcc = server.hasArg(F("ivdv")) && !server.arg(F("ivdv")).isEmpty() ? server.arg(F("ivdv")).toInt() : 0;
|
||||
gpioConfig->ledDisablePin = server.hasArg(F("idd")) && !server.arg(F("idd")).isEmpty() ? server.arg(F("idd")).toInt() : 0;
|
||||
config->setGpioConfig(*gpioConfig);
|
||||
}
|
||||
|
||||
if(server.hasArg(F("idb"))) {
|
||||
gpioConfig->ledBehaviour = server.hasArg(F("idb")) && !server.arg(F("idb")).isEmpty() ? server.arg(F("idb")).toInt() : 0;
|
||||
config->setGpioConfig(*gpioConfig);
|
||||
}
|
||||
|
||||
@ -1410,9 +1484,9 @@ void AmsWebServer::handleSave() {
|
||||
debugger->setPassword(F(""));
|
||||
}
|
||||
debugger->setSerialEnabled(debug.serial);
|
||||
WiFiConfig wifi;
|
||||
if(config->getWiFiConfig(wifi) && strlen(wifi.hostname) > 0) {
|
||||
debugger->begin(wifi.hostname, (uint8_t) debug.level);
|
||||
NetworkConfig network;
|
||||
if(config->getNetworkConfig(network) && strlen(network.hostname) > 0) {
|
||||
debugger->begin(network.hostname, (uint8_t) debug.level);
|
||||
if(!debug.telnet) {
|
||||
debugger->stop();
|
||||
}
|
||||
@ -1437,6 +1511,8 @@ void AmsWebServer::handleSave() {
|
||||
ui.showDayPlot = server.arg(F("ud")).toInt();
|
||||
ui.showMonthPlot = server.arg(F("um")).toInt();
|
||||
ui.showTemperaturePlot = server.arg(F("us")).toInt();
|
||||
ui.showRealtimePlot = server.arg(F("ul")).toInt();
|
||||
ui.darkMode = server.arg(F("uk")).toInt();
|
||||
config->setUiConfig(ui);
|
||||
}
|
||||
|
||||
@ -1471,11 +1547,38 @@ void AmsWebServer::handleSave() {
|
||||
config->setEnergyAccountingConfig(eac);
|
||||
}
|
||||
|
||||
if(server.hasArg(F("c")) && server.arg(F("c")) == F("true")) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Received cloud config\n"));
|
||||
SystemConfig sys;
|
||||
config->getSystemConfig(sys);
|
||||
sys.energyspeedometer = server.hasArg(F("ces")) && server.arg(F("ces")) == F("true") ? 7 : 0;
|
||||
config->setSystemConfig(sys);
|
||||
|
||||
CloudConfig cloud;
|
||||
config->getCloudConfig(cloud);
|
||||
cloud.enabled = server.hasArg(F("ce")) && server.arg(F("ce")) == F("true");
|
||||
if(cloud.enabled) {
|
||||
String host = server.arg("ch");
|
||||
if(!host.isEmpty()) {
|
||||
strcpy(cloud.hostname, host.c_str());
|
||||
}
|
||||
String clientId = server.arg(F("ci")).c_str();
|
||||
if(!clientId.isEmpty()) {
|
||||
strcpy(cloud.clientId, clientId.c_str());
|
||||
}
|
||||
String secret = server.arg(F("cs"));
|
||||
if(!secret.isEmpty() && !secret.equals("***")) {
|
||||
strcpy(cloud.clientSecret, secret.c_str());
|
||||
}
|
||||
}
|
||||
config->setCloudConfig(cloud);
|
||||
}
|
||||
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Saving configuration now...\n"));
|
||||
|
||||
if (config->save()) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("Successfully saved.\n"));
|
||||
if(config->isWifiChanged() || performRestart) {
|
||||
if(config->isNetworkConfigChanged() || performRestart) {
|
||||
performRestart = true;
|
||||
} else {
|
||||
hw->setup(gpioConfig, config);
|
||||
@ -1702,14 +1805,8 @@ HTTPUpload& AmsWebServer::uploadFile(const char* path) {
|
||||
}
|
||||
}
|
||||
} else if(upload.status == UPLOAD_FILE_WRITE) {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("handleFileUpload Writing: %u\n"), upload.currentSize);
|
||||
}
|
||||
if(file) {
|
||||
size_t written = file.write(upload.buf, upload.currentSize);
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) {
|
||||
debugger->printf_P(PSTR("handleFileUpload Written: %u\n"), written);
|
||||
}
|
||||
delay(1);
|
||||
if(written != upload.currentSize) {
|
||||
file.flush();
|
||||
@ -1942,6 +2039,30 @@ void AmsWebServer::tariffJson() {
|
||||
server.send(200, MIME_JSON, buf);
|
||||
}
|
||||
|
||||
void AmsWebServer::realtimeJson() {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Serving /realtime.json over http...\n"));
|
||||
if(rtp == NULL) {
|
||||
server.send_P(500, MIME_PLAIN, PSTR("500: Not available"));
|
||||
return;
|
||||
}
|
||||
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_CONTROL_NO_CACHE);
|
||||
server.sendHeader(HEADER_PRAGMA, PRAGMA_NO_CACHE);
|
||||
server.sendHeader(HEADER_EXPIRES, EXPIRES_OFF);
|
||||
|
||||
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||
snprintf_P(buf, BufferSize, PSTR("{\"size\":\"%d\",\"data\":["), rtp->getSize());
|
||||
server.send(200, MIME_JSON, buf);
|
||||
bool first = true;
|
||||
for(uint16_t i = 0; i < rtp->getSize(); i++) {
|
||||
snprintf_P(buf, BufferSize, PSTR("%s%d"), first ? "" : ",", rtp->getValue(i));
|
||||
server.sendContent(buf);
|
||||
first = false;
|
||||
}
|
||||
snprintf_P(buf, BufferSize, PSTR("]}"));
|
||||
server.sendContent(buf);
|
||||
}
|
||||
|
||||
void AmsWebServer::setPriceSettings(String region, String currency) {
|
||||
this->priceRegion = region;
|
||||
this->priceCurrency = currency;
|
||||
@ -1977,20 +2098,21 @@ void AmsWebServer::configFileDownload() {
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("boardType %d\n"), sys.boardType));
|
||||
|
||||
if(includeWifi) {
|
||||
WiFiConfig wifi;
|
||||
config->getWiFiConfig(wifi);
|
||||
if(includeSecrets) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("hostname %s\n"), wifi.hostname));
|
||||
if(includeSecrets) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("ssid %s\n"), wifi.ssid));
|
||||
if(includeSecrets) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("psk %s\n"), wifi.psk));
|
||||
if(strlen(wifi.ip) > 0) {
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("ip %s\n"), wifi.ip));
|
||||
if(strlen(wifi.gateway) > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("gateway %s\n"), wifi.gateway));
|
||||
if(strlen(wifi.subnet) > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("subnet %s\n"), wifi.subnet));
|
||||
if(strlen(wifi.dns1) > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("dns1 %s\n"), wifi.dns1));
|
||||
if(strlen(wifi.dns2) > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("dns2 %s\n"), wifi.dns2));
|
||||
NetworkConfig network;
|
||||
config->getNetworkConfig(network);
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("netmode %d\n"), network.mode));
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("hostname %s\n"), network.hostname));
|
||||
if(includeSecrets) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("ssid %s\n"), network.ssid));
|
||||
if(includeSecrets) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("psk %s\n"), network.psk));
|
||||
if(strlen(network.ip) > 0) {
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("ip %s\n"), network.ip));
|
||||
if(strlen(network.gateway) > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("gateway %s\n"), network.gateway));
|
||||
if(strlen(network.subnet) > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("subnet %s\n"), network.subnet));
|
||||
if(strlen(network.dns1) > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("dns1 %s\n"), network.dns1));
|
||||
if(strlen(network.dns2) > 0) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("dns2 %s\n"), network.dns2));
|
||||
}
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("mdns %d\n"), wifi.mdns ? 1 : 0));
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("use11b %d\n"), wifi.use11b ? 1 : 0));
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("mdns %d\n"), network.mdns ? 1 : 0));
|
||||
server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("use11b %d\n"), network.use11b ? 1 : 0));
|
||||
}
|
||||
|
||||
if(includeMqtt) {
|
||||
@ -2065,10 +2187,12 @@ void AmsWebServer::configFileDownload() {
|
||||
}
|
||||
|
||||
if(includeGpio) {
|
||||
MeterConfig meter;
|
||||
config->getMeterConfig(meter);
|
||||
GpioConfig gpio;
|
||||
config->getGpioConfig(gpio);
|
||||
if(gpio.hanPin != 0xFF) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("gpioHanPin %d\n"), gpio.hanPin));
|
||||
if(gpio.hanPin != 0xFF) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("gpioHanPinPullup %d\n"), gpio.hanPinPullup ? 1 : 0));
|
||||
if(meter.rxPin != 0xFF) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("gpioHanPin %d\n"), meter.rxPin));
|
||||
if(meter.rxPin != 0xFF) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("gpioHanPinPullup %d\n"), meter.rxPinPullup ? 1 : 0));
|
||||
if(gpio.apPin != 0xFF) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("gpioApPin %d\n"), gpio.apPin));
|
||||
if(gpio.ledPin != 0xFF) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("gpioLedPin %d\n"), gpio.ledPin));
|
||||
if(gpio.ledPin != 0xFF) server.sendContent(buf, snprintf_P(buf, BufferSize, PSTR("gpioLedInverted %d\n"), gpio.ledInverted ? 1 : 0));
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _UPTIME_H
|
||||
#define _UPTIME_H
|
||||
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Uptime.h"
|
||||
|
||||
uint32_t _uptime_last_value = 0;
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
extra_configs = platformio-user.ini
|
||||
|
||||
[common]
|
||||
lib_deps = EEPROM, LittleFS, DNSServer, https://github.com/256dpi/arduino-mqtt.git, OneWireNg@0.10.0, DallasTemperature@3.9.1, EspSoftwareSerial@6.14.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.1, Timezone@1.2.4, FirmwareVersion, AmsConfiguration, AmsData, AmsDataStorage, HwTools, Uptime, AmsDecoder, EntsoePriceApi, EnergyAccounting, RawMqttHandler, JsonMqttHandler, DomoticzMqttHandler, HomeAssistantMqttHandler, SvelteUi
|
||||
lib_deps = EEPROM, LittleFS, DNSServer, https://github.com/256dpi/arduino-mqtt.git, OneWireNg@0.10.0, DallasTemperature@3.9.1, EspSoftwareSerial@6.14.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.1, Timezone@1.2.4, FirmwareVersion, AmsConfiguration, AmsData, AmsDataStorage, HwTools, Uptime, AmsDecoder, EntsoePriceApi, EnergyAccounting, AmsMqttHandler, RawMqttHandler, JsonMqttHandler, DomoticzMqttHandler, HomeAssistantMqttHandler, RealtimePlot, SvelteUi
|
||||
lib_ignore = OneWire
|
||||
extra_scripts =
|
||||
pre:scripts/addversion.py
|
||||
@ -17,7 +17,7 @@ build_flags =
|
||||
-fexceptions
|
||||
|
||||
[esp32]
|
||||
lib_deps = WiFi, ESPmDNS, WiFiClientSecure, HTTPClient, FS, Update, HTTPUpdate, WebServer, ${common.lib_deps}
|
||||
lib_deps = WiFi, Ethernet, ESPmDNS, WiFiClientSecure, HTTPClient, FS, Update, HTTPUpdate, WebServer, ${common.lib_deps}, CloudConnector
|
||||
|
||||
[env:esp8266]
|
||||
platform = espressif8266@4.2.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,39 +0,0 @@
|
||||
#ifndef _AMSTOMQTTBRIDGE_H
|
||||
#define _AMSTOMQTTBRIDGE_H
|
||||
|
||||
#define WIFI_CONNECTION_TIMEOUT 30000
|
||||
|
||||
#define INVALID_BUTTON_PIN 0xFFFFFFFF
|
||||
|
||||
#define MAX_PEM_SIZE 4096
|
||||
|
||||
#define METER_SOURCE_NONE 0
|
||||
#define METER_SOURCE_SERIAL 1
|
||||
#define METER_SOURCE_MQTT 2
|
||||
#define METER_SOURCE_ESPNOW 3
|
||||
|
||||
#define METER_ERROR_NO_DATA 90
|
||||
#define METER_ERROR_BREAK 91
|
||||
#define METER_ERROR_BUFFER 92
|
||||
#define METER_ERROR_FIFO 93
|
||||
#define METER_ERROR_FRAME 94
|
||||
#define METER_ERROR_PARITY 95
|
||||
#define METER_ERROR_RX 96
|
||||
#define METER_ERROR_EXCEPTION 98
|
||||
#define METER_ERROR_AUTODETECT 99
|
||||
|
||||
#include <SoftwareSerial.h>
|
||||
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266mDNS.h>
|
||||
#elif defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include "Update.h"
|
||||
#endif
|
||||
|
||||
#include "LittleFS.h"
|
||||
|
||||
#endif
|
||||
41
src/ConnectionHandler.h
Normal file
41
src/ConnectionHandler.h
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @copyright Utilitech AS 2023
|
||||
* License: Fair Source
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _CONNECTIONHANDLER_H
|
||||
#define _CONNECTIONHANDLER_H
|
||||
|
||||
#include "AmsConfiguration.h"
|
||||
#if defined(ESP8266)
|
||||
#include <ESP8266WiFi.h>
|
||||
#elif defined(ESP32)
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#define NETWORK_MODE_WIFI_CLIENT 1
|
||||
#define NETWORK_MODE_WIFI_AP 2
|
||||
#define NETWORK_MODE_ETH_CLIENT 3
|
||||
|
||||
class ConnectionHandler {
|
||||
public:
|
||||
virtual ~ConnectionHandler() {};
|
||||
virtual bool connect(NetworkConfig config, SystemConfig sys);
|
||||
virtual void disconnect(unsigned long reconnectDelay);
|
||||
virtual bool isConnected();
|
||||
virtual bool isConfigChanged();
|
||||
virtual void getCurrentConfig(NetworkConfig& networkConfig);
|
||||
#if defined(ESP32)
|
||||
virtual void eventHandler(WiFiEvent_t event, WiFiEventInfo_t info);
|
||||
#endif
|
||||
|
||||
uint8_t getMode() {
|
||||
return this->mode;
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t mode;
|
||||
};
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user