diff --git a/frames/slovenia-iskra.raw b/frames/slovenia-iskra.raw new file mode 100644 index 00000000..4cd6817a --- /dev/null +++ b/frames/slovenia-iskra.raw @@ -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- diff --git a/lib/AmsData/include/AmsData.h b/lib/AmsData/include/AmsData.h index 61d07805..b864a2bc 100644 --- a/lib/AmsData/include/AmsData.h +++ b/lib/AmsData/include/AmsData.h @@ -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 = ""; diff --git a/lib/AmsData/src/AmsData.cpp b/lib/AmsData/src/AmsData.cpp index bc2a2491..66e5fea5 100644 --- a/lib/AmsData/src/AmsData.cpp +++ b/lib/AmsData/src/AmsData.cpp @@ -82,7 +82,7 @@ void AmsData::apply(AmsData& other) { this->activeExportPower = other.getActiveExportPower(); } -unsigned long AmsData::getLastUpdateMillis() { +uint64_t AmsData::getLastUpdateMillis() { return this->lastUpdateMillis; } diff --git a/lib/AmsDataStorage/src/AmsDataStorage.cpp b/lib/AmsDataStorage/src/AmsDataStorage.cpp index 35a39ff8..44b2bcb3 100644 --- a/lib/AmsDataStorage/src/AmsDataStorage.cpp +++ b/lib/AmsDataStorage/src/AmsDataStorage.cpp @@ -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; diff --git a/lib/EnergyAccounting/include/EnergyAccounting.h b/lib/EnergyAccounting/include/EnergyAccounting.h index bf86e8ce..10ee6817 100644 --- a/lib/EnergyAccounting/include/EnergyAccounting.h +++ b/lib/EnergyAccounting/include/EnergyAccounting.h @@ -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 = ""; diff --git a/lib/EnergyAccounting/src/EnergyAccounting.cpp b/lib/EnergyAccounting/src/EnergyAccounting.cpp index 278e563d..593a734f 100644 --- a/lib/EnergyAccounting/src/EnergyAccounting.cpp +++ b/lib/EnergyAccounting/src/EnergyAccounting.cpp @@ -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; } diff --git a/lib/EntsoePriceApi/src/EntsoeApi.cpp b/lib/EntsoePriceApi/src/EntsoeApi.cpp index 9aa44f0f..79b6baae 100644 --- a/lib/EntsoePriceApi/src/EntsoeApi.cpp +++ b/lib/EntsoePriceApi/src/EntsoeApi.cpp @@ -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, diff --git a/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp b/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp index edb443fb..18897833 100644 --- a/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp +++ b/lib/HomeAssistantMqttHandler/src/HomeAssistantMqttHandler.cpp @@ -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; diff --git a/lib/RawMqttHandler/src/RawMqttHandler.cpp b/lib/RawMqttHandler/src/RawMqttHandler.cpp index a906dfb3..792c59ea 100644 --- a/lib/RawMqttHandler/src/RawMqttHandler.cpp +++ b/lib/RawMqttHandler/src/RawMqttHandler.cpp @@ -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)); diff --git a/lib/SvelteUi/app/src/lib/Clock.svelte b/lib/SvelteUi/app/src/lib/Clock.svelte index 9418bc50..ddc23062 100644 --- a/lib/SvelteUi/app/src/lib/Clock.svelte +++ b/lib/SvelteUi/app/src/lib/Clock.svelte @@ -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)); } diff --git a/lib/SvelteUi/app/src/lib/DayPlot.svelte b/lib/SvelteUi/app/src/lib/DayPlot.svelte index 86480623..7c242606 100644 --- a/lib/SvelteUi/app/src/lib/DayPlot.svelte +++ b/lib/SvelteUi/app/src/lib/DayPlot.svelte @@ -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, diff --git a/lib/SvelteUi/app/src/lib/MonthPlot.svelte b/lib/SvelteUi/app/src/lib/MonthPlot.svelte index 9e6682c2..04bc6bce 100644 --- a/lib/SvelteUi/app/src/lib/MonthPlot.svelte +++ b/lib/SvelteUi/app/src/lib/MonthPlot.svelte @@ -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, diff --git a/lib/SvelteUi/app/src/lib/StatusPage.svelte b/lib/SvelteUi/app/src/lib/StatusPage.svelte index 8f13918f..11896cbe 100644 --- a/lib/SvelteUi/app/src/lib/StatusPage.svelte +++ b/lib/SvelteUi/app/src/lib/StatusPage.svelte @@ -88,7 +88,7 @@
Device information
- Chip: {sysinfo.chip} + Chip: {sysinfo.chip} ({sysinfo.cpu}MHz)
Device: {boardtype(sysinfo.chip, sysinfo.board)} diff --git a/lib/SvelteUi/include/AmsWebHeaders.h b/lib/SvelteUi/include/AmsWebHeaders.h index f7d9f60f..582e50ad 100644 --- a/lib/SvelteUi/include/AmsWebHeaders.h +++ b/lib/SvelteUi/include/AmsWebHeaders.h @@ -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\""; diff --git a/lib/SvelteUi/json/sysinfo.json b/lib/SvelteUi/json/sysinfo.json index 3f9c0c7e..a8aab8b8 100644 --- a/lib/SvelteUi/json/sysinfo.json +++ b/lib/SvelteUi/json/sysinfo.json @@ -2,6 +2,7 @@ "version": "%s", "chip": "%s", "chipId": "%s", + "cpu": %d, "mac": "%s", "apmac": "%s", "board": %d, diff --git a/lib/SvelteUi/scripts/generate_includes.py b/lib/SvelteUi/scripts/generate_includes.py index 0b75a3ce..f74c62bd 100644 --- a/lib/SvelteUi/scripts/generate_includes.py +++ b/lib/SvelteUi/scripts/generate_includes.py @@ -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(";"); \ No newline at end of file diff --git a/lib/SvelteUi/src/AmsWebServer.cpp b/lib/SvelteUi/src/AmsWebServer.cpp index 2772b089..c9399ef2 100644 --- a/lib/SvelteUi/src/AmsWebServer.cpp +++ b/lib/SvelteUi/src/AmsWebServer.cpp @@ -35,6 +35,7 @@ #if defined(ESP32) #include #include +#include #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; diff --git a/platformio.ini b/platformio.ini index 3f17ae5f..4310874b 100755 --- a/platformio.ini +++ b/platformio.ini @@ -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 diff --git a/src/AmsToMqttBridge.cpp b/src/AmsToMqttBridge.cpp index 686b6333..1c55c00f 100644 --- a/src/AmsToMqttBridge.cpp +++ b/src/AmsToMqttBridge.cpp @@ -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; } diff --git a/src/IEC6205621.cpp b/src/IEC6205621.cpp index 79def1a5..0d0c7851 100644 --- a/src/IEC6205621.cpp +++ b/src/IEC6205621.cpp @@ -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"))) { diff --git a/src/IEC6205675.cpp b/src/IEC6205675.cpp index fb3ffdd2..1922abfe 100644 --- a/src/IEC6205675.cpp +++ b/src/IEC6205675.cpp @@ -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; } diff --git a/src/IEC6205675.h b/src/IEC6205675.h index d3b2aa10..dba6c333 100644 --- a/src/IEC6205675.h +++ b/src/IEC6205675.h @@ -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); diff --git a/src/LNG.cpp b/src/LNG.cpp index e536da50..faa153ea 100644 --- a/src/LNG.cpp +++ b/src/LNG.cpp @@ -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(); } } } diff --git a/src/LNG2.cpp b/src/LNG2.cpp index d7d85c41..7640954c 100644 --- a/src/LNG2.cpp +++ b/src/LNG2.cpp @@ -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(); } }