mirror of
https://github.com/UtilitechAS/amsreader-firmware.git
synced 2026-01-13 23:45:25 +00:00
Merge branch 'master' into use11b
This commit is contained in:
commit
e2ff7613b6
34
frames/slovenia-iskra.raw
Normal file
34
frames/slovenia-iskra.raw
Normal file
@ -0,0 +1,34 @@
|
||||
02 12
|
||||
09 10 49 53 4B 31 30 33 30 37 38 39 33 37 35 36 30 36 // str: ISK1030789375606 (device name)
|
||||
09 08 31 36 38 32 30 30 30 35 // str: 16820005 (device id)
|
||||
06 00 00 05 71 // active import
|
||||
06 00 00 00 00 // active export
|
||||
06 00 00 00 00 // reactive import
|
||||
06 00 00 02 E2 // reactive export
|
||||
12 09 4B // U1
|
||||
12 09 4B // U2
|
||||
12 08 F8 // U3
|
||||
12 00 67 // I1
|
||||
12 00 7D // I2
|
||||
12 02 55 // I3
|
||||
06 00 00 00 10 // L1 a+
|
||||
06 00 00 00 33 // L2 a+
|
||||
06 00 00 05 2D // L3 a+
|
||||
06 00 00 00 00 // L1 a-
|
||||
06 00 00 00 00 // L2 a-
|
||||
06 00 00 00 00 // L3 a-
|
||||
|
||||
|
||||
02 0C
|
||||
09 10 49 53 4B 31 30 33 30 37 38 39 33 37 35 36 30 36
|
||||
09 08 31 36 38 32 30 30 30 36
|
||||
16 01 // Disconnect control
|
||||
09 02 00 01 // Currently active energy tariff
|
||||
06 00 00 CE 65 // 1.8.0 a+
|
||||
06 00 00 35 19 // 1.8.1
|
||||
06 00 00 99 4C // 1.8.2
|
||||
06 00 02 B7 07 // 2.8.0 a-
|
||||
06 00 01 33 17 // 2.8.1
|
||||
06 00 01 83 F0 // 2.8.2
|
||||
06 00 01 A5 6A // q+
|
||||
06 00 00 A8 F5 // q-
|
||||
@ -22,7 +22,7 @@ public:
|
||||
|
||||
void apply(AmsData& other);
|
||||
|
||||
unsigned long getLastUpdateMillis();
|
||||
uint64_t getLastUpdateMillis();
|
||||
|
||||
time_t getPackageTimestamp();
|
||||
|
||||
@ -73,8 +73,8 @@ public:
|
||||
void setLastError(int8_t);
|
||||
|
||||
protected:
|
||||
unsigned long lastUpdateMillis = 0;
|
||||
unsigned long lastList2 = 0;
|
||||
uint64_t lastUpdateMillis = 0;
|
||||
uint64_t lastList2 = 0;
|
||||
uint8_t listType = 0, meterType = AmsTypeUnknown;
|
||||
time_t packageTimestamp = 0;
|
||||
String listId = "", meterId = "", meterModel = "";
|
||||
|
||||
@ -82,7 +82,7 @@ void AmsData::apply(AmsData& other) {
|
||||
this->activeExportPower = other.getActiveExportPower();
|
||||
}
|
||||
|
||||
unsigned long AmsData::getLastUpdateMillis() {
|
||||
uint64_t AmsData::getLastUpdateMillis() {
|
||||
return this->lastUpdateMillis;
|
||||
}
|
||||
|
||||
|
||||
@ -547,7 +547,7 @@ uint8_t AmsDataStorage::getDayAccuracy() {
|
||||
|
||||
void AmsDataStorage::setDayAccuracy(uint8_t accuracy) {
|
||||
if(day.accuracy != accuracy) {
|
||||
uint16_t multiplier = pow(10, day.accuracy)/pow(10, accuracy);
|
||||
double multiplier = pow(10, day.accuracy)/pow(10, accuracy);
|
||||
for(uint8_t i = 0; i < 24; i++) {
|
||||
day.hImport[i] = day.hImport[i] * multiplier;
|
||||
day.hExport[i] = day.hExport[i] * multiplier;
|
||||
@ -562,7 +562,7 @@ uint8_t AmsDataStorage::getMonthAccuracy() {
|
||||
|
||||
void AmsDataStorage::setMonthAccuracy(uint8_t accuracy) {
|
||||
if(month.accuracy != accuracy) {
|
||||
uint16_t multiplier = pow(10, month.accuracy)/pow(10, accuracy);
|
||||
double multiplier = pow(10, month.accuracy)/pow(10, accuracy);
|
||||
for(uint8_t i = 0; i < 31; i++) {
|
||||
month.dImport[i] = month.dImport[i] * multiplier;
|
||||
month.dExport[i] = month.dExport[i] * multiplier;
|
||||
|
||||
@ -56,10 +56,25 @@ struct EnergyAccountingData2 {
|
||||
uint16_t costLastMonth;
|
||||
};
|
||||
|
||||
struct EnergyAccountingRealtimeData {
|
||||
uint8_t magic;
|
||||
uint8_t currentHour;
|
||||
uint8_t currentDay;
|
||||
uint8_t currentThresholdIdx;
|
||||
float use;
|
||||
float costHour;
|
||||
float costDay;
|
||||
float produce;
|
||||
float incomeHour;
|
||||
float incomeDay;
|
||||
unsigned long lastImportUpdateMillis;
|
||||
unsigned long lastExportUpdateMillis;
|
||||
};
|
||||
|
||||
|
||||
class EnergyAccounting {
|
||||
public:
|
||||
EnergyAccounting(RemoteDebug*);
|
||||
EnergyAccounting(RemoteDebug*, EnergyAccountingRealtimeData*);
|
||||
void setup(AmsDataStorage *ds, EnergyAccountingConfig *config);
|
||||
void setEapi(EntsoeApi *eapi);
|
||||
void setTimezone(Timezone*);
|
||||
@ -103,16 +118,13 @@ public:
|
||||
|
||||
private:
|
||||
RemoteDebug* debugger = NULL;
|
||||
unsigned long lastUpdateMillis = 0;
|
||||
bool init = false, initPrice = false;
|
||||
AmsDataStorage *ds = NULL;
|
||||
EntsoeApi *eapi = NULL;
|
||||
EnergyAccountingConfig *config = NULL;
|
||||
Timezone *tz = NULL;
|
||||
uint8_t currentHour = 0, currentDay = 0, currentThresholdIdx = 0;
|
||||
float use = 0, costHour = 0, costDay = 0;
|
||||
float produce = 0, incomeHour = 0, incomeDay = 0;
|
||||
EnergyAccountingData data = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
EnergyAccountingRealtimeData* realtimeData = NULL;
|
||||
float fixedPrice = 0;
|
||||
String currency = "";
|
||||
|
||||
|
||||
@ -3,15 +3,29 @@
|
||||
#include "AmsStorage.h"
|
||||
#include "FirmwareVersion.h"
|
||||
|
||||
EnergyAccounting::EnergyAccounting(RemoteDebug* debugger) {
|
||||
EnergyAccounting::EnergyAccounting(RemoteDebug* debugger, EnergyAccountingRealtimeData* rtd) {
|
||||
data.version = 1;
|
||||
this->debugger = debugger;
|
||||
if(rtd->magic != 0x6A) {
|
||||
rtd->magic = 0x6A;
|
||||
rtd->currentHour = 0;
|
||||
rtd->currentDay = 0;
|
||||
rtd->currentThresholdIdx = 0;
|
||||
rtd->use = 0;
|
||||
rtd->costHour = 0;
|
||||
rtd->costDay = 0;
|
||||
rtd->produce = 0;
|
||||
rtd->incomeHour = 0;
|
||||
rtd->incomeDay = 0;
|
||||
rtd->lastImportUpdateMillis = 0;
|
||||
rtd->lastExportUpdateMillis = 0;
|
||||
}
|
||||
this->realtimeData = rtd;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setup(AmsDataStorage *ds, EnergyAccountingConfig *config) {
|
||||
this->ds = ds;
|
||||
this->config = config;
|
||||
this->currentThresholdIdx = 0;
|
||||
}
|
||||
|
||||
void EnergyAccounting::setEapi(EntsoeApi *eapi) {
|
||||
@ -44,8 +58,8 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
breakTime(tz->toLocal(now), local);
|
||||
|
||||
if(!init) {
|
||||
currentHour = local.Hour;
|
||||
currentDay = local.Day;
|
||||
this->realtimeData->currentHour = local.Hour;
|
||||
this->realtimeData->currentDay = local.Day;
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("(EnergyAccounting) Initializing data at %lu\n"), (int32_t) now);
|
||||
if(!load()) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) Unable to load existing data\n"));
|
||||
@ -75,7 +89,7 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
calcDayCost();
|
||||
}
|
||||
|
||||
if(local.Hour != currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
|
||||
if(local.Hour != this->realtimeData->currentHour && (amsData->getListType() >= 3 || local.Minute == 1)) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) New local hour %d\n"), local.Hour);
|
||||
|
||||
tmElements_t oneHrAgo, oneHrAgoLocal;
|
||||
@ -85,28 +99,28 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
breakTime(tz->toLocal(now-3600), oneHrAgoLocal);
|
||||
ret |= updateMax(val, oneHrAgoLocal.Day);
|
||||
|
||||
currentHour = local.Hour; // Need to be defined here so that day cost is correctly calculated
|
||||
this->realtimeData->currentHour = local.Hour; // Need to be defined here so that day cost is correctly calculated
|
||||
if(local.Hour > 0) {
|
||||
calcDayCost();
|
||||
}
|
||||
|
||||
use = 0;
|
||||
produce = 0;
|
||||
costHour = 0;
|
||||
incomeHour = 0;
|
||||
this->realtimeData->use = 0;
|
||||
this->realtimeData->produce = 0;
|
||||
this->realtimeData->costHour = 0;
|
||||
this->realtimeData->incomeHour = 0;
|
||||
|
||||
uint8_t prevDay = currentDay;
|
||||
if(local.Day != currentDay) {
|
||||
uint8_t prevDay = this->realtimeData->currentDay;
|
||||
if(local.Day != this->realtimeData->currentDay) {
|
||||
if(debugger->isActive(RemoteDebug::INFO)) debugger->printf_P(PSTR("(EnergyAccounting) New day %d\n"), local.Day);
|
||||
data.costYesterday = costDay * 100;
|
||||
data.costThisMonth += costDay * 100;
|
||||
costDay = 0;
|
||||
data.costYesterday = this->realtimeData->costDay * 100;
|
||||
data.costThisMonth += this->realtimeData->costDay * 100;
|
||||
this->realtimeData->costDay = 0;
|
||||
|
||||
data.incomeYesterday = incomeDay * 100;
|
||||
data.incomeThisMonth += incomeDay * 100;
|
||||
incomeDay = 0;
|
||||
data.incomeYesterday = this->realtimeData->incomeDay * 100;
|
||||
data.incomeThisMonth += this->realtimeData->incomeDay * 100;
|
||||
this->realtimeData->incomeDay = 0;
|
||||
|
||||
currentDay = local.Day;
|
||||
this->realtimeData->currentDay = local.Day;
|
||||
ret = true;
|
||||
}
|
||||
|
||||
@ -137,40 +151,47 @@ bool EnergyAccounting::update(AmsData* amsData) {
|
||||
data.lastMonthAccuracy = accuracy;
|
||||
|
||||
data.month = local.Month;
|
||||
currentThresholdIdx = 0;
|
||||
this->realtimeData->currentThresholdIdx = 0;
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long ms = this->lastUpdateMillis > amsData->getLastUpdateMillis() ? 0 : amsData->getLastUpdateMillis() - this->lastUpdateMillis;
|
||||
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
lastUpdateMillis = amsData->getLastUpdateMillis();
|
||||
if(kwhi > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) Adding %.4f kWh import\n"), kwhi);
|
||||
use += kwhi;
|
||||
if(price != ENTSOE_NO_VALUE) {
|
||||
float cost = price * kwhi;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), cost / 100.0, currency.c_str());
|
||||
costHour += cost;
|
||||
costDay += cost;
|
||||
if(this->realtimeData->lastImportUpdateMillis < amsData->getLastUpdateMillis()) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastImportUpdateMillis;
|
||||
float kwhi = (amsData->getActiveImportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(kwhi > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) Adding %.4f kWh import\n"), kwhi);
|
||||
this->realtimeData->use += kwhi;
|
||||
if(price != ENTSOE_NO_VALUE) {
|
||||
float cost = price * kwhi;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), cost / 100.0, currency.c_str());
|
||||
this->realtimeData->costHour += cost;
|
||||
this->realtimeData->costDay += cost;
|
||||
}
|
||||
}
|
||||
this->realtimeData->lastImportUpdateMillis = amsData->getLastUpdateMillis();
|
||||
}
|
||||
if(kwhe > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) Adding %.4f kWh export\n"), kwhe);
|
||||
produce += kwhe;
|
||||
if(price != ENTSOE_NO_VALUE) {
|
||||
float income = price * kwhe;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), income / 100.0, currency.c_str());
|
||||
incomeHour += income;
|
||||
incomeDay += income;
|
||||
|
||||
if(amsData->getListType() > 1 && this->realtimeData->lastExportUpdateMillis < amsData->getLastUpdateMillis()) {
|
||||
unsigned long ms = amsData->getLastUpdateMillis() - this->realtimeData->lastExportUpdateMillis;
|
||||
float kwhe = (amsData->getActiveExportPower() * (((float) ms) / 3600000.0)) / 1000.0;
|
||||
if(kwhe > 0) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) Adding %.4f kWh export\n"), kwhe);
|
||||
this->realtimeData->produce += kwhe;
|
||||
if(price != ENTSOE_NO_VALUE) {
|
||||
float income = price * kwhe;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) and %.4f %s\n"), income / 100.0, currency.c_str());
|
||||
this->realtimeData->incomeHour += income;
|
||||
this->realtimeData->incomeDay += income;
|
||||
}
|
||||
}
|
||||
this->realtimeData-> lastExportUpdateMillis = amsData->getLastUpdateMillis();
|
||||
}
|
||||
|
||||
if(config != NULL) {
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) calculating threshold, currently at %d\n"), currentThresholdIdx);
|
||||
while(getMonthMax() > config->thresholds[currentThresholdIdx] && currentThresholdIdx < 10) currentThresholdIdx++;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) new threshold %d\n"), currentThresholdIdx);
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) calculating threshold, currently at %d\n"), this->realtimeData->currentThresholdIdx);
|
||||
while(getMonthMax() > config->thresholds[this->realtimeData->currentThresholdIdx] && this->realtimeData->currentThresholdIdx < 10) this->realtimeData->currentThresholdIdx++;
|
||||
if(debugger->isActive(RemoteDebug::VERBOSE)) debugger->printf_P(PSTR("(EnergyAccounting) new threshold %d\n"), this->realtimeData->currentThresholdIdx);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -184,25 +205,25 @@ void EnergyAccounting::calcDayCost() {
|
||||
|
||||
if(getPriceForHour(0) != ENTSOE_NO_VALUE) {
|
||||
if(initPrice) {
|
||||
costDay = 0;
|
||||
incomeDay = 0;
|
||||
this->realtimeData->costDay = 0;
|
||||
this->realtimeData->incomeDay = 0;
|
||||
}
|
||||
for(uint8_t i = 0; i < currentHour; i++) {
|
||||
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
|
||||
float price = getPriceForHour(i - local.Hour);
|
||||
if(price == ENTSOE_NO_VALUE) break;
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
int16_t wh = ds->getHourImport(utc.Hour);
|
||||
costDay += price * (wh / 1000.0);
|
||||
this->realtimeData->costDay += price * (wh / 1000.0);
|
||||
|
||||
wh = ds->getHourExport(utc.Hour);
|
||||
incomeDay += price * (wh / 1000.0);
|
||||
this->realtimeData->incomeDay += price * (wh / 1000.0);
|
||||
}
|
||||
initPrice = true;
|
||||
}
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseThisHour() {
|
||||
return use;
|
||||
return this->realtimeData->use;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getUseToday() {
|
||||
@ -212,7 +233,7 @@ float EnergyAccounting::getUseToday() {
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
tmElements_t utc, local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(uint8_t i = 0; i < currentHour; i++) {
|
||||
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
ret += ds->getHourImport(utc.Hour) / 1000.0;
|
||||
}
|
||||
@ -223,7 +244,7 @@ float EnergyAccounting::getUseThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
float ret = 0;
|
||||
for(uint8_t i = 1; i < currentDay; i++) {
|
||||
for(uint8_t i = 1; i < this->realtimeData->currentDay; i++) {
|
||||
ret += ds->getDayImport(i) / 1000.0;
|
||||
}
|
||||
return ret + getUseToday();
|
||||
@ -234,7 +255,7 @@ float EnergyAccounting::getUseLastMonth() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedThisHour() {
|
||||
return produce;
|
||||
return this->realtimeData->produce;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getProducedToday() {
|
||||
@ -244,7 +265,7 @@ float EnergyAccounting::getProducedToday() {
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
tmElements_t utc, local;
|
||||
breakTime(tz->toLocal(now), local);
|
||||
for(uint8_t i = 0; i < currentHour; i++) {
|
||||
for(uint8_t i = 0; i < this->realtimeData->currentHour; i++) {
|
||||
breakTime(now - ((local.Hour - i) * 3600), utc);
|
||||
ret += ds->getHourExport(utc.Hour) / 1000.0;
|
||||
}
|
||||
@ -255,7 +276,7 @@ float EnergyAccounting::getProducedThisMonth() {
|
||||
time_t now = time(nullptr);
|
||||
if(now < FirmwareVersion::BuildEpoch) return 0.0;
|
||||
float ret = 0;
|
||||
for(uint8_t i = 1; i < currentDay; i++) {
|
||||
for(uint8_t i = 1; i < this->realtimeData->currentDay; i++) {
|
||||
ret += ds->getDayExport(i) / 1000.0;
|
||||
}
|
||||
return ret + getProducedToday();
|
||||
@ -266,11 +287,11 @@ float EnergyAccounting::getProducedLastMonth() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostThisHour() {
|
||||
return costHour;
|
||||
return this->realtimeData->costHour;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostToday() {
|
||||
return costDay;
|
||||
return this->realtimeData->costDay;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getCostYesterday() {
|
||||
@ -286,11 +307,11 @@ float EnergyAccounting::getCostLastMonth() {
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeThisHour() {
|
||||
return incomeHour;
|
||||
return this->realtimeData->incomeHour;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeToday() {
|
||||
return incomeDay;
|
||||
return this->realtimeData->incomeDay;
|
||||
}
|
||||
|
||||
float EnergyAccounting::getIncomeYesterday() {
|
||||
@ -308,7 +329,7 @@ float EnergyAccounting::getIncomeLastMonth() {
|
||||
uint8_t EnergyAccounting::getCurrentThreshold() {
|
||||
if(config == NULL)
|
||||
return 0;
|
||||
return config->thresholds[currentThresholdIdx];
|
||||
return config->thresholds[this->realtimeData->currentThresholdIdx];
|
||||
}
|
||||
|
||||
float EnergyAccounting::getMonthMax() {
|
||||
@ -521,11 +542,11 @@ bool EnergyAccounting::updateMax(uint16_t val, uint8_t day) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
uint16_t test = 0;
|
||||
uint16_t test = val;
|
||||
uint8_t idx = 255;
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
if(val > data.peaks[i].value) {
|
||||
if(test < data.peaks[i].value) {
|
||||
if(test > data.peaks[i].value) {
|
||||
test = data.peaks[i].value;
|
||||
idx = i;
|
||||
}
|
||||
|
||||
@ -301,14 +301,14 @@ float EntsoeApi::getCurrencyMultiplier(const char* from, const char* to, time_t
|
||||
}
|
||||
|
||||
PricesContainer* EntsoeApi::fetchPrices(time_t t) {
|
||||
tmElements_t tm;
|
||||
breakTime(t, tm);
|
||||
if(strlen(getToken()) > 0) {
|
||||
time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second; // UTC midnight
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(t), tm);
|
||||
time_t e1 = t - (tm.Hour * 3600) - (tm.Minute * 60) - tm.Second; // Local midnight
|
||||
time_t e2 = e1 + SECS_PER_DAY;
|
||||
tmElements_t d1, d2;
|
||||
breakTime(tz->toUTC(e1), d1); // To get day and hour for CET/CEST at UTC midnight
|
||||
breakTime(tz->toUTC(e2), d2);
|
||||
breakTime(e1, d1);
|
||||
breakTime(e2, d2);
|
||||
|
||||
snprintf_P(buf, BufferSize, PSTR("https://web-api.tp.entsoe.eu/api?securityToken=%s&documentType=A44&periodStart=%04d%02d%02d%02d%02d&periodEnd=%04d%02d%02d%02d%02d&in_Domain=%s&out_Domain=%s"),
|
||||
getToken(),
|
||||
@ -333,6 +333,9 @@ PricesContainer* EntsoeApi::fetchPrices(time_t t) {
|
||||
return NULL;
|
||||
}
|
||||
} else if(hub) {
|
||||
tmElements_t tm;
|
||||
breakTime(tz->toLocal(t), tm);
|
||||
|
||||
String data;
|
||||
snprintf_P(buf, BufferSize, PSTR("http://hub.amsleser.no/hub/price/%s/%d/%d/%d?currency=%s"),
|
||||
config->area,
|
||||
|
||||
@ -444,7 +444,7 @@ void HomeAssistantMqttHandler::publishRealtimeSensors(EnergyAccounting* ea, Ents
|
||||
char name[strlen(RealtimePeakSensor.name)];
|
||||
snprintf(name, strlen(RealtimePeakSensor.name), RealtimePeakSensor.name, i+1);
|
||||
char path[strlen(RealtimePeakSensor.path)];
|
||||
snprintf(path, strlen(RealtimePeakSensor.path), RealtimePeakSensor.path, i+1);
|
||||
snprintf(path, strlen(RealtimePeakSensor.path), RealtimePeakSensor.path, i);
|
||||
HomeAssistantSensor sensor = {
|
||||
name,
|
||||
RealtimePeakSensor.topic,
|
||||
@ -524,7 +524,7 @@ void HomeAssistantMqttHandler::publishPriceSensors(EntsoeApi* eapi) {
|
||||
path,
|
||||
uom.c_str(),
|
||||
PriceSensor.devcl,
|
||||
PriceSensor.stacl
|
||||
i == 0 ? "total" : PriceSensor.stacl
|
||||
};
|
||||
publishSensor(sensor);
|
||||
prInit[i] = true;
|
||||
|
||||
@ -267,7 +267,7 @@ bool RawMqttHandler::publishSystem(HwTools* hw, EntsoeApi* eapi, EnergyAccountin
|
||||
return false;
|
||||
|
||||
mqtt->publish(topic + "/id", WiFi.macAddress(), true, 0);
|
||||
mqtt->publish(topic + "/uptime", String((unsigned long) millis64()/1000));
|
||||
mqtt->publish(topic + "/uptime", String((uint32_t) (millis64()/1000)));
|
||||
float vcc = hw->getVcc();
|
||||
if(vcc > 0) {
|
||||
mqtt->publish(topic + "/vcc", String(vcc, 2));
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
$:{
|
||||
showFull = Math.abs(new Date().getTime()-timestamp.getTime()) < 300000;
|
||||
if(!isNaN(offset))
|
||||
addHours(timestamp, offset - ((timestamp.getHours() - timestamp.getUTCHours())%24));
|
||||
addHours(timestamp, offset - ((24 + timestamp.getHours() - timestamp.getUTCHours())%24));
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
min = max = 0;
|
||||
let cur = addHours(new Date(), -24);
|
||||
let currentHour = new Date().getUTCHours();
|
||||
addHours(cur, sysinfo.clock_offset - ((cur.getHours() - cur.getUTCHours())%24));
|
||||
addHours(cur, sysinfo.clock_offset - ((24 + cur.getHours() - cur.getUTCHours())%24));
|
||||
for(i = currentHour; i<24; i++) {
|
||||
let imp = json["i"+zeropad(i)];
|
||||
let exp = json["e"+zeropad(i)];
|
||||
@ -63,24 +63,27 @@
|
||||
addHours(cur, 1);
|
||||
};
|
||||
|
||||
let boundary = Math.ceil(Math.max(min, max));
|
||||
|
||||
max = boundary;
|
||||
min = min == 0 ? 0 : boundary*-1;
|
||||
min *= -1;
|
||||
let range = Math.max(max, Math.abs(min));
|
||||
|
||||
if(min < 0) {
|
||||
let yTickDistDown = min/4;
|
||||
for(i = 1; i < 5; i++) {
|
||||
min = Math.min((range/4)*-1, min);
|
||||
let yTicksNum = Math.ceil((Math.abs(min)/range) * 4);
|
||||
let yTickDistDown = min/yTicksNum;
|
||||
for(i = 1; i < yTicksNum+1; i++) {
|
||||
let val = (yTickDistDown*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
label: (val/10).toFixed(1)
|
||||
});
|
||||
}
|
||||
console.log(yTicksNum);
|
||||
}
|
||||
|
||||
let yTickDistUp = max/4;
|
||||
for(i = 0; i < 5; i++) {
|
||||
max = Math.max((range/4), max);
|
||||
let xTicksNum = Math.ceil((max/range) * 4);
|
||||
let yTickDistUp = max/xTicksNum;
|
||||
for(i = 0; i < xTicksNum+1; i++) {
|
||||
let val = (yTickDistUp*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
|
||||
@ -17,8 +17,8 @@
|
||||
min = max = 0;
|
||||
let cur = new Date();
|
||||
let lm = new Date();
|
||||
addHours(cur, sysinfo.clock_offset - ((cur.getHours() - cur.getUTCHours())%24));
|
||||
addHours(lm, sysinfo.clock_offset - ((lm.getHours() - lm.getUTCHours())%24));
|
||||
addHours(cur, sysinfo.clock_offset - ((24 + cur.getHours() - cur.getUTCHours())%24));
|
||||
addHours(lm, sysinfo.clock_offset - ((24 + lm.getHours() - lm.getUTCHours())%24));
|
||||
lm.setDate(0);
|
||||
|
||||
for(i = cur.getDate(); i<=lm.getDate(); i++) {
|
||||
@ -64,14 +64,14 @@
|
||||
max = Math.max(max, imp);
|
||||
}
|
||||
|
||||
let boundary = Math.ceil(Math.max(min, max)/10)*10;
|
||||
|
||||
max = boundary;
|
||||
min = min == 0 ? 0 : boundary*-1;
|
||||
min *= -1;
|
||||
let range = Math.max(max, Math.abs(min));
|
||||
|
||||
if(min < 0) {
|
||||
let yTickDistDown = min/4;
|
||||
for(i = 0; i < 5; i++) {
|
||||
min = Math.min((range/4)*-1, min);
|
||||
let yTicksNum = Math.ceil((Math.abs(min)/range) * 4);
|
||||
let yTickDistDown = min/yTicksNum;
|
||||
for(i = 1; i < yTicksNum+1; i++) {
|
||||
let val = (yTickDistDown*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
@ -80,8 +80,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
let yTickDistUp = max/4;
|
||||
for(i = 0; i < 5; i++) {
|
||||
max = Math.max((range/4), max);
|
||||
let xTicksNum = Math.ceil((max/range) * 4);
|
||||
let yTickDistUp = max/xTicksNum;
|
||||
for(i = 0; i < xTicksNum+1; i++) {
|
||||
let val = (yTickDistUp*i);
|
||||
yTicks.push({
|
||||
value: val,
|
||||
|
||||
@ -88,7 +88,7 @@
|
||||
<div class="cnt">
|
||||
<strong class="text-sm">Device information</strong>
|
||||
<div class="my-2">
|
||||
Chip: {sysinfo.chip}
|
||||
Chip: {sysinfo.chip} ({sysinfo.cpu}MHz)
|
||||
</div>
|
||||
<div class="my-2">
|
||||
Device: <Link to="/vendor">{boardtype(sysinfo.chip, sysinfo.board)}</Link>
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
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";
|
||||
static const char HEADER_EXPIRES[] PROGMEM = "Expires";
|
||||
static const char HEADER_AUTHENTICATE[] PROGMEM = "WWW-Authenticate";
|
||||
static const char HEADER_LOCATION[] PROGMEM = "Location";
|
||||
|
||||
static const char CACHE_CONTROL_NO_CACHE[] PROGMEM = "no-cache, no-store, must-revalidate";
|
||||
static const char CONTENT_ENCODING_GZIP[] PROGMEM = "gzip";
|
||||
static const char CACHE_1HR[] PROGMEM = "public, max-age=3600";
|
||||
static const char CACHE_1MO[] PROGMEM = "public, max-age=2592000";
|
||||
static const char CACHE_1MO[] PROGMEM = "public, max-age=2630000";
|
||||
static const char CACHE_1YR[] PROGMEM = "public, max-age=31536000";
|
||||
static const char PRAGMA_NO_CACHE[] PROGMEM = "no-cache";
|
||||
static const char EXPIRES_OFF[] PROGMEM = "-1";
|
||||
static const char AUTHENTICATE_BASIC[] PROGMEM = "Basic realm=\"Secure Area\"";
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
"version": "%s",
|
||||
"chip": "%s",
|
||||
"chipId": "%s",
|
||||
"cpu": %d,
|
||||
"mac": "%s",
|
||||
"apmac": "%s",
|
||||
"board": %d,
|
||||
|
||||
@ -2,6 +2,7 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import gzip
|
||||
|
||||
try:
|
||||
from css_html_js_minify import html_minify, js_minify, css_minify
|
||||
@ -65,16 +66,24 @@ for webroot in ["lib/SvelteUi/app/dist", "lib/SvelteUi/json"]:
|
||||
content = js_minify(content)
|
||||
except:
|
||||
print("WARN: Unable to minify")
|
||||
|
||||
|
||||
content_bytes = content.encode("utf-8")
|
||||
if filename in ["index.js", "index.css"]:
|
||||
content_bytes = gzip.compress(content_bytes, compresslevel=9)
|
||||
content_len = len(content_bytes)
|
||||
else:
|
||||
content_len = len(content_bytes)
|
||||
content_bytes += b"\0"
|
||||
|
||||
with open(dstfile, "w") as dst:
|
||||
dst.write("static const char ")
|
||||
dst.write(varname)
|
||||
dst.write("[] PROGMEM = R\"==\"==(")
|
||||
dst.write(content)
|
||||
dst.write(")==\"==\";\n")
|
||||
dst.write("[] PROGMEM = {")
|
||||
dst.write(", ".join([str(c) for c in content_bytes]))
|
||||
dst.write("};\n")
|
||||
dst.write("const int ");
|
||||
dst.write(varname)
|
||||
dst.write("_LEN PROGMEM = ");
|
||||
dst.write(str(len(content)))
|
||||
dst.write(str(content_len))
|
||||
dst.write(";");
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
#if defined(ESP32)
|
||||
#include <esp_task_wdt.h>
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_clk.h>
|
||||
#endif
|
||||
|
||||
|
||||
@ -190,14 +191,14 @@ void AmsWebServer::notFound() {
|
||||
void AmsWebServer::githubSvg() {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Serving /github.svg over http...\n"));
|
||||
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1HR);
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1MO);
|
||||
server.send_P(200, "image/svg+xml", GITHUB_SVG);
|
||||
}
|
||||
|
||||
void AmsWebServer::faviconSvg() {
|
||||
if(debugger->isActive(RemoteDebug::DEBUG)) debugger->printf_P(PSTR("Serving /favicon.ico over http...\n"));
|
||||
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1HR);
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1MO);
|
||||
server.send_P(200, "image/svg+xml", FAVICON_SVG);
|
||||
}
|
||||
|
||||
@ -239,9 +240,11 @@ void AmsWebServer::sysinfoJson() {
|
||||
#if defined(ESP8266)
|
||||
wifi_get_macaddr(STATION_IF, mac);
|
||||
wifi_get_macaddr(SOFTAP_IF, apmac);
|
||||
uint32_t cpu_freq = 80;
|
||||
#elif defined(ESP32)
|
||||
esp_wifi_get_mac((wifi_interface_t)ESP_IF_WIFI_STA, mac);
|
||||
esp_wifi_get_mac((wifi_interface_t)ESP_IF_WIFI_AP, apmac);
|
||||
uint32_t cpu_freq = esp_clk_cpu_freq();
|
||||
#endif
|
||||
|
||||
sprintf(macStr, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
@ -277,6 +280,7 @@ void AmsWebServer::sysinfoJson() {
|
||||
"esp8266",
|
||||
#endif
|
||||
chipIdStr.c_str(),
|
||||
cpu_freq / 1000000,
|
||||
macStr,
|
||||
apMacStr,
|
||||
sys.boardType,
|
||||
@ -764,9 +768,9 @@ void AmsWebServer::indexCss() {
|
||||
if(!checkSecurity(2))
|
||||
return;
|
||||
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1MO);
|
||||
server.setContentLength(INDEX_CSS_LEN);
|
||||
server.send_P(200, MIME_CSS, INDEX_CSS);
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1YR);
|
||||
server.sendHeader(HEADER_CONTENT_ENCODING, CONTENT_ENCODING_GZIP);
|
||||
server.send_P(200, MIME_CSS, INDEX_CSS, INDEX_CSS_LEN);
|
||||
}
|
||||
|
||||
void AmsWebServer::indexJs() {
|
||||
@ -775,8 +779,9 @@ void AmsWebServer::indexJs() {
|
||||
if(!checkSecurity(2))
|
||||
return;
|
||||
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1MO);
|
||||
server.send_P(200, MIME_JS, INDEX_JS);
|
||||
server.sendHeader(HEADER_CACHE_CONTROL, CACHE_1YR);
|
||||
server.sendHeader(HEADER_CONTENT_ENCODING, CONTENT_ENCODING_GZIP);
|
||||
server.send_P(200, MIME_JS, INDEX_JS, INDEX_JS_LEN);
|
||||
}
|
||||
|
||||
void AmsWebServer::configurationJson() {
|
||||
@ -1005,6 +1010,13 @@ void AmsWebServer::handleSave() {
|
||||
gpioConfig->ledPin = 15;
|
||||
gpioConfig->ledInverted = false;
|
||||
gpioConfig->apPin = 0;
|
||||
gpioConfig->hanPin = hanPin > 0 ? hanPin : 18;
|
||||
if(gpioConfig->hanPin != 18) {
|
||||
gpioConfig->vccPin = 18;
|
||||
gpioConfig->vccResistorGnd = 45;
|
||||
gpioConfig->vccResistorVcc = 10;
|
||||
}
|
||||
break;
|
||||
case 50: // Generic ESP32-S2
|
||||
gpioConfig->hanPin = hanPin > 0 ? hanPin : 18;
|
||||
break;
|
||||
|
||||
@ -32,7 +32,7 @@ lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
|
||||
[env:esp32]
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.04.02/platform-espressif32.zip
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.07.00/platform-espressif32.zip
|
||||
framework = arduino
|
||||
board = esp32dev
|
||||
board_build.f_cpu = 160000000L
|
||||
@ -47,7 +47,7 @@ extra_scripts = ${common.extra_scripts}
|
||||
# https://github.com/Jason2866/esp32-arduino-lib-builder
|
||||
|
||||
[env:esp32s2]
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.04.02/platform-espressif32.zip
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.07.00/platform-espressif32.zip
|
||||
framework = arduino
|
||||
board = esp32-s2-saola-1
|
||||
board_build.mcu = esp32s2
|
||||
@ -63,7 +63,7 @@ lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
|
||||
[env:esp32solo]
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.04.02/platform-espressif32.zip
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.07.00/platform-espressif32.zip
|
||||
framework = arduino
|
||||
board = esp32-solo1
|
||||
board_build.f_cpu = 160000000L
|
||||
@ -75,7 +75,7 @@ lib_ignore = ${common.lib_ignore}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
|
||||
[env:esp32c3]
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.04.02/platform-espressif32.zip
|
||||
platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.07.00/platform-espressif32.zip
|
||||
framework = arduino
|
||||
board = esp32-c3-devkitm-1
|
||||
board_build.mcu = esp32c3
|
||||
|
||||
@ -113,6 +113,7 @@ SoftwareSerial *swSerial = NULL;
|
||||
HardwareSerial *hwSerial = NULL;
|
||||
uint8_t rxBufferErrors = 0;
|
||||
|
||||
SystemConfig sysConfig;
|
||||
GpioConfig gpioConfig;
|
||||
MeterConfig meterConfig;
|
||||
bool mqttEnabled = false;
|
||||
@ -123,7 +124,12 @@ bool ntpEnabled = false;
|
||||
bool mdnsEnabled = false;
|
||||
|
||||
AmsDataStorage ds(&Debug);
|
||||
EnergyAccounting ea(&Debug);
|
||||
#if defined(ESP32)
|
||||
__NOINIT_ATTR EnergyAccountingRealtimeData rtd;
|
||||
#else
|
||||
EnergyAccountingRealtimeData rtd;
|
||||
#endif
|
||||
EnergyAccounting ea(&Debug, &rtd);
|
||||
|
||||
uint8_t wifiReconnectCount = 0;
|
||||
bool wifiDisable11b = false;
|
||||
@ -145,6 +151,7 @@ void configFileParse();
|
||||
void swapWifiMode();
|
||||
void WiFi_connect();
|
||||
void WiFi_post_connect();
|
||||
void WiFi_disconnect(unsigned long timeout);
|
||||
void MQTT_connect();
|
||||
void handleNtpChange();
|
||||
void handleDataSuccess(AmsData* data);
|
||||
@ -155,6 +162,7 @@ void handleButton(unsigned long now);
|
||||
void handlePriceApi(unsigned long now);
|
||||
void handleClear(unsigned long now);
|
||||
void handleEnergyAccountingChanged();
|
||||
bool handleVoltageCheck();
|
||||
bool readHanPort();
|
||||
void setupHanPort(GpioConfig& gpioConfig, uint32_t baud, uint8_t parityOrdinal, bool invert);
|
||||
void rxerr(int err);
|
||||
@ -194,15 +202,21 @@ void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
}
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
wifi_err_reason_t reason = (wifi_err_reason_t) info.wifi_sta_disconnected.reason;
|
||||
debugI_P(PSTR("WiFi disconnected, reason %s"), WiFi.disconnectReasonName(reason));
|
||||
const char* descr = WiFi.disconnectReasonName(reason);
|
||||
debugI_P(PSTR("WiFi disconnected, reason %s"), descr);
|
||||
switch(reason) {
|
||||
case WIFI_REASON_AUTH_FAIL:
|
||||
case WIFI_REASON_NO_AP_FOUND:
|
||||
SystemConfig sys;
|
||||
if(!config.getSystemConfig(sys) || sys.dataCollectionConsent == 0) {
|
||||
if(sysConfig.dataCollectionConsent == 0) {
|
||||
swapWifiMode();
|
||||
} else if(strlen(descr) > 0) {
|
||||
WiFi_disconnect(5000);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if(strlen(descr) > 0) {
|
||||
WiFi_disconnect(5000);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -218,6 +232,12 @@ void setup() {
|
||||
if(!config.getGpioConfig(gpioConfig)) {
|
||||
config.clearGpio(gpioConfig);
|
||||
}
|
||||
if(!config.getSystemConfig(sysConfig)) {
|
||||
sysConfig.boardType = 0;
|
||||
sysConfig.vendorConfigured = false;
|
||||
sysConfig.userConfigured = false;
|
||||
sysConfig.dataCollectionConsent = false;
|
||||
}
|
||||
|
||||
delay(1);
|
||||
config.loadTempSensors();
|
||||
@ -290,7 +310,11 @@ void setup() {
|
||||
break;
|
||||
}
|
||||
#if defined(ESP32)
|
||||
Serial.begin(meterConfig.baud == 0 ? 2400 : meterConfig.baud, serialConfig, -1, -1, meterConfig.invert);
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
Serial0.begin(meterConfig.baud == 0 ? 2400 : meterConfig.baud, serialConfig, -1, -1, meterConfig.invert);
|
||||
#else
|
||||
Serial.begin(meterConfig.baud == 0 ? 2400 : meterConfig.baud, serialConfig, -1, -1, meterConfig.invert);
|
||||
#endif
|
||||
#else
|
||||
Serial.begin(meterConfig.baud == 0 ? 2400 : meterConfig.baud, serialConfig, SERIAL_FULL, 1, meterConfig.invert);
|
||||
#endif
|
||||
@ -458,6 +482,7 @@ bool wifiConnected = false;
|
||||
unsigned long lastTemperatureRead = 0;
|
||||
unsigned long lastSysupdate = 0;
|
||||
unsigned long lastErrorBlink = 0;
|
||||
unsigned long lastVoltageCheck = 0;
|
||||
int lastError = 0;
|
||||
|
||||
bool meterAutodetect = false;
|
||||
@ -554,6 +579,11 @@ void loop() {
|
||||
debugW_P(PSTR("Used %dms to handle mqtt"), millis()-start);
|
||||
}
|
||||
}
|
||||
|
||||
if(now - lastVoltageCheck > 1000) {
|
||||
handleVoltageCheck();
|
||||
lastVoltageCheck = now;
|
||||
}
|
||||
} else {
|
||||
if(dnsServer != NULL) {
|
||||
dnsServer->processNextRequest();
|
||||
@ -646,6 +676,7 @@ void handleEnergyAccountingChanged() {
|
||||
}
|
||||
|
||||
char ntpServerName[64] = "";
|
||||
float maxVcc = 2.9;
|
||||
|
||||
void handleNtpChange() {
|
||||
NtpConfig ntp;
|
||||
@ -814,6 +845,26 @@ void handleButton(unsigned long now) {
|
||||
}
|
||||
}
|
||||
|
||||
bool handleVoltageCheck() {
|
||||
if(sysConfig.boardType == 7 && maxVcc > 2.8) { // Pow-U
|
||||
float vcc = hw.getVcc();
|
||||
if(vcc > 3.4 || vcc < 2.8) {
|
||||
maxVcc = 0;
|
||||
} else if(vcc > maxVcc) {
|
||||
debugD_P(PSTR("Setting new max Vcc to %.2f"), vcc);
|
||||
maxVcc = vcc;
|
||||
} else if(WiFi.getMode() != WIFI_OFF) {
|
||||
float diff = maxVcc-vcc;
|
||||
if(diff > 0.3) {
|
||||
debugW_P(PSTR("Vcc dropped to %.2f, disconnecting WiFi for 5 seconds to preserve power"), vcc);
|
||||
WiFi_disconnect(5000);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void rxerr(int err) {
|
||||
if(err == 0) return;
|
||||
switch(err) {
|
||||
@ -837,7 +888,8 @@ void rxerr(int err) {
|
||||
debugW_P(PSTR("Serial parity error"));
|
||||
break;
|
||||
}
|
||||
meterState.setLastError(90+err);
|
||||
// Do not include serial break
|
||||
if(err > 1) meterState.setLastError(90+err);
|
||||
}
|
||||
|
||||
void setupHanPort(GpioConfig& gpioConfig, uint32_t baud, uint8_t parityOrdinal, bool invert) {
|
||||
@ -854,16 +906,18 @@ void setupHanPort(GpioConfig& gpioConfig, uint32_t baud, uint8_t parityOrdinal,
|
||||
parityOrdinal = 3; // 8N1
|
||||
}
|
||||
|
||||
SystemConfig sys;
|
||||
config.getSystemConfig(sys);
|
||||
switch(sys.boardType) {
|
||||
switch(sysConfig.boardType) {
|
||||
case 8: // HAN mosquito: has inverting level shifter
|
||||
invert = !invert;
|
||||
break;
|
||||
}
|
||||
|
||||
if(pin == 3 || pin == 113) {
|
||||
hwSerial = &Serial;
|
||||
#if ARDUINO_USB_CDC_ON_BOOT
|
||||
hwSerial = &Serial0;
|
||||
#else
|
||||
hwSerial = &Serial;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(ESP32)
|
||||
@ -912,7 +966,7 @@ void setupHanPort(GpioConfig& gpioConfig, uint32_t baud, uint8_t parityOrdinal,
|
||||
|
||||
hwSerial->setRxBufferSize(64 * meterConfig.bufferSize);
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
hwSerial->begin(baud, serialConfig, -1, -1, invert);
|
||||
hwSerial->begin(baud, serialConfig, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, invert);
|
||||
uart_set_pin(UART_NUM_1, UART_PIN_NO_CHANGE, pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
#elif defined(ESP32)
|
||||
hwSerial->begin(baud, serialConfig, -1, -1, invert);
|
||||
@ -930,6 +984,23 @@ void setupHanPort(GpioConfig& gpioConfig, uint32_t baud, uint8_t parityOrdinal,
|
||||
}
|
||||
#endif
|
||||
|
||||
// Prevent pullup on TX pin if not uart0
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
pinMode(17, INPUT);
|
||||
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
pinMode(7, INPUT);
|
||||
#elif defined(ESP32)
|
||||
if(pin == 9) {
|
||||
pinMode(10, INPUT);
|
||||
} else if(pin == 16) {
|
||||
pinMode(17, INPUT);
|
||||
}
|
||||
#elif defined(ESP8266)
|
||||
if(pin == 113) {
|
||||
pinMode(15, INPUT);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ESP32)
|
||||
hwSerial->onReceiveError(rxerr);
|
||||
#endif
|
||||
@ -1185,7 +1256,12 @@ bool readHanPort() {
|
||||
// Rudimentary detector for L&G proprietary format, this is terrible code... Fix later
|
||||
if(payload[0] == CosemTypeStructure && payload[2] == CosemTypeArray && payload[1] == payload[3]) {
|
||||
debugV_P(PSTR("LNG"));
|
||||
data = new LNG(payload, meterState.getMeterType(), &meterConfig, ctx, &Debug);
|
||||
LNG lngData = LNG(payload, meterState.getMeterType(), &meterConfig, ctx, &Debug);
|
||||
if(lngData.getListType() >= 3) {
|
||||
data = new AmsData();
|
||||
data->apply(meterState);
|
||||
data->apply(lngData);
|
||||
}
|
||||
} else if(payload[0] == CosemTypeStructure &&
|
||||
payload[2] == CosemTypeLongUnsigned &&
|
||||
payload[5] == CosemTypeLongUnsigned &&
|
||||
@ -1195,11 +1271,16 @@ bool readHanPort() {
|
||||
payload[17] == CosemTypeLongUnsigned
|
||||
) {
|
||||
debugV_P(PSTR("LNG2"));
|
||||
data = new LNG2(payload, meterState.getMeterType(), &meterConfig, ctx, &Debug);
|
||||
LNG2 lngData = LNG2(payload, meterState.getMeterType(), &meterConfig, ctx, &Debug);
|
||||
if(lngData.getListType() >= 3) {
|
||||
data = new AmsData();
|
||||
data->apply(meterState);
|
||||
data->apply(lngData);
|
||||
}
|
||||
} else {
|
||||
debugV_P(PSTR("DLMS"));
|
||||
// TODO: Split IEC6205675 into DataParserKaifa and DataParserObis. This way we can add other means of parsing, for those other proprietary formats
|
||||
data = new IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx);
|
||||
data = new IEC6205675(payload, meterState.getMeterType(), &meterConfig, ctx, meterState);
|
||||
}
|
||||
} else if(ctx.type == DATA_TAG_DSMR) {
|
||||
data = new IEC6205621(payload, tz);
|
||||
@ -1336,6 +1417,41 @@ void debugPrint(byte *buffer, int start, int length) {
|
||||
|
||||
unsigned long wifiTimeout = WIFI_CONNECTION_TIMEOUT;
|
||||
unsigned long lastWifiRetry = -WIFI_CONNECTION_TIMEOUT;
|
||||
|
||||
void WiFi_disconnect(unsigned long timeout) {
|
||||
if (Debug.isActive(RemoteDebug::INFO)) debugI_P(PSTR("Not connected to WiFi, closing resources"));
|
||||
if(mqtt != NULL) {
|
||||
mqtt->disconnect();
|
||||
mqtt->loop();
|
||||
delay(10);
|
||||
yield();
|
||||
delete mqtt;
|
||||
mqtt = NULL;
|
||||
ws.setMqtt(NULL);
|
||||
}
|
||||
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
mqttClient = NULL;
|
||||
if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
WiFiClient::stopAll();
|
||||
#endif
|
||||
|
||||
MDNS.end();
|
||||
WiFi.disconnect(true);
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.enableAP(false);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
yield();
|
||||
wifiTimeout = timeout;
|
||||
}
|
||||
|
||||
void WiFi_connect() {
|
||||
if(millis() - lastWifiRetry < wifiTimeout) {
|
||||
delay(50);
|
||||
@ -1343,6 +1459,11 @@ void WiFi_connect() {
|
||||
}
|
||||
lastWifiRetry = millis();
|
||||
|
||||
if(!handleVoltageCheck()) {
|
||||
debugW_P(PSTR("Voltage is not high enough to reconnect"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
WiFiConfig wifi;
|
||||
if(!config.getWiFiConfig(wifi) || strlen(wifi.ssid) == 0) {
|
||||
@ -1352,42 +1473,14 @@ void WiFi_connect() {
|
||||
|
||||
if(WiFi.getMode() != WIFI_OFF) {
|
||||
if(wifiReconnectCount > 3 && wifi.autoreboot) {
|
||||
if (Debug.isActive(RemoteDebug::INFO)) debugI_P(PSTR("Unable to connect to WiFi, rebooting because auto reboot is enabled"));
|
||||
ESP.restart();
|
||||
return;
|
||||
}
|
||||
if (Debug.isActive(RemoteDebug::INFO)) debugI_P(PSTR("Not connected to WiFi, closing resources"));
|
||||
if(mqtt != NULL) {
|
||||
mqtt->disconnect();
|
||||
mqtt->loop();
|
||||
delay(10);
|
||||
yield();
|
||||
delete mqtt;
|
||||
mqtt = NULL;
|
||||
ws.setMqtt(NULL);
|
||||
}
|
||||
|
||||
if(mqttClient != NULL) {
|
||||
mqttClient->stop();
|
||||
delete mqttClient;
|
||||
mqttClient = NULL;
|
||||
if(mqttSecureClient != NULL) {
|
||||
mqttSecureClient = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
WiFiClient::stopAll();
|
||||
#endif
|
||||
|
||||
MDNS.end();
|
||||
WiFi.disconnect(true);
|
||||
WiFi.softAPdisconnect(true);
|
||||
WiFi.enableAP(false);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
yield();
|
||||
wifiTimeout = 5000;
|
||||
WiFi_disconnect(5000);
|
||||
return;
|
||||
}
|
||||
|
||||
wifiTimeout = WIFI_CONNECTION_TIMEOUT;
|
||||
|
||||
if (Debug.isActive(RemoteDebug::INFO)) debugI_P(PSTR("Connecting to WiFi network: %s"), wifi.ssid);
|
||||
@ -1719,10 +1812,8 @@ void MQTT_connect() {
|
||||
break;
|
||||
case 4:
|
||||
HomeAssistantConfig haconf;
|
||||
SystemConfig sys;
|
||||
config.getHomeAssistantConfig(haconf);
|
||||
config.getSystemConfig(sys);
|
||||
mqttHandler = new HomeAssistantMqttHandler(mqtt, (char*) commonBuffer, mqttConfig.clientId, mqttConfig.publishTopic, sys.boardType, haconf, &hw);
|
||||
mqttHandler = new HomeAssistantMqttHandler(mqtt, (char*) commonBuffer, mqttConfig.clientId, mqttConfig.publishTopic, sysConfig.boardType, haconf, &hw);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#include "IEC6205621.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
IEC6205621::IEC6205621(const char* p, Timezone* tz) {
|
||||
if(strlen(p) < 16)
|
||||
@ -6,7 +7,7 @@ IEC6205621::IEC6205621(const char* p, Timezone* tz) {
|
||||
|
||||
String payload(p+1);
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
lastUpdateMillis = millis64();
|
||||
listId = payload.substring(payload.startsWith("/") ? 1 : 0, payload.indexOf("\n"));
|
||||
|
||||
if(listId.startsWith(F("ADN"))) {
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
#include "lwip/def.h"
|
||||
#include "Timezone.h"
|
||||
#include "ntohll.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx) {
|
||||
IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state) {
|
||||
float val;
|
||||
char str[64];
|
||||
|
||||
@ -127,14 +128,89 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
}
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(listId.startsWith("ISK")) { // Iskra special case
|
||||
this->listId = listId;
|
||||
meterType = AmsTypeIskra;
|
||||
|
||||
int idx = 0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
if(data->base.length == 0x12) {
|
||||
listType = 2;
|
||||
|
||||
idx++;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
memcpy(str, data->oct.data, data->oct.length);
|
||||
str[data->oct.length] = 0x00;
|
||||
meterId = String(str);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportPower = ntohl(data->dlu.data);
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportPower = ntohl(data->dlu.data);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1voltage = ntohs(data->lu.data) / 10.0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2voltage = ntohs(data->lu.data) / 10.0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3voltage = ntohs(data->lu.data) / 10.0;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1current = ntohs(data->lu.data) / 100.0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2current = ntohs(data->lu.data) / 100.0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3current = ntohs(data->lu.data) / 100.0;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1activeImportPower = ntohl(data->dlu.data);
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2activeImportPower = ntohl(data->dlu.data);
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3activeImportPower = ntohl(data->dlu.data);
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l1activeExportPower = ntohl(data->dlu.data);
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l2activeExportPower = ntohl(data->dlu.data);
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
l3activeExportPower = ntohl(data->dlu.data);
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
} else if(data->base.length == 0x0C) {
|
||||
apply(state);
|
||||
|
||||
listType = 3;
|
||||
idx += 4;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
idx += 2;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
idx += 2;
|
||||
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
data = getCosemDataAt(idx++, ((char *) (d)));
|
||||
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
|
||||
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
}
|
||||
} else if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
|
||||
this->packageTimestamp = this->packageTimestamp > 0 ? tz.toUTC(this->packageTimestamp) : 0;
|
||||
listType = 1;
|
||||
meterType = AmsTypeKaifa;
|
||||
activeImportPower = ntohl(data->dlu.data);
|
||||
lastUpdateMillis = millis();
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
// Kaifa end
|
||||
} else {
|
||||
@ -378,7 +454,7 @@ IEC6205675::IEC6205675(const char* d, uint8_t useMeterType, MeterConfig* meterCo
|
||||
}
|
||||
}
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
|
||||
if(meterConfig->wattageMultiplier > 0) {
|
||||
@ -538,37 +614,37 @@ float IEC6205675::getNumber(CosemData* item) {
|
||||
switch(item->base.type) {
|
||||
case CosemTypeLongSigned: {
|
||||
int16_t i16 = ntohs(item->ls.data);
|
||||
ret = i16;
|
||||
ret = (i16 * 1.0);
|
||||
pos += 3;
|
||||
break;
|
||||
}
|
||||
case CosemTypeLongUnsigned: {
|
||||
uint16_t u16 = ntohs(item->lu.data);
|
||||
ret = u16;
|
||||
ret = (u16 * 1.0);
|
||||
pos += 3;
|
||||
break;
|
||||
}
|
||||
case CosemTypeDLongSigned: {
|
||||
int32_t i32 = ntohl(item->dlu.data);
|
||||
ret = i32;
|
||||
ret = (i32 * 1.0);
|
||||
pos += 5;
|
||||
break;
|
||||
}
|
||||
case CosemTypeDLongUnsigned: {
|
||||
uint32_t u32 = ntohl(item->dlu.data);
|
||||
ret = u32;
|
||||
ret = (u32 * 1.0);
|
||||
pos += 5;
|
||||
break;
|
||||
}
|
||||
case CosemTypeLong64Signed: {
|
||||
int64_t i64 = ntohll(item->l64s.data);
|
||||
ret = i64;
|
||||
ret = (i64 * 1.0);
|
||||
pos += 9;
|
||||
break;
|
||||
}
|
||||
case CosemTypeLong64Unsigned: {
|
||||
uint64_t u64 = ntohll(item->l64u.data);
|
||||
ret = u64;
|
||||
ret = (u64 * 1.0);
|
||||
pos += 9;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ struct AmsOctetTimestamp {
|
||||
|
||||
class IEC6205675 : public AmsData {
|
||||
public:
|
||||
IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx);
|
||||
IEC6205675(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, AmsData &state);
|
||||
|
||||
private:
|
||||
CosemData* getCosemDataAt(uint8_t index, const char* ptr);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "LNG.h"
|
||||
#include "lwip/def.h"
|
||||
#include "ntohll.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) {
|
||||
LngHeader* h = (LngHeader*) payload;
|
||||
@ -117,7 +118,7 @@ LNG::LNG(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, Da
|
||||
data += 3;
|
||||
}
|
||||
|
||||
lastUpdateMillis = millis();
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#include "LNG2.h"
|
||||
#include "Uptime.h"
|
||||
|
||||
LNG2::LNG2(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig, DataParserContext &ctx, RemoteDebug* debugger) {
|
||||
CosemBasic* h = (CosemBasic*) payload;
|
||||
@ -26,7 +27,7 @@ LNG2::LNG2(const char* payload, uint8_t useMeterType, MeterConfig* meterConfig,
|
||||
this->meterId = String(str);
|
||||
}
|
||||
listType = 3;
|
||||
lastUpdateMillis = millis();
|
||||
lastUpdateMillis = millis64();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user