Compare commits

..

15 Commits

Author SHA1 Message Date
Gunnar Skjold
c49ff9f3ec Allow up to 8 cycles to charge capacitor 2026-02-27 09:06:19 +01:00
Gunnar Skjold
ae473cab20 Moved reset of reboot reason to main program 2026-02-27 08:55:10 +01:00
Gunnar Skjold
0b4884652f Allow for more errors during upgrade (#1139)
* Allow for more errors during upgrade

* More instead of equals
2026-02-12 12:28:31 +01:00
Gunnar Skjold
82aeae8699 Fixed compile error for 8266 after #1121 (#1138) 2026-02-12 09:45:04 +01:00
Gunnar Skjold
a7333653b0 Fixed decimal accuracy on saved values (#1133) 2026-02-12 08:25:32 +01:00
Gunnar Skjold
24e63d5e32 Fixed HA object id (#1132) 2026-02-12 08:25:17 +01:00
Gunnar Skjold
eb7c266378 Fixed double slash on Wiki links (#1123) 2026-02-12 08:25:04 +01:00
Gunnar Skjold
cf8c48ab99 Added code to ensure stable boot (#1121)
* If BUS powered, wait for capacitor to charge on boot, this ensures better boot stability

* Some cleanup
2026-02-12 08:24:50 +01:00
Gunnar Skjold
78a1cd78ea Added support for a new format for a Iskra meter in Switzerland (#1118) 2026-02-12 08:24:26 +01:00
Gunnar Skjold
fdfa6c1b52 Fixed MQTT JSON for prices (#1116) 2026-01-01 20:07:48 +01:00
Gunnar Skjold
4f1790a464 Added support for Iskraemeco IE.5 in Croatia (#1107)
* Added support for Croation Iskra

* Temp removed meterid

* Fixed HDLC block decoding

* Fixed context length

* Changing some stuff back

* Change some stuff back

* Final test

* Added debugging

* Updated selector for iskra dataformat

* Added fake test frame
2025-12-30 10:12:31 +01:00
Gunnar Skjold
ca4cef5233 Fixed empty timestamp in Home-Assistant JSON (#1105)
* Nullable timestamps for HA JSON

* Nullable timestamps for MQTT JSON
2025-12-30 10:05:39 +01:00
Gunnar Skjold
a0d7fd0d95 Fixed extraction of negative prices from server (#1104) 2025-12-30 10:04:55 +01:00
Gunnar Skjold
489dbf9254 Fixed IPv6 formatting (#1106) 2025-12-30 10:04:30 +01:00
Gunnar Skjold
a81aa11558 Added support for frames without checksum (#1108) 2025-12-29 13:25:36 +01:00
26 changed files with 524 additions and 211 deletions

48
frames/iskra_croatia.txt Normal file
View File

@@ -0,0 +1,48 @@
They actually use multiple frames, so this is a "fake" frame combining the two into one, but without checksum fields.
7E
A0 BD
CF 02 23 03 00 00
E6 E7 00
0F 00 03 46 3B
0C 07 E9 0C 13 05 17 37 28 00 FF C4 00
02 21
09 08 39 32 30 32 39 36 39 31
09 04 17 37 28 00
09 05 07 E9 0C 13 05
06 00 6C 28 5A
06 00 4B 76 1A
06 00 20 B2 40
06 00 58 68 AA
06 00 57 A1 62
06 00 00 C7 48
06 00 17 EE D7
06 00 12 F5 5C
06 00 00 D9 6A
06 00 15 36 84
06 00 00 01 7E
06 00 00 00 00
12 03 79
06 00 00 00 7F
06 00 00 00 BD
06 00 00 00 41
06 00 00 00 00
06 00 00 00 00
06 00 00 00 00
12 09 54
12 09 35
12 09 49
12 00 37
12 00 59
12 00 4D
06 00 00 43 62
01 01
12 24 B8
01 01
12 24 B8
01 01
12 24 B8
03 01
00 00 7E

View File

@@ -591,7 +591,6 @@ void AmsConfiguration::clearGpio(GpioConfig& config, bool all) {
config.tempAnalogSensorPin = 0xFF;
config.vccPin = 0xFF;
config.ledDisablePin = 0xFF;
config.powersaving = 0;
if(all) {
config.vccOffset = 0;
@@ -600,6 +599,7 @@ void AmsConfiguration::clearGpio(GpioConfig& config, bool all) {
config.vccResistorGnd = 0;
config.vccResistorVcc = 0;
config.ledBehaviour = LED_BEHAVIOUR_DEFAULT;
config.powersaving = 0;
}
}

View File

@@ -74,7 +74,7 @@ int8_t DSMRParser::parse(uint8_t *buf, DataParserContext &ctx, bool verified, Pr
fromHex((uint8_t*) &crc, String((char*) buf+crcPos), 2);
crc = ntohs(crc);
if(crc != crc_calc) {
if(crc > 0 && crc != crc_calc) {
if(debugger != NULL) {
debugger->printf_P(PSTR("CRC incorrrect, %04X != %04X at position %lu\n"), crc, crc_calc, crcPos);
}

View File

@@ -32,7 +32,7 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
return DATA_PARSE_BOUNDARY_FLAG_MISSING;
// Verify FCS
if(ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
if(f->fcs > 0 && ntohs(f->fcs) != crc16_x25(d + 1, len - sizeof *f - 1))
return DATA_PARSE_FOOTER_CHECKSUM_ERROR;
// Skip destination address, LSB marks last byte
@@ -50,7 +50,7 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
HDLC3CtrlHcs* t3 = (HDLC3CtrlHcs*) (ptr);
// Verify HCS
if(ntohs(t3->hcs) != crc16_x25(d + 1, ptr-d))
if(t3->hcs > 0 && ntohs(t3->hcs) != crc16_x25(d + 1, ptr-d))
return DATA_PARSE_HEADER_CHECKSUM_ERROR;
ptr += 3;
@@ -69,7 +69,12 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
if(buf == NULL) return DATA_PARSE_FAIL;
memcpy(buf + pos, ptr+3, ctx.length); // +3 to skip LLC
if((*ptr) == DATA_TAG_LLC) {
ptr += 3; // Skip LLC
ctx.length -= 3;
}
memcpy(buf + pos, ptr, ctx.length);
pos += ctx.length;
lastSequenceNumber++;
@@ -78,7 +83,12 @@ int8_t HDLCParser::parse(uint8_t *d, DataParserContext &ctx) {
lastSequenceNumber = 0;
if(buf == NULL) return DATA_PARSE_FAIL;
memcpy(buf + pos, ptr+3, ctx.length); // +3 to skip LLC
if((*ptr) == DATA_TAG_LLC) {
ptr += 3; // Skip LLC
ctx.length -= 3;
}
memcpy(buf + pos, ptr, ctx.length);
pos += ctx.length;
memcpy((uint8_t *) d, buf, pos);

View File

@@ -39,6 +39,8 @@
#define AMS_UPDATE_ERR_SUCCESS_CONFIRMED 123
#define UPDATE_BUF_SIZE 4096
#define UPDATE_MAX_BLOCK_RETRY 25
#define UPDATE_MAX_REBOOT_RETRY 12
class AmsFirmwareUpdater {
public:

View File

@@ -74,7 +74,7 @@ void AmsFirmwareUpdater::setUpgradeInformation(UpgradeInformation& upinfo) {
#endif
debugger->printf_P(PSTR("Resuming uprade to %s\n"), updateStatus.toVersion);
if(updateStatus.reboot_count++ < 8) {
if(updateStatus.reboot_count++ < UPDATE_MAX_REBOOT_RETRY) {
updateStatus.errorCode = AMS_UPDATE_ERR_OK;
} else {
updateStatus.errorCode = AMS_UPDATE_ERR_REBOOT;
@@ -129,7 +129,7 @@ void AmsFirmwareUpdater::loop() {
HTTPClient http;
start = millis();
if(!fetchFirmwareChunk(http)) {
if(updateStatus.retry_count++ == 3) {
if(updateStatus.retry_count++ > UPDATE_MAX_BLOCK_RETRY) {
updateStatus.errorCode = AMS_UPDATE_ERR_FETCH;
updateStatusChanged = true;
}

View File

@@ -79,6 +79,7 @@ private:
void publishPriceSensors(PriceService* ps);
void publishSystemSensors();
void publishThresholdSensors();
void toJsonIsoTimestamp(time_t t, char* buf, size_t buflen);
String boardTypeToString(uint8_t b) {
switch(b) {

View File

@@ -1,4 +1,4 @@
{
"P" : %lu,
"t" : "%s"
"t" : %s
}

View File

@@ -3,6 +3,6 @@
"tPO" : %.3f,
"tQI" : %.3f,
"tQO" : %.3f,
"rtc" : "%s",
"t" : "%s"
"rtc" : %s,
"t" : %s
}

View File

@@ -12,5 +12,5 @@
"U1" : %.2f,
"U2" : %.2f,
"U3" : %.2f,
"t" : "%s"
"t" : %s
}

View File

@@ -28,5 +28,5 @@
"tPO1" : %.3f,
"tPO2" : %.3f,
"tPO3" : %.3f,
"t" : "%s"
"t" : %s
}

View File

@@ -2,7 +2,7 @@
"name" : "%s%s",
"stat_t" : "%s%s",
"uniq_id" : "%s_%s",
"obj_id" : "%s_%s",
"default_entity_id" : "sensor.%s_%s",
"val_tpl" : "{{ value_json.%s | is_defined }}",
"expire_after" : %d,
"dev" : {

View File

@@ -28,7 +28,8 @@ void HomeAssistantMqttHandler::setHomeAssistantConfig(HomeAssistantConfig config
snprintf_P(json, 128, PSTR("[%s] "), config.discoveryNameTag);
sensorNamePrefix = String(json);
} else {
deviceName = F("AMS reader");
snprintf_P(json, 128, PSTR("AMS reader"));
deviceName = String(json);
sensorNamePrefix = "";
}
deviceModel = boardTypeToString(boardType);
@@ -52,20 +53,18 @@ void HomeAssistantMqttHandler::setHomeAssistantConfig(HomeAssistantConfig config
deviceUrl = String(json);
}
if(strlen(config.discoveryPrefix) > 0) {
snprintf_P(json, 128, PSTR("%s/status"), config.discoveryPrefix);
statusTopic = String(json);
snprintf_P(json, 128, PSTR("%s/sensor"), config.discoveryPrefix);
sensorTopic = String(json);
snprintf_P(json, 128, PSTR("%s/update"), config.discoveryPrefix);
updateTopic = String(json);
} else {
statusTopic = F("homeassistant/status");
sensorTopic = F("homeassistant/sensor");
updateTopic = F("homeassistant/update");
if(strlen(config.discoveryPrefix) == 0) {
snprintf_P(config.discoveryPrefix, 64, PSTR("homeassistant"));
}
snprintf_P(json, 128, PSTR("%s/status"), config.discoveryPrefix);
statusTopic = String(json);
snprintf_P(json, 128, PSTR("%s/sensor"), config.discoveryPrefix);
sensorTopic = String(json);
snprintf_P(json, 128, PSTR("%s/update"), config.discoveryPrefix);
updateTopic = String(json);
strcpy(this->mqttConfig.subscribeTopic, statusTopic.c_str());
}
@@ -134,12 +133,7 @@ bool HomeAssistantMqttHandler::publishList1(AmsData* data, EnergyAccounting* ea)
publishList1Sensors();
char pt[24];
memset(pt, 0, 24);
if(data->getPackageTimestamp() > 0) {
tmElements_t tm;
breakTime(data->getPackageTimestamp(), tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
snprintf_P(json, BufferSize, HA1_JSON, data->getActiveImportPower(), pt);
return mqtt.publish(pubTopic + "/power", json);
@@ -150,12 +144,7 @@ bool HomeAssistantMqttHandler::publishList2(AmsData* data, EnergyAccounting* ea)
if(data->getActiveExportPower() > 0) publishList2ExportSensors();
char pt[24];
memset(pt, 0, 24);
if(data->getPackageTimestamp() > 0) {
tmElements_t tm;
breakTime(data->getPackageTimestamp(), tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
snprintf_P(json, BufferSize, HA3_JSON,
data->getListId().c_str(),
@@ -181,20 +170,11 @@ bool HomeAssistantMqttHandler::publishList3(AmsData* data, EnergyAccounting* ea)
if(data->getActiveExportCounter() > 0.0) publishList3ExportSensors();
char mt[24];
memset(mt, 0, 24);
if(data->getMeterTimestamp() > 0) {
tmElements_t tm;
breakTime(data->getMeterTimestamp(), tm);
sprintf_P(mt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
toJsonIsoTimestamp(data->getMeterTimestamp(), mt, sizeof(mt));
char pt[24];
memset(pt, 0, 24);
if(data->getPackageTimestamp() > 0) {
tmElements_t tm;
breakTime(data->getPackageTimestamp(), tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
snprintf_P(json, BufferSize, HA2_JSON,
data->getActiveImportCounter(),
@@ -212,12 +192,7 @@ bool HomeAssistantMqttHandler::publishList4(AmsData* data, EnergyAccounting* ea)
if(data->getL1ActiveExportPower() > 0 || data->getL2ActiveExportPower() > 0 || data->getL3ActiveExportPower() > 0) publishList4ExportSensors();
char pt[24];
memset(pt, 0, 24);
if(data->getPackageTimestamp() > 0) {
tmElements_t tm;
breakTime(data->getPackageTimestamp(), tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
toJsonIsoTimestamp(data->getPackageTimestamp(), pt, sizeof(pt));
snprintf_P(json, BufferSize, HA4_JSON,
data->getListId().c_str(),
@@ -307,13 +282,8 @@ bool HomeAssistantMqttHandler::publishRealtime(AmsData* data, EnergyAccounting*
time_t now = time(nullptr);
char pt[24];
memset(pt, 0, 24);
if(now > 0) {
tmElements_t tm;
breakTime(now, tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":\"%s\""), pt);
toJsonIsoTimestamp(now, pt, sizeof(pt));
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":%s"), pt);
json[pos++] = '}';
json[pos] = '\0';
@@ -343,13 +313,8 @@ bool HomeAssistantMqttHandler::publishTemperatures(AmsConfiguration* config, HwT
time_t now = time(nullptr);
char pt[24];
memset(pt, 0, 24);
if(now > 0) {
tmElements_t tm;
breakTime(now, tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":\"%s\""), pt);
toJsonIsoTimestamp(now, pt, sizeof(pt));
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":%s"), pt);
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("}"));
bool ret = mqtt.publish(pubTopic + "/temperatures", json);
@@ -421,25 +386,34 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
memset(ts1hr, 0, 24);
if(min1hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
tmElements_t tm;
tmElements_t tm;
breakTime(ts, tm);
sprintf_P(ts1hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
tm.Minute = 0;
tm.Second = 0;
ts = makeTime(tm);
toJsonIsoTimestamp(ts, ts1hr, sizeof(ts1hr));
}
char ts3hr[24];
memset(ts3hr, 0, 24);
if(min3hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
tmElements_t tm;
tmElements_t tm;
breakTime(ts, tm);
sprintf_P(ts3hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
tm.Minute = 0;
tm.Second = 0;
ts = makeTime(tm);
toJsonIsoTimestamp(ts, ts3hr, sizeof(ts3hr));
}
char ts6hr[24];
memset(ts6hr, 0, 24);
if(min6hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
tmElements_t tm;
tmElements_t tm;
breakTime(ts, tm);
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
tm.Minute = 0;
tm.Second = 0;
ts = makeTime(tm);
toJsonIsoTimestamp(ts, ts6hr, sizeof(ts6hr));
}
uint16_t pos = snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"prices\":{\"import\":["), WiFi.macAddress().c_str());
@@ -468,7 +442,7 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
}
pos--;
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("],\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}"),
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("],\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":%s,\"cheapest3hr\":%s,\"cheapest6hr\":%s}"),
min == INT16_MAX ? 0.0 : min,
max == INT16_MIN ? 0.0 : max,
ts1hr,
@@ -477,13 +451,8 @@ bool HomeAssistantMqttHandler::publishPrices(PriceService* ps) {
);
char pt[24];
memset(pt, 0, 24);
if(now > 0) {
tmElements_t tm;
breakTime(now, tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":\"%s\""), pt);
toJsonIsoTimestamp(now, pt, sizeof(pt));
pos += snprintf_P(json+pos, BufferSize-pos, PSTR(",\"t\":%s"), pt);
json[pos++] = '}';
json[pos] = '\0';
@@ -502,14 +471,9 @@ bool HomeAssistantMqttHandler::publishSystem(HwTools* hw, PriceService* ps, Ener
time_t now = time(nullptr);
char pt[24];
memset(pt, 0, 24);
if(now > 0) {
tmElements_t tm;
breakTime(now, tm);
sprintf_P(pt, PSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
}
toJsonIsoTimestamp(now, pt, sizeof(pt));
snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\",\"t\":\"%s\"}"),
snprintf_P(json, BufferSize, PSTR("{\"id\":\"%s\",\"name\":\"%s\",\"up\":%d,\"vcc\":%.3f,\"rssi\":%d,\"temp\":%.2f,\"version\":\"%s\",\"t\":%s}"),
WiFi.macAddress().c_str(),
mqttConfig.clientId,
(uint32_t) (millis64()/1000),
@@ -902,3 +866,14 @@ void HomeAssistantMqttHandler::onMessage(String &topic, String &payload) {
}
}
}
void HomeAssistantMqttHandler::toJsonIsoTimestamp(time_t t, char* buf, size_t buflen) {
memset(buf, 0, buflen);
if(t > 0) {
tmElements_t tm;
breakTime(t, tm);
snprintf_P(buf, buflen, PSTR("\"%04d-%02d-%02dT%02d:%02d:%02dZ\""), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
} else {
snprintf_P(buf, buflen, PSTR("null"));
}
}

View File

@@ -45,6 +45,7 @@ public:
bool applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterConfig& meterConfig, uint8_t hanPin);
void setup(SystemConfig* sys, GpioConfig* gpio);
float getVcc();
void setMaxVcc(float maxVcc);
uint8_t getTempSensorCount();
TempSensorData* getTempSensorData(uint8_t);
bool updateTemperatures();
@@ -68,7 +69,7 @@ private:
uint8_t vccPin, vccGnd_r, vccVcc_r;
float vccOffset, vccMultiplier;
float vcc = 3.3; // Last known Vcc
float maxVcc = 3.25; // Best to have this close to max as a start, in case Pow-U reboots and starts off with a low voltage, we dont want that to be perceived as max
float maxVcc = 3.28; // Best to have this close to max as a start, in case Pow-U reboots and starts off with a low voltage, we dont want that to be perceived as max
unsigned long lastVccRead = 0;
uint16_t analogRange = 1024;

View File

@@ -677,4 +677,8 @@ bool HwTools::isVoltageOptimal(float range) {
uint8_t HwTools::getBoardType() {
return boardType;
}
void HwTools::setMaxVcc(float vcc) {
this->maxVcc = min(3.3f, vcc);
}

View File

@@ -42,5 +42,6 @@ private:
bool publishList3(AmsData* data, EnergyAccounting* ea);
bool publishList4(AmsData* data, EnergyAccounting* ea);
String getMeterModel(AmsData* data);
void toJsonIsoTimestamp(time_t t, char* buf, size_t buflen);
};
#endif

View File

@@ -356,25 +356,34 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) {
memset(ts1hr, 0, 24);
if(min1hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min1hrIdx);
tmElements_t tm;
tmElements_t tm;
breakTime(ts, tm);
sprintf_P(ts1hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
tm.Minute = 0;
tm.Second = 0;
ts = makeTime(tm);
toJsonIsoTimestamp(ts, ts1hr, sizeof(ts1hr));
}
char ts3hr[24];
memset(ts3hr, 0, 24);
if(min3hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min3hrIdx);
tmElements_t tm;
tmElements_t tm;
breakTime(ts, tm);
sprintf_P(ts3hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
tm.Minute = 0;
tm.Second = 0;
ts = makeTime(tm);
toJsonIsoTimestamp(ts, ts3hr, sizeof(ts3hr));
}
char ts6hr[24];
memset(ts6hr, 0, 24);
if(min6hrIdx > -1) {
time_t ts = now + (SECS_PER_HOUR * min6hrIdx);
tmElements_t tm;
tmElements_t tm;
breakTime(ts, tm);
sprintf_P(ts6hr, PSTR("%04d-%02d-%02dT%02d:00:00Z"), tm.Year+1970, tm.Month, tm.Day, tm.Hour);
tm.Minute = 0;
tm.Second = 0;
ts = makeTime(tm);
toJsonIsoTimestamp(ts, ts6hr, sizeof(ts6hr));
}
if(mqttConfig.payloadFormat == 6) {
@@ -388,7 +397,7 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) {
}
}
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"pr_min\":%.4f,\"pr_max\":%.4f,\"pr_cheapest1hr\":\"%s\",\"pr_cheapest3hr\":\"%s\",\"pr_cheapest6hr\":\"%s\"}"),
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("\"pr_min\":%.4f,\"pr_max\":%.4f,\"pr_cheapest1hr\":%s,\"pr_cheapest3hr\":%s,\"pr_cheapest6hr\":%s}"),
min == INT16_MAX ? 0.0 : min,
max == INT16_MIN ? 0.0 : max,
ts1hr,
@@ -422,7 +431,7 @@ bool JsonMqttHandler::publishPrices(PriceService* ps) {
}
pos--;
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("],\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":\"%s\",\"cheapest3hr\":\"%s\",\"cheapest6hr\":\"%s\"}}"),
pos += snprintf_P(json+pos, BufferSize-pos, PSTR("],\"min\":%.4f,\"max\":%.4f,\"cheapest1hr\":%s,\"cheapest3hr\":%s,\"cheapest6hr\":%s}}"),
min == INT16_MAX ? 0.0 : min,
max == INT16_MIN ? 0.0 : max,
ts1hr,
@@ -535,3 +544,14 @@ void JsonMqttHandler::onMessage(String &topic, String &payload) {
}
}
}
void JsonMqttHandler::toJsonIsoTimestamp(time_t t, char* buf, size_t buflen) {
memset(buf, 0, buflen);
if(t > 0) {
tmElements_t tm;
breakTime(t, tm);
snprintf_P(buf, buflen, PSTR("\"%04d-%02d-%02dT%02d:%02d:%02dZ\""), tm.Year+1970, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second);
} else {
snprintf_P(buf, buflen, PSTR("null"));
}
}

View File

@@ -25,16 +25,206 @@ IEC6205675::IEC6205675(const char* d, Timezone* tz, uint8_t useMeterType, MeterC
if(val == NOVALUE) {
CosemData* data = getCosemDataAt(1, ((char *) (d)));
// Kaifa special case...
if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) {
this->packageTimestamp = this->packageTimestamp > 0 && tz != NULL ? tz->toUTC(this->packageTimestamp) : 0;
if(useMeterType == AmsTypeIskra) { // Iskra special case
meterType = AmsTypeIskra;
uint8_t idx = 0;
data = getCosemDataAt(idx, ((char *) (d)));
if(data->base.length == 0x21) {
idx = 4;
// 1.8.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
// 1.8.1
// 1.8.2
idx += 2;
// 2.8.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
// 2.8.1
// 2.8.2
idx += 2;
// 5.8.0
// 6.8.0
// 7.8.0
// 8.8.0
idx += 4;
// 1.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportPower = ntohl(data->dlu.data);
// 2.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportPower = ntohl(data->dlu.data);
// 13.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
powerFactor= ntohl(data->dlu.data) / 1000.0;
// 21.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1activeImportPower = ntohl(data->dlu.data);
// 41.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l2activeImportPower = ntohl(data->dlu.data);
// 61.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l3activeImportPower = ntohl(data->dlu.data);
// 22.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1activeExportPower = ntohl(data->dlu.data);
// 42.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l2activeExportPower = ntohl(data->dlu.data);
// 62.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l3activeExportPower = ntohl(data->dlu.data);
// 32.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1voltage = ntohs(data->lu.data) / 10.0;
// 52.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l2voltage = ntohs(data->lu.data) / 10.0;
// 72.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l3voltage = ntohs(data->lu.data) / 10.0;
// 31.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l1current = ntohs(data->lu.data) / 100.0;
// 51.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l2current = ntohs(data->lu.data) / 100.0;
// 71.7.0
data = getCosemDataAt(idx++, ((char *) (d)));
l3current = ntohs(data->lu.data) / 100.0;
listType = 4;
lastUpdateMillis = millis64();
} else if(data->base.length == 0x0F) {
idx = 1;
// 1.8.0 ?
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
// 1.8.1 ?
// 1.8.2 ?
idx += 2;
// 2.8.0 ?
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
// 2.8.1 ?
// 2.8.2 ?
idx += 2;
idx++; // Unknown empty octet string
CosemData* meterTs = getCosemDataAt(idx++, ((char *) (d)));
if(meterTs != NULL) {
AmsOctetTimestamp* amst = (AmsOctetTimestamp*) meterTs;
time_t ts = decodeCosemDateTime(amst->dt);
meterTimestamp = ts;
}
// 2.7.0 ?
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportPower = ntohl(data->dlu.data);
// 1.7.0 ?
data = getCosemDataAt(idx++, ((char *) (d)));
activeImportPower = ntohl(data->dlu.data);
// 31.7.0 ?
data = getCosemDataAt(idx++, ((char *) (d)));
l1current = ntohs(data->lu.data) / 100.0;
// 51.7.0 ?
data = getCosemDataAt(idx++, ((char *) (d)));
l2current = ntohs(data->lu.data) / 100.0;
// 71.7.0 ?
data = getCosemDataAt(idx++, ((char *) (d)));
l3current = ntohs(data->lu.data) / 100.0;
// 32.7.0 ?
data = getCosemDataAt(idx++, ((char *) (d)));
l1voltage = ntohs(data->lu.data) / 10.0;
// 72.7.0 ?
data = getCosemDataAt(idx++, ((char *) (d)));
l3voltage = ntohs(data->lu.data) / 10.0;
// 52.7.0 missing?
l2voltage = sqrt(pow(l1voltage - l3voltage * cos(60 * (PI/180)), 2) + pow(l3voltage * sin(60 * (PI/180)),2));
listType = 3;
lastUpdateMillis = millis64();
} else {
idx = 5;
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeImportPower = ntohl(data->dlu.data);
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeExportPower = ntohl(data->dlu.data);
}
uint8_t str_len = 0;
str_len = getString(AMS_OBIS_UNKNOWN_1, sizeof(AMS_OBIS_UNKNOWN_1), ((char *) (d)), str);
if(str_len > 0) {
meterId = String(str);
}
listType = 4;
lastUpdateMillis = millis64();
}
} else if(useMeterType == AmsTypeKaifa && data->base.type == CosemTypeDLongUnsigned) { // Kaifa special case
listType = 1;
meterType = AmsTypeKaifa;
activeImportPower = ntohl(data->dlu.data);
lastUpdateMillis = millis64();
} else if(data->base.type == CosemTypeOctetString) {
this->packageTimestamp = this->packageTimestamp > 0 && tz != NULL ? tz->toUTC(this->packageTimestamp) : 0;
} else if(data->base.type == CosemTypeOctetString) { // Assuming first string is a list identifier
memcpy(str, data->oct.data, data->oct.length);
str[data->oct.length] = 0x00;
String listId = String(str);
@@ -42,7 +232,7 @@ IEC6205675::IEC6205675(const char* d, Timezone* tz, uint8_t useMeterType, MeterC
this->listId = listId;
meterType = AmsTypeKaifa;
int idx = 0;
uint8_t idx = 0;
data = getCosemDataAt(idx, ((char *) (d)));
idx+=2;
if(data->base.length == 0x0D || data->base.length == 0x12) {
@@ -144,7 +334,7 @@ IEC6205675::IEC6205675(const char* d, Timezone* tz, uint8_t useMeterType, MeterC
this->listId = listId;
meterType = AmsTypeIskra;
int idx = 0;
uint8_t idx = 0;
data = getCosemDataAt(idx++, ((char *) (d)));
if(data->base.length == 0x12) {
apply(state);
@@ -559,49 +749,31 @@ IEC6205675::IEC6205675(const char* d, Timezone* tz, uint8_t useMeterType, MeterC
data = getCosemDataAt(idx++, ((char *) (d)));
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
}
} else if(useMeterType == AmsTypeIskra && data->base.type == CosemTypeOctetString) { // Iskra special case
}
}
if(meterType == AmsTypeUnknown && useMeterType == AmsTypeUnknown) {
debugger->println("AMS unknown meter type, trying to identify...");
CosemData* d1 = getCosemDataAt(1, ((char *) (d)));
CosemData* d2 = getCosemDataAt(2, ((char *) (d)));
CosemData* d3 = getCosemDataAt(3, ((char *) (d)));
CosemData* d7 = getCosemDataAt(7, ((char *) (d)));
CosemData* d8 = getCosemDataAt(8, ((char *) (d)));
if(d1->base.type == CosemTypeDLongUnsigned &&
d2->base.type == CosemTypeDLongUnsigned &&
d3->base.type == CosemTypeDLongUnsigned &&
d7->base.type == CosemTypeOctetString &&
d8->base.type == CosemTypeOctetString
) {
meterType = AmsTypeIskra;
uint8_t idx = 5;
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeImportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeExportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
reactiveImportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
reactiveExportCounter = ntohl(data->dlu.data) / 1000.0;
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeImportPower = ntohl(data->dlu.data);
}
data = getCosemDataAt(idx++, ((char *) (d)));
if(data != NULL) {
activeExportPower = ntohl(data->dlu.data);
}
uint8_t str_len = 0;
str_len = getString(AMS_OBIS_UNKNOWN_1, sizeof(AMS_OBIS_UNKNOWN_1), ((char *) (d)), str);
if(str_len > 0) {
meterId = String(str);
}
listType = 3;
lastUpdateMillis = millis64();
} else if(useMeterType == AmsTypeUnknown) {
listType = 3;
} else if(d1->base.type == CosemTypeOctetString && d2->base.type == CosemTypeOctetString && d3->base.type == CosemTypeOctetString) {
meterType = AmsTypeIskra;
lastUpdateMillis = millis64();
listType = 3;
} else {
uint8_t str_len = 0;
str_len = getString(AMS_OBIS_UNKNOWN_1, sizeof(AMS_OBIS_UNKNOWN_1), ((char *) (d)), str);
if(str_len > 0) {
@@ -612,7 +784,7 @@ IEC6205675::IEC6205675(const char* d, Timezone* tz, uint8_t useMeterType, MeterC
}
}
}
} else {
} else { // OBIS code parsing
listType = 1;
activeImportPower = val;

View File

@@ -574,7 +574,8 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
for(uint8_t i = 0; i < ret->getNumberOfPoints(); i++) {
// To avoid alignment issues on ESP8266, we use memcpy
memcpy(&intval, &points[i], sizeof(int32_t));
float value = ntohl(intval) / 10000.0;
intval = ntohl(intval); // Change byte order before converting to float, to support negative values
float value = intval / 10000.0;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif
@@ -585,7 +586,8 @@ PricesContainer* PriceService::fetchPrices(time_t t) {
for(uint8_t i = 0; i < ret->getNumberOfPoints(); i++) {
// To avoid alignment issues on ESP8266, we use memcpy
memcpy(&intval, &points[ret->getNumberOfPoints()+i], sizeof(int32_t));
float value = ntohl(intval) / 10000.0;
intval = ntohl(intval); // Change byte order before converting to float, to support negative values
float value = intval / 10000.0;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::VERBOSE))
#endif

File diff suppressed because one or more lines are too long

View File

@@ -9,7 +9,8 @@
"version": "0.0.0",
"dependencies": {
"cssnano": "^5.1.15",
"esbuild": ">=0.25.0"
"esbuild": ">=0.25.0",
"ipaddr.js": "^2.3.0"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.1.0",
@@ -1584,6 +1585,14 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/ipaddr.js": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz",
"integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==",
"engines": {
"node": ">= 10"
}
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",

View File

@@ -10,10 +10,10 @@
"preview": "vite preview"
},
"overrides": {
"svelte-navigator": {
"svelte": ">=4.x"
}
},
"svelte-navigator": {
"svelte": ">=4.x"
}
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^2.1.0",
"@tailwindcss/forms": "^0.5.3",
@@ -30,6 +30,7 @@
},
"dependencies": {
"cssnano": "^5.1.15",
"esbuild": ">=0.25.0"
"esbuild": ">=0.25.0",
"ipaddr.js": "^2.3.0"
}
}

View File

@@ -130,7 +130,7 @@ export function uiVisibility(choice, state) {
}
export function wiki(page) {
let ret = "https://wiki.amsleser.no/";
let ret = "https://wiki.amsleser.no";
if(page) {
ret += "/en/firmware#" + page;
}

View File

@@ -7,6 +7,7 @@
import Clock from './Clock.svelte';
import Mask from './Mask.svelte';
import { scanForDevice } from './Helpers.js';
import ipaddr from 'ipaddr.js';
export let data;
export let sysinfo;
@@ -207,11 +208,11 @@
</div>
{#if sysinfo.net.ipv6}
<div class="my-2">
IPv6: <span style="font-size: 14px;">{sysinfo.net.ipv6.replace(/\b:?(?:0+:?){2,}/, '::')}</span>
IPv6: <span style="font-size: 14px;">{ipaddr.parse(sysinfo.net.ipv6)}</span>
</div>
<div class="my-2">
{#if sysinfo.net.dns1v6}DNSv6: <span style="font-size: 14px;">{sysinfo.net.dns1v6.replace(/\b:?(?:0+:?){2,}/, '::')}</span>{/if}
{#if sysinfo.net.dns2v6}DNSv6: <span style="font-size: 14px;">{sysinfo.net.dns2v6.replace(/\b:?(?:0+:?){2,}/, '::')}</span>{/if}
{#if sysinfo.net.dns1v6}DNSv6: <span style="font-size: 14px;">{ipaddr.parse(sysinfo.net.dns1v6)}</span>{/if}
{#if sysinfo.net.dns2v6}DNSv6: <span style="font-size: 14px;">{ipaddr.parse(sysinfo.net.dns2v6)}</span>{/if}
</div>
{/if}
</div>

View File

@@ -61,14 +61,6 @@ AmsWebServer::AmsWebServer(uint8_t* buf, Stream* Debug, HwTools* hw, ResetDataCo
this->hw = hw;
this->buf = (char*) buf;
this->rdc = rdc;
if(rdc->magic != 0x4a) {
rdc->last_cause = 0;
rdc->cause = 0;
rdc->magic = 0x4a;
} else {
rdc->last_cause = rdc->cause;
rdc->cause = 0;
}
}
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, AmsData* meterState, AmsDataStorage* ds, EnergyAccounting* ea, RealtimePlot* rtp, AmsFirmwareUpdater* updater) {
@@ -1389,10 +1381,10 @@ void AmsWebServer::handleSave() {
memset(meterConfig.authenticationKey, 0, 16);
}
meterConfig.wattageMultiplier = server.arg(F("mmw")).toFloat() * 1000;
meterConfig.voltageMultiplier = server.arg(F("mmv")).toFloat() * 1000;
meterConfig.amperageMultiplier = server.arg(F("mma")).toFloat() * 1000;
meterConfig.accumulatedMultiplier = server.arg(F("mmc")).toFloat() * 1000;
meterConfig.wattageMultiplier = server.arg(F("mmw")).toDouble() * 1000.0;
meterConfig.voltageMultiplier = server.arg(F("mmv")).toDouble() * 1000.0;
meterConfig.amperageMultiplier = server.arg(F("mma")).toDouble() * 1000.0;
meterConfig.accumulatedMultiplier = server.arg(F("mmc")).toDouble() * 1000.0;
config->setMeterConfig(meterConfig);
}
@@ -1408,7 +1400,7 @@ void AmsWebServer::handleSave() {
if(!psk.equals("***")) {
strcpy(network.psk, psk.c_str());
}
network.power = server.arg(F("ww")).toFloat() * 10;
network.power = server.arg(F("ww")).toDouble() * 10.0;
network.sleep = server.arg(F("wz")).toInt();
network.use11b = server.hasArg(F("wb")) && server.arg(F("wb")) == F("true");
}
@@ -1569,9 +1561,9 @@ void AmsWebServer::handleSave() {
}
if(server.hasArg(F("iv")) && server.arg(F("iv")) == F("true")) {
gpioConfig->vccOffset = server.hasArg(F("ivo")) && !server.arg(F("ivo")).isEmpty() ? server.arg(F("ivo")).toFloat() * 100 : 0;
gpioConfig->vccMultiplier = server.hasArg(F("ivm")) && !server.arg(F("ivm")).isEmpty() ? server.arg(F("ivm")).toFloat() * 1000 : 1000;
gpioConfig->vccBootLimit = server.hasArg(F("ivb")) && !server.arg(F("ivb")).isEmpty() ? server.arg(F("ivb")).toFloat() * 10 : 0;
gpioConfig->vccOffset = server.hasArg(F("ivo")) && !server.arg(F("ivo")).isEmpty() ? server.arg(F("ivo")).toDouble() * 100.0 : 0;
gpioConfig->vccMultiplier = server.hasArg(F("ivm")) && !server.arg(F("ivm")).isEmpty() ? server.arg(F("ivm")).toDouble() * 1000.0 : 1000;
gpioConfig->vccBootLimit = server.hasArg(F("ivb")) && !server.arg(F("ivb")).isEmpty() ? server.arg(F("ivb")).toDouble() * 10.0 : 0;
config->setGpioConfig(*gpioConfig);
}
@@ -1686,7 +1678,7 @@ void AmsWebServer::handleSave() {
snprintf_P(buf, BufferSize, PSTR("rd%d"), i);
pc.direction = server.arg(buf).toInt();
snprintf_P(buf, BufferSize, PSTR("rv%d"), i);
pc.value = server.arg(buf).toFloat() * 10000;
pc.value = server.arg(buf).toDouble() * 10000.0;
snprintf_P(buf, BufferSize, PSTR("rn%d"), i);
String name = server.arg(buf);
strcpy(pc.name, name.c_str());

View File

@@ -30,6 +30,7 @@ ADC_MODE(ADC_VCC);
#include "ZmartChargeCloudConnector.h"
#endif
#define MAX_BOOT_CYCLES 8
#define WDT_TIMEOUT 120
#if defined(SLOW_PROC_TRIGGER_MS)
#warning "Using predefined slow process trigger"
@@ -190,10 +191,13 @@ CloudConnector *cloud = NULL;
#if defined(ZMART_CHARGE)
ZmartChargeCloudConnector *zcloud = NULL;
#endif
#if defined(ESP32)
__NOINIT_ATTR EnergyAccountingRealtimeData rtd;
RTC_DATA_ATTR uint8_t bootcount = 0;
#else
EnergyAccountingRealtimeData rtd;
uint32_t bootcount = 0;
#endif
EnergyAccounting ea(&Debug, &rtd);
@@ -326,6 +330,31 @@ void rxerr(int err) {
}
#endif
uint8_t incrementBootCycleCounter(bool deepSleep) {
#if defined(ESP8266)
if(deepSleep) {
if(ESP.rtcUserMemoryRead(0, &bootcount, sizeof(bootcount))) {
bootcount++;
ESP.rtcUserMemoryWrite(0, &bootcount, sizeof(bootcount));
}
return bootcount;
} else {
return ++bootcount;
}
#else
return ++bootcount;
#endif
}
void resetBootCycleCounter(bool deepSleep) {
#if defined(ESP8266)
bootcount = 0;
if(deepSleep) {
ESP.rtcUserMemoryWrite(0, &bootcount, sizeof(bootcount));
}
#else
bootcount = 0;
#endif
}
void setup() {
Serial.begin(115200);
@@ -380,20 +409,6 @@ void setup() {
}
}
hw.ledBlink(LED_INTERNAL, 1);
hw.ledBlink(LED_RED, 1);
hw.ledBlink(LED_YELLOW, 1);
hw.ledBlink(LED_GREEN, 1);
hw.ledBlink(LED_BLUE, 1);
PriceServiceConfig price;
if(config.getPriceServiceConfig(price)) {
ps = new PriceService(&Debug);
ps->setup(price);
ws.setPriceService(ps);
}
ws.setPriceSettings(price.area, price.currency);
ea.setCurrency(price.currency);
bool shared = false;
Serial.flush();
Serial.end();
@@ -441,22 +456,61 @@ void setup() {
yield();
#endif
float vcc = hw.getVcc();
if(!hw.ledOn(LED_YELLOW)) {
hw.ledOn(LED_INTERNAL);
}
debugI_P(PSTR("AMS reader %s started"), FirmwareVersion::VersionString);
debugI_P(PSTR("Configuration version: %d, board type: %d"), config.getConfigVersion(), sysConfig.boardType);
float vcc = hw.getVcc();
debugI_P(PSTR("Voltage: %.2fV"), vcc);
float vccBootLimit = gpioConfig.vccBootLimit == 0 ? 0 : min(3.29, gpioConfig.vccBootLimit / 10.0); // Make sure it is never above 3.3v
if(vcc > 2.5 && vccBootLimit > 2.5 && vccBootLimit < 3.3 && (gpioConfig.apPin == 0xFF || digitalRead(gpioConfig.apPin) == HIGH)) { // Skip if user is holding AP button while booting (HIGH = button is released)
if (vcc < vccBootLimit) {
{
Debug.printf_P(PSTR("(setup) Voltage is too low (%.2f < %.2f), sleeping\n"), vcc, vccBootLimit);
bool deepSleep = false; // Disable for now, as it makes it difficult to debug why devices rebooted
#if defined(ESP32)
float allowedDrift = bootcount * 0.01;
#else
float allowedDrift = gpioConfig.vccBootLimit == 0 ? 0.05 : 3.3 - min(3.29, gpioConfig.vccBootLimit / 10.0); // Make sure boot limit is never above 3.3v
deepSleep = gpioConfig.vccBootLimit > 0; // If a boot limit is set, we are assume the hardware has been configured for deep sleep (Hint: GPIO16)
#endif
while(!hw.isVoltageOptimal(allowedDrift)) {
uint8_t bootCycles = incrementBootCycleCounter(deepSleep);
debugW_P(PSTR("Voltage is outside optimal range (%.2fV)"), allowedDrift);
if(gpioConfig.apPin != 0xFF && digitalRead(gpioConfig.apPin) == LOW) {
debugW_P(PSTR("AP button is pressed, skipping voltage wait"));
} else if(bootCycles < MAX_BOOT_CYCLES) {
int secs = MAX_BOOT_CYCLES - bootCycles;
if(deepSleep) {
debugI_P(PSTR("Sleeping for %d seconds to allow capacitor charge (%d.cycle)"), secs, bootCycles);
Serial.flush();
ESP.deepSleep(secs * 1000000); // Deep sleep to allow output cap to charge up
return;
} else {
debugI_P(PSTR("Waiting (no sleep) for %d seconds to allow capacitor charge (%d.cycle)"), secs, bootCycles);
delay(secs * 1000); // Just delay to allow output cap to charge up
vcc = hw.getVcc();
debugI_P(PSTR("Voltage: %.2fV"), vcc);
}
ESP.deepSleep(10000000); //Deep sleep to allow output cap to charge up
}
} else {
debugE_P(PSTR("Voltage not reaching optimal level after multiple attempts, continuing boot"));
hw.setMaxVcc(vcc); // Since we had to sleep, set max Vcc to current level because this is probably the highest we will get
break;
}
}
#if defined(ESP8266)
resetBootCycleCounter(deepSleep);
#endif
if(rdc.magic != 0x4a) {
rdc.last_cause = 0;
rdc.cause = 0;
rdc.magic = 0x4a;
} else {
rdc.last_cause = rdc.cause;
rdc.cause = 0;
}
hw.ledOff(LED_YELLOW);
hw.ledOff(LED_INTERNAL);
if(!hw.ledOn(LED_GREEN)) {
hw.ledOn(LED_INTERNAL);
@@ -472,6 +526,12 @@ void setup() {
hw.ledOff(LED_GREEN);
hw.ledOff(LED_INTERNAL);
hw.ledBlink(LED_INTERNAL, 1);
hw.ledBlink(LED_RED, 1);
hw.ledBlink(LED_YELLOW, 1);
hw.ledBlink(LED_GREEN, 1);
hw.ledBlink(LED_BLUE, 1);
WiFi.disconnect(true);
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_OFF);
@@ -537,6 +597,15 @@ void setup() {
toggleSetupMode();
}
PriceServiceConfig price;
if(config.getPriceServiceConfig(price)) {
ps = new PriceService(&Debug);
ps->setup(price);
ws.setPriceService(ps);
}
ws.setPriceSettings(price.area, price.currency);
ea.setCurrency(price.currency);
EnergyAccountingConfig *eac = new EnergyAccountingConfig();
if(!config.getEnergyAccountingConfig(*eac)) {
config.clearEnergyAccountingConfig(*eac);
@@ -633,7 +702,12 @@ void loop() {
handleEnergySpeedometer();
#endif
#endif
handlePriceService(now);
// In case of BUS powered meters, we need to be sure voltage is stable before fetching prices. But we refuse to wait forever, so max 30 seconds
if(now > 30000 || hw.isVoltageOptimal(0.01)) {
handlePriceService(now);
}
#if defined(AMS_CLOUD)
handleCloud();
#endif