Compare commits

..

8 Commits

Author SHA1 Message Date
Gunnar Skjold
c6af74d919 Fixed parsing of Iskra 2026-03-05 11:47:12 +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
11 changed files with 782 additions and 630 deletions

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

@@ -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

@@ -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

@@ -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);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

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

@@ -1389,10 +1389,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 +1408,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 +1569,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 +1686,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

@@ -190,10 +190,15 @@ CloudConnector *cloud = NULL;
#if defined(ZMART_CHARGE)
ZmartChargeCloudConnector *zcloud = NULL;
#endif
#define MAX_BOOT_CYCLES 6
#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 +331,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 +410,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 +457,51 @@ 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 = true;
#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
hw.ledOff(LED_YELLOW);
hw.ledOff(LED_INTERNAL);
if(!hw.ledOn(LED_GREEN)) {
hw.ledOn(LED_INTERNAL);
@@ -472,6 +517,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 +588,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 +693,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