Initial implementation of custom upgrade code

This commit is contained in:
Gunnar Skjold 2024-08-02 17:56:39 +02:00
parent 9a4a8a10f2
commit 054fd43a0d
20 changed files with 792 additions and 313 deletions

7
custom_partition.csv Normal file
View File

@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1D0000,
app1, app, ota_1, 0x1E0000,0x1D0000,
spiffs, data, spiffs, 0x3B0000,0x40000,
coredump, data, coredump,0x3F0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x1D0000
5 app1 app ota_1 0x1E0000 0x1D0000
6 spiffs data spiffs 0x3B0000 0x40000
7 coredump data coredump 0x3F0000 0x10000

View File

@ -238,7 +238,10 @@ struct UpgradeInformation {
char toVersion[8];
int16_t exitCode;
int16_t errorCode;
}; // 20
uint32_t size;
uint16_t block_position;
uint8_t retry_count;
}; // 27
struct CloudConfig {
bool enabled;

View File

@ -0,0 +1,91 @@
#pragma once
#include <stdint.h>
#include <Print.h>
#include "HwTools.h"
#include "AmsData.h"
#include "AmsConfiguration.h"
#if defined(ESP32)
#include "esp_flash_partitions.h"
#include "LittleFS.h"
#include "WiFi.h"
#include "HTTPClient.h"
#define AMS_PARTITION_TABLE_OFFSET 0x8000
#define AMS_PARTITION_APP0_OFFSET 0x10000
#define AMS_PARTITION_APP_SIZE 0x1D0000
#define AMS_PARTITION_SPIFFS_SIZE 0x40000
#endif
#define AMS_UPDATE_ERR_OK 0
#define AMS_UPDATE_ERR_DETAILS 1
#define AMS_UPDATE_ERR_FETCH 2
#define AMS_UPDATE_ERR_ERASE 3
#define AMS_UPDATE_ERR_WRITE 4
#define AMS_UPDATE_ERR_READ 5
#define AMS_UPDATE_ERR_MD5 6
#define AMS_UPDATE_ERR_ACTIVATE 7
#define UPDATE_BUF_SIZE 4096
class AmsFirmwareUpdater {
public:
AmsFirmwareUpdater(Print* debugger, HwTools* hw, AmsData* meterState);
bool relocateOrRepartitionIfNecessary();
void loop();
char* getNextVersion();
bool setTargetVersion(const char* version);
void getUpgradeInformation(UpgradeInformation&);
float getProgress();
private:
#if defined(ESP8266)
char chipType[10] = "esp8266";
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
char chipType[10] = "esp32s2";
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
char chipType[10] = "esp32s3";
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
char chipType[10] = "esp32c3";
#elif defined(ESP32)
#if defined(CONFIG_FREERTOS_UNICORE)
char chipType[10] = "esp32solo";
#else
char chipType[10] = "esp32";
#endif
#endif
Print* debugger;
HwTools* hw;
AmsData* meterState;
UpgradeInformation updateStatus = {"","",0,0,0,0,0};
uint8_t blocksWritten = 0;
String md5;
uint32_t lastVersionCheck = 0;
uint8_t firmwareVariant;
bool autoUpgrade;
char nextVersion[10];
bool fetchNextVersion();
bool fetchVersionDetails();
bool fetchFirmwareChunk(HTTPClient& http);
bool writeBufferToFlash(size_t bytes);
bool verifyChecksum();
bool activateNewFirmware();
bool writeUpdateStatus();
uint32_t sketchSize(sketchSize_t response);
#if defined(ESP32)
uint32_t updateHandle = 0;
char* buf = NULL;
bool readPartition(uint8_t num, const esp_partition_info_t* partition);
bool writePartition(uint8_t num, const esp_partition_info_t* partition);
bool copyData(const esp_partition_info_t* src, esp_partition_info_t* dst);
bool copyFile(fs::LittleFSFS* src, fs::LittleFSFS* dst, const char* filename);
#endif
};

View File

@ -0,0 +1,490 @@
#include "AmsFirmwareUpdater.h"
#include "AmsStorage.h"
#include "FirmwareVersion.h"
#if defined(ESP32)
#include "esp_ota_ops.h"
#include "driver/spi_common.h"
#include "esp_flash_spi_init.h"
#include "MD5Builder.h"
#elif defined(ESP8266)
#include ""
#endif
AmsFirmwareUpdater::AmsFirmwareUpdater(Print* debugger, HwTools* hw, AmsData* meterState) {
this->debugger = debugger;
this->hw = hw;
this->meterState = meterState;
memset(nextVersion, 0, sizeof(nextVersion));
firmwareVariant = 0;
autoUpgrade = false;
}
char* AmsFirmwareUpdater::getNextVersion() {
return nextVersion;
}
bool AmsFirmwareUpdater::setTargetVersion(const char* version) {
if(strcmp(version, FirmwareVersion::VersionString) == 0) {
memset(updateStatus.toVersion, 0, sizeof(updateStatus.toVersion));
return false;
}
if(strcmp(version, updateStatus.toVersion) == 0) {
return true;
}
strcpy(updateStatus.fromVersion, FirmwareVersion::VersionString);
strcpy(updateStatus.toVersion, version);
updateStatus.size = 0;
updateStatus.retry_count = 0;
updateStatus.block_position = 0;
updateStatus.errorCode = AMS_UPDATE_ERR_OK;
return true;
}
void AmsFirmwareUpdater::getUpgradeInformation(UpgradeInformation& upinfo) {
memcpy(&upinfo, &updateStatus, sizeof(upinfo));
}
float AmsFirmwareUpdater::getProgress() {
if(strlen(updateStatus.toVersion) == 0 || updateStatus.size == 0) return -1.0;
return min((float) 100.0, ((((float) updateStatus.block_position) * UPDATE_BUF_SIZE) / updateStatus.size) * 100);
}
void AmsFirmwareUpdater::loop() {
if(strlen(updateStatus.toVersion) > 0) {
if(updateStatus.errorCode > 0) return;
if(!hw->isVoltageOptimal(0.1)) {
writeUpdateStatus();
return;
}
unsigned long start = 0, end = 0;
if(buf == NULL) buf = (char*) malloc(UPDATE_BUF_SIZE);
if(updateStatus.size == 0) {
start = millis();
if(!fetchVersionDetails()) {
updateStatus.errorCode = AMS_UPDATE_ERR_DETAILS;
return;
}
end = millis();
debugger->printf_P(PSTR("fetch details took %lums\n"), end-start);
updateStatus.retry_count = 0;
updateStatus.block_position = 0;
updateStatus.errorCode = AMS_UPDATE_ERR_OK;
} else if(updateStatus.block_position * UPDATE_BUF_SIZE < updateStatus.size) {
HTTPClient http;
start = millis();
if(!fetchFirmwareChunk(http)) {
if(updateStatus.retry_count++ == 3) {
updateStatus.errorCode = AMS_UPDATE_ERR_FETCH;
}
writeUpdateStatus();
http.end();
return;
}
end = millis();
debugger->printf_P(PSTR("fetch chunk took %lums\n"), end-start);
start = millis();
WiFiClient* client = http.getStreamPtr();
updateStatus.retry_count = 0;
if(!client->available()) {
http.end();
return;
}
end = millis();
debugger->printf_P(PSTR("get ptr took %lums (%d) \n"), end-start, client->available());
size_t bytes = UPDATE_BUF_SIZE; // To start first loop
while(bytes > 0 && client->available() > 0) {
start = millis();
bytes = client->readBytes(buf, UPDATE_BUF_SIZE);
end = millis();
debugger->printf_P(PSTR("read buffer took %lums (%lu bytes, %d left)\n"), end-start, bytes, client->available());
if(bytes > 0) {
start = millis();
if(!writeBufferToFlash(bytes)) {
http.end();
return;
}
end = millis();
debugger->printf_P(PSTR("write buffer took %lums\n"), end-start);
}
start = millis();
if(!hw->isVoltageOptimal(0.2)) {
writeUpdateStatus();
}
end = millis();
debugger->printf_P(PSTR("check voltage took %lums\n"), end-start);
}
start = millis();
http.end();
end = millis();
debugger->printf_P(PSTR("http end took %lums\n"), end-start);
} else if(updateStatus.block_position * UPDATE_BUF_SIZE >= updateStatus.size) {
if(!verifyChecksum()) {
updateStatus.errorCode = AMS_UPDATE_ERR_MD5;
return;
}
if(!activateNewFirmware()) {
return;
}
}
} else {
uint32_t seconds = millis() / 1000.0;
if((lastVersionCheck == 0 && seconds > 20) || seconds - lastVersionCheck > 86400) {
fetchNextVersion();
lastVersionCheck = seconds;
}
}
}
bool AmsFirmwareUpdater::fetchNextVersion() {
HTTPClient http;
const char * headerkeys[] = { "x-version" };
http.collectHeaders(headerkeys, 1);
char firmwareVariant[10] = "stable";
char url[128];
snprintf_P(url, 128, PSTR("http://hub.amsleser.no/hub/firmware/%s/%s/next"), chipType, firmwareVariant);
if(http.begin(url)) {
http.useHTTP10(true);
http.setTimeout(30000);
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
http.setUserAgent("AMS-Firmware-Updater");
http.addHeader(F("Cache-Control"), "no-cache");
http.addHeader(F("x-AMS-version"), FirmwareVersion::VersionString);
int status = http.GET();
if(status == 204) {
String nextVersion = http.header("x-version");
strcpy(this->nextVersion, nextVersion.c_str());
if(autoUpgrade && strcmp(updateStatus.toVersion, this->nextVersion) != 0) {
strcpy(updateStatus.toVersion, this->nextVersion);
updateStatus.size = 0;
}
http.end();
return strlen(this->nextVersion) > 0;
} else if(status == 200) {
memset(this->nextVersion, 0, sizeof(this->nextVersion));
}
http.end();
}
return false;
}
bool AmsFirmwareUpdater::fetchVersionDetails() {
HTTPClient http;
const char * headerkeys[] = { "x-size" };
http.collectHeaders(headerkeys, 1);
char firmwareVariant[10] = "stable";
char url[128];
snprintf_P(url, 128, PSTR("http://hub.amsleser.no/hub/firmware/%s/%s/%s/details"), chipType, firmwareVariant, updateStatus.toVersion);
if(http.begin(url)) {
http.useHTTP10(true);
http.setTimeout(30000);
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
http.setUserAgent("AMS-Firmware-Updater");
http.addHeader(F("Cache-Control"), "no-cache");
http.addHeader(F("x-AMS-STA-MAC"), WiFi.macAddress());
http.addHeader(F("x-AMS-AP-MAC"), WiFi.softAPmacAddress());
http.addHeader(F("x-AMS-free-space"), String(ESP.getFreeSketchSpace()));
http.addHeader(F("x-AMS-sketch-size"), String(ESP.getSketchSize()));
String sketchMD5 = ESP.getSketchMD5();
if(!sketchMD5.isEmpty()) {
http.addHeader(F("x-AMS-sketch-md5"), sketchMD5);
}
http.addHeader(F("x-AMS-chip-size"), String(ESP.getFlashChipSize()));
http.addHeader(F("x-AMS-sdk-version"), ESP.getSdkVersion());
http.addHeader(F("x-AMS-mode"), "sketch");
http.addHeader(F("x-AMS-version"), FirmwareVersion::VersionString);
http.addHeader(F("x-AMS-board-type"), String(hw->getBoardType(), 10));
if(meterState->getMeterType() != AmsTypeAutodetect) {
http.addHeader(F("x-AMS-meter-mfg"), String(meterState->getMeterType(), 10));
}
if(!meterState->getMeterModel().isEmpty()) {
http.addHeader(F("x-AMS-meter-model"), meterState->getMeterModel());
}
int status = http.GET();
if(status == 204) {
String size = http.header("x-size");
updateStatus.size = size.toInt();
http.end();
return true;
}
http.end();
}
return false;
}
bool AmsFirmwareUpdater::fetchFirmwareChunk(HTTPClient& http) {
const char * headerkeys[] = { "x-MD5" };
http.collectHeaders(headerkeys, 1);
uint32_t start = updateStatus.block_position * UPDATE_BUF_SIZE;
uint32_t end = start + (UPDATE_BUF_SIZE * 1);
char range[24];
snprintf_P(range, 24, PSTR("bytes=%lu-%lu"), start, end);
char firmwareVariant[10] = "stable";
char url[128];
snprintf_P(url, 128, PSTR("http://hub.amsleser.no/hub/firmware/%s/%s/%s/chunk"), chipType, firmwareVariant, updateStatus.toVersion);
if(http.begin(url)) {
http.useHTTP10(true);
http.setTimeout(30000);
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
http.setUserAgent("AMS-Firmware-Updater");
http.addHeader(F("Cache-Control"), "no-cache");
http.addHeader(F("x-AMS-version"), FirmwareVersion::VersionString);
http.addHeader(F("Range"), range);
if(http.GET() == 206) {
this->md5 = http.header("x-MD5");
return true;
}
}
return false;
}
bool AmsFirmwareUpdater::writeUpdateStatus() {
return false; // TODO
}
#if defined(ESP32)
bool AmsFirmwareUpdater::writeBufferToFlash(size_t bytes) {
uint32_t offset = updateStatus.block_position * UPDATE_BUF_SIZE;
const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL);
esp_err_t eraseErr = esp_partition_erase_range(partition, offset, UPDATE_BUF_SIZE);
if(eraseErr != ESP_OK) {
debugger->printf_P(PSTR("esp_partition_erase_range(%s, %lu, %lu) failed with %d\n"), partition->label, offset, UPDATE_BUF_SIZE, eraseErr);
updateStatus.errorCode = AMS_UPDATE_ERR_ERASE;
return false;
}
esp_err_t writeErr = esp_partition_write(partition, offset, buf, bytes);
if(writeErr != ESP_OK) {
debugger->printf_P(PSTR("esp_partition_write(%s, %lu, buf, %lu) failed with %d\n"), partition->label, offset, bytes, writeErr);
updateStatus.errorCode = AMS_UPDATE_ERR_WRITE;
return false;
}
updateStatus.block_position++;
return true;
}
bool AmsFirmwareUpdater::activateNewFirmware() {
const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL);
if(esp_ota_set_boot_partition(partition) != ESP_OK) {
updateStatus.errorCode = AMS_UPDATE_ERR_ACTIVATE;
return false;
}
ESP.restart();
return true;
}
bool AmsFirmwareUpdater::relocateOrRepartitionIfNecessary() {
const esp_partition_t* active = esp_ota_get_running_partition();
debugger->printf_P(PSTR("Firmware currently running from %s\n"), active->label);
if(active->type != ESP_PARTITION_TYPE_APP) {
debugger->printf_P(PSTR("Not running on APP partition?\n"));
return false;
}
if(active->size >= AMS_PARTITION_APP_SIZE) {
debugger->printf_P(PSTR("Partition is large enough, no change\n"));
return false;
}
if(buf == NULL) buf = (char*) malloc(UPDATE_BUF_SIZE);
esp_partition_info_t p_nvs, p_ota, p_app0, p_app1, p_spiffs, p_coredump;
readPartition(0, &p_nvs);
readPartition(1, &p_ota);
readPartition(2, &p_app0);
readPartition(3, &p_app1);
readPartition(4, &p_spiffs);
readPartition(5, &p_coredump);
const esp_partition_t* app0 = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_MIN, NULL);
if(active->subtype != ESP_PARTITION_SUBTYPE_APP_OTA_MIN) {
debugger->printf_P(PSTR("Relocating %s to %s\n"), active->label, p_app0.label);
if(!copyData(&p_app1, &p_app0)) {
debugger->printf_P(PSTR("Unable to copy app0 to app1\n"));
return false;
}
if(esp_ota_set_boot_partition(app0) != ESP_OK) {
debugger->printf_P(PSTR("Unable to set app0 active\n"));
return false;
}
return true;
}
debugger->printf_P(PSTR("Small partition, repartitioning\n"));
p_app0.pos.offset = AMS_PARTITION_APP0_OFFSET;
p_app0.pos.size = AMS_PARTITION_APP_SIZE;
p_app1.pos.offset = p_app0.pos.offset + p_app0.pos.size;
p_app1.pos.size = AMS_PARTITION_APP_SIZE;
p_spiffs.pos.offset = p_app1.pos.offset + p_app1.pos.size;
p_spiffs.pos.size = AMS_PARTITION_SPIFFS_SIZE;
esp_err_t p_erase_err = esp_flash_erase_region(NULL, AMS_PARTITION_TABLE_OFFSET, 4096);
if(p_erase_err != ESP_OK) {
debugger->printf_P(PSTR("Unable to erase partition table (%d)\n"), p_erase_err);
return false;
}
writePartition(0, &p_nvs);
writePartition(1, &p_ota);
writePartition(2, &p_app0);
writePartition(3, &p_app1);
writePartition(4, &p_spiffs);
writePartition(5, &p_coredump);
uint32_t md5pos = 0;
esp_partition_info_t part;
for(uint8_t i = 0; i < 10; i++) {
uint16_t size = sizeof(part);
uint32_t pos = i * size;
readPartition(i, &part);
if(part.magic == ESP_PARTITION_MAGIC) {
debugger->printf_P(PSTR("Partition %d, magic: %04X, offset: %X, size: %d, type: %d:%d, label: %s, flags: %04X\n"), i, part.magic, part.pos.offset, part.pos.size, part.type, part.subtype, part.label, part.flags);
} else {
md5pos = pos;
break;
}
}
memset(buf, 0, UPDATE_BUF_SIZE);
MD5Builder md5;
md5.begin();
md5.add((uint8_t*) &p_nvs, sizeof(p_nvs));
md5.add((uint8_t*) &p_ota, sizeof(p_ota));
md5.add((uint8_t*) &p_app0, sizeof(p_app0));
md5.add((uint8_t*) &p_app1, sizeof(p_app1));
md5.add((uint8_t*) &p_spiffs, sizeof(p_spiffs));
md5.add((uint8_t*) &p_coredump, sizeof(p_coredump));
md5.calculate();
md5.getChars(buf);
debugger->printf_P(PSTR("Writing MD5 %s to position %d\n"), buf, md5pos);
part.magic = ESP_PARTITION_MAGIC_MD5;
if(esp_flash_write(NULL, (uint8_t*) &part, AMS_PARTITION_TABLE_OFFSET + md5pos, 2) != ESP_OK) {
debugger->printf_P(PSTR("Unable to write md5 header\n"));
return false;
}
md5.getBytes((uint8_t*) buf);
if(esp_flash_write(NULL, buf, AMS_PARTITION_TABLE_OFFSET + md5pos + ESP_PARTITION_MD5_OFFSET, ESP_ROM_MD5_DIGEST_LEN) != ESP_OK) {
debugger->printf_P(PSTR("Unable to write md5\n"));
return false;
}
if(esp_flash_erase_region(NULL, p_app1.pos.offset, p_app1.pos.size) != ESP_OK) {
debugger->printf_P(PSTR("Unable to erase app1\n"));
}
uint32_t eraseForFsStart = p_spiffs.pos.offset + p_spiffs.pos.size - UPDATE_BUF_SIZE;
if(esp_flash_erase_region(NULL, eraseForFsStart, UPDATE_BUF_SIZE) == ESP_OK) {
fs::LittleFSFS newFs;
if(newFs.begin(true, "/newfs", 10, (char*) p_spiffs.label)) {
copyFile(&LittleFS, &newFs, FILE_MQTT_CA);
copyFile(&LittleFS, &newFs, FILE_MQTT_CERT);
copyFile(&LittleFS, &newFs, FILE_MQTT_KEY);
copyFile(&LittleFS, &newFs, FILE_DAYPLOT);
copyFile(&LittleFS, &newFs, FILE_MONTHPLOT);
copyFile(&LittleFS, &newFs, FILE_ENERGYACCOUNTING);
copyFile(&LittleFS, &newFs, FILE_PRICE_CONF);
} else {
debugger->printf_P(PSTR("Unable to start spiffs on new location\n"));
}
} else {
debugger->printf_P(PSTR("Unable to erase fs\n"));
}
esp_image_header_t h_app0;
if(esp_flash_read(NULL, buf, p_app0.pos.offset, sizeof(&h_app0)) == ESP_OK) {
if(esp_flash_write(NULL, buf, p_app1.pos.offset, sizeof(&h_app0)) != ESP_OK) {
debugger->printf_P(PSTR("Unable to write header to app1\n"));
}
} else {
debugger->printf_P(PSTR("Unable to read header from app0\n"));
}
return true;
}
bool AmsFirmwareUpdater::readPartition(uint8_t num, const esp_partition_info_t* partition) {
uint32_t pos = num * sizeof(*partition);
if(esp_flash_read(NULL, (uint8_t*) partition, AMS_PARTITION_TABLE_OFFSET + pos, sizeof(*partition)) != ESP_OK) {
debugger->printf_P(PSTR("Unable to read partition %d\n"), num);
return false;
}
return true;
}
bool AmsFirmwareUpdater::writePartition(uint8_t num, const esp_partition_info_t* partition) {
uint32_t pos = num * sizeof(*partition);
if(esp_flash_write(NULL, (uint8_t*) partition, AMS_PARTITION_TABLE_OFFSET + pos, sizeof(*partition)) != ESP_OK) {
debugger->printf_P(PSTR("Unable to write partition %d\n"), num);
return false;
}
return true;
}
bool AmsFirmwareUpdater::copyData(const esp_partition_info_t* src, esp_partition_info_t* dst) {
if(esp_flash_erase_region(NULL, dst->pos.offset, dst->pos.size) != ESP_OK) {
return false;
}
uint32_t pos = 0;
while(pos < dst->pos.size) {
if(esp_flash_read(NULL, buf, src->pos.offset + pos, UPDATE_BUF_SIZE) != ESP_OK) {
return false;
}
if(esp_flash_write(NULL, buf, dst->pos.offset + pos, UPDATE_BUF_SIZE) != ESP_OK) {
return false;
}
pos += UPDATE_BUF_SIZE;
}
return true;
}
bool AmsFirmwareUpdater::copyFile(fs::LittleFSFS* srcFs, fs::LittleFSFS* dstFs, const char* filename) {
if(srcFs->exists(filename)) {
File src = srcFs->open(filename, "r");
File dst = dstFs->open(filename, "w");
size_t size;
while((size = src.readBytes(buf, UPDATE_BUF_SIZE)) > 0) {
dst.write((uint8_t*) buf, size);
}
dst.close();
src.close();
return true;
}
return false;
}
bool AmsFirmwareUpdater::verifyChecksum() {
const esp_partition_t *partition = esp_ota_get_next_update_partition(NULL);
if (!partition) {
return false;
}
MD5Builder md5;
md5.begin();
uint32_t offset = 0;
uint32_t lengthLeft = updateStatus.size;
while( lengthLeft > 0) {
size_t bytes = (lengthLeft < UPDATE_BUF_SIZE) ? lengthLeft : UPDATE_BUF_SIZE;
if(esp_partition_read(partition, offset, buf, bytes) != ESP_OK) {
updateStatus.errorCode = AMS_UPDATE_ERR_READ;
return false;
}
md5.add((uint8_t*) buf, bytes);
lengthLeft -= bytes;
offset += bytes;
delay(1);
}
md5.calculate();
return !this->md5.isEmpty() && this->md5.equals(md5.toString());
}
#endif

View File

@ -43,7 +43,7 @@ struct AdcConfig {
class HwTools {
public:
bool applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterConfig& meterConfig, uint8_t hanPin);
void setup(GpioConfig*);
void setup(SystemConfig* sys, GpioConfig* gpio);
float getVcc();
uint8_t getTempSensorCount();
TempSensorData* getTempSensorData(uint8_t);
@ -56,15 +56,24 @@ public:
bool ledOff(uint8_t color);
bool ledBlink(uint8_t color, uint8_t blink);
void setBootSuccessful(bool value);
bool isVoltageOptimal(float range = 0.4);
uint8_t getBoardType();
HwTools() {};
private:
uint8_t boardType;
uint8_t ledPin, redPin, greenPin, bluePin, tempPin, atempPin;
uint8_t ledDisablePin, ledBehaviour;
bool ledInvert, rgbInvert;
uint8_t vccPin, vccGnd_r, vccVcc_r;
float vccOffset, vccMultiplier;
float maxVcc = 2.9;
uint16_t analogRange = 1024;
AdcConfig voltAdc, tempAdc;
#if defined(ESP32)
esp_adc_cal_characteristics_t* voltAdcChar, tempAdcChar;
#endif
GpioConfig* config;
bool tempSensorInit;
OneWire *oneWire = NULL;
DallasTemperature *sensorApi = NULL;

View File

@ -144,8 +144,8 @@ bool HwTools::applyBoardConfig(uint8_t boardType, GpioConfig& gpioConfig, MeterC
return false;
}
void HwTools::setup(GpioConfig* config) {
this->config = config;
void HwTools::setup(SystemConfig* sys, GpioConfig* config) {
this->boardType = sys->boardType;
this->tempSensorInit = false;
if(sensorApi != NULL)
delete sensorApi;
@ -153,8 +153,9 @@ void HwTools::setup(GpioConfig* config) {
delete oneWire;
if(config->tempSensorPin > 0 && config->tempSensorPin < 40) {
pinMode(config->tempSensorPin, INPUT);
tempPin = config->tempSensorPin;
} else {
config->tempSensorPin = 0xFF;
tempPin = config->tempSensorPin = 0xFF;
}
#if defined(CONFIG_IDF_TARGET_ESP32S2)
@ -196,47 +197,63 @@ void HwTools::setup(GpioConfig* config) {
#else
pinMode(config->vccPin, INPUT);
#endif
vccPin = config->vccPin;
vccOffset = config->vccOffset / 100.0;
vccMultiplier = config->vccMultiplier / 1000.0;
vccGnd_r = config->vccResistorGnd;
vccVcc_r = config->vccResistorVcc;
} else {
voltAdc.unit = 0xFF;
voltAdc.channel = 0xFF;
config->vccPin = 0xFF;
vccPin = config->vccPin = 0xFF;
}
if(config->tempAnalogSensorPin > 0 && config->tempAnalogSensorPin < 40) {
pinMode(config->tempAnalogSensorPin, INPUT);
atempPin = config->tempAnalogSensorPin;
} else {
config->tempAnalogSensorPin = 0xFF;
atempPin = config->tempAnalogSensorPin = 0xFF;
}
if(config->ledPin > 0 && config->ledPin < 40) {
pinMode(config->ledPin, OUTPUT);
ledPin = config->ledPin;
ledInvert = config->ledInverted;
ledOff(LED_INTERNAL);
} else {
config->ledPin = 0xFF;
ledPin = config->ledPin = 0xFF;
}
if(config->ledPinRed > 0 && config->ledPinRed < 40) {
pinMode(config->ledPinRed, OUTPUT);
redPin = config->ledPinRed;
ledOff(LED_RED);
} else {
config->ledPinRed = 0xFF;
redPin = config->ledPinRed = 0xFF;
}
if(config->ledPinGreen > 0 && config->ledPinGreen < 40) {
pinMode(config->ledPinGreen, OUTPUT);
greenPin = config->ledPinGreen;
ledOff(LED_GREEN);
} else {
config->ledPinGreen = 0xFF;
greenPin = config->ledPinGreen = 0xFF;
}
if(config->ledPinBlue > 0 && config->ledPinBlue < 40) {
pinMode(config->ledPinBlue, OUTPUT);
bluePin = config->ledPinBlue;
ledOff(LED_BLUE);
} else {
config->ledPinBlue = 0xFF;
bluePin = config->ledPinBlue = 0xFF;
}
rgbInvert = config->ledRgbInverted;
if(config->ledDisablePin > 0 && config->ledDisablePin < 40) {
pinMode(config->ledDisablePin, OUTPUT_OPEN_DRAIN);
ledDisablePin = config->ledDisablePin;
ledBehaviour = config->ledBehaviour;
setBootSuccessful(false);
}
}
@ -362,7 +379,7 @@ void HwTools::getAdcChannel(uint8_t pin, AdcConfig& config) {
float HwTools::getVcc() {
float volts = 0.0;
if(config->vccPin != 0xFF) {
if(vccPin != 0xFF) {
#if defined(ESP32)
if(voltAdc.unit != 0xFF) {
uint32_t x = 0;
@ -385,7 +402,7 @@ float HwTools::getVcc() {
} else {
uint32_t x = 0;
for (int i = 0; i < 10; i++) {
x += analogRead(config->vccPin);
x += analogRead(vccPin);
}
volts = (x * 3.3) / 10.0 / analogRange;
}
@ -403,13 +420,10 @@ float HwTools::getVcc() {
}
if(volts == 0.0) return 0.0;
if(config->vccResistorGnd > 0 && config->vccResistorVcc > 0) {
volts *= ((float) (config->vccResistorGnd + config->vccResistorVcc) / config->vccResistorGnd);
if(vccGnd_r > 0 && vccVcc_r > 0) {
volts *= ((float) (vccGnd_r + vccVcc_r) / vccGnd_r);
}
float vccOffset = config->vccOffset / 100.0;
float vccMultiplier = config->vccMultiplier / 1000.0;
return vccOffset + (volts > 0.0 ? volts * vccMultiplier : 0.0);
}
@ -425,9 +439,9 @@ TempSensorData* HwTools::getTempSensorData(uint8_t i) {
}
bool HwTools::updateTemperatures() {
if(config->tempSensorPin != 0xFF) {
if(tempPin != 0xFF) {
if(!tempSensorInit) {
oneWire = new OneWire(config->tempSensorPin);
oneWire = new OneWire(tempPin);
sensorApi = new DallasTemperature(this->oneWire);
sensorApi->begin();
delay(100);
@ -513,9 +527,9 @@ float HwTools::getTemperature() {
return c == 0 ? DEVICE_DISCONNECTED_C : ret/c;
}
float HwTools::getTemperatureAnalog() {
if(config->tempAnalogSensorPin != 0xFF) {
if(atempPin != 0xFF) {
float adcCalibrationFactor = 1.06587;
int volts = ((float) analogRead(config->tempAnalogSensorPin) / analogRange) * 3.3;
int volts = ((float) analogRead(atempPin) / analogRange) * 3.3;
return ((volts * adcCalibrationFactor) - 0.4) / 0.0195;
}
return DEVICE_DISCONNECTED_C;
@ -529,42 +543,42 @@ int HwTools::getWifiRssi() {
void HwTools::setBootSuccessful(bool value) {
if(bootSuccessful && value) return;
bootSuccessful = value;
if(config->ledDisablePin > 0 && config->ledDisablePin < 40) {
switch(config->ledBehaviour) {
if(ledDisablePin > 0 && ledDisablePin < 40) {
switch(ledBehaviour) {
case LED_BEHAVIOUR_ERROR_ONLY:
case LED_BEHAVIOUR_OFF:
digitalWrite(config->ledDisablePin, LOW);
digitalWrite(ledDisablePin, LOW);
break;
case LED_BEHAVIOUR_BOOT:
if(bootSuccessful) {
digitalWrite(config->ledDisablePin, LOW);
digitalWrite(ledDisablePin, LOW);
} else {
digitalWrite(config->ledDisablePin, HIGH);
digitalWrite(ledDisablePin, HIGH);
}
break;
default:
digitalWrite(config->ledDisablePin, HIGH);
digitalWrite(ledDisablePin, HIGH);
}
}
}
bool HwTools::ledOn(uint8_t color) {
if(config->ledBehaviour == LED_BEHAVIOUR_OFF) return false;
if(config->ledBehaviour == LED_BEHAVIOUR_ERROR_ONLY && color != LED_RED) return false;
if(config->ledBehaviour == LED_BEHAVIOUR_BOOT && color != LED_RED && bootSuccessful) return false;
if(ledBehaviour == LED_BEHAVIOUR_OFF) return false;
if(ledBehaviour == LED_BEHAVIOUR_ERROR_ONLY && color != LED_RED) return false;
if(ledBehaviour == LED_BEHAVIOUR_BOOT && color != LED_RED && bootSuccessful) return false;
if(color == LED_INTERNAL) {
return writeLedPin(color, config->ledInverted ? LOW : HIGH);
return writeLedPin(color, ledInvert ? LOW : HIGH);
} else {
return writeLedPin(color, config->ledRgbInverted ? LOW : HIGH);
return writeLedPin(color, rgbInvert ? LOW : HIGH);
}
}
bool HwTools::ledOff(uint8_t color) {
if(color == LED_INTERNAL) {
return writeLedPin(color, config->ledInverted ? HIGH : LOW);
return writeLedPin(color, ledInvert ? HIGH : LOW);
} else {
return writeLedPin(color, config->ledRgbInverted ? HIGH : LOW);
return writeLedPin(color, rgbInvert ? HIGH : LOW);
}
}
@ -581,8 +595,8 @@ bool HwTools::ledBlink(uint8_t color, uint8_t blink) {
bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
switch(color) {
case LED_INTERNAL: {
if(config->ledPin != 0xFF) {
digitalWrite(config->ledPin, state);
if(ledPin != 0xFF) {
digitalWrite(ledPin, state);
return true;
} else {
return false;
@ -590,8 +604,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
break;
}
case LED_RED: {
if(config->ledPinRed != 0xFF) {
digitalWrite(config->ledPinRed, state);
if(redPin != 0xFF) {
digitalWrite(redPin, state);
return true;
} else {
return false;
@ -599,8 +613,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
break;
}
case LED_GREEN: {
if(config->ledPinGreen != 0xFF) {
digitalWrite(config->ledPinGreen, state);
if(greenPin != 0xFF) {
digitalWrite(greenPin, state);
return true;
} else {
return false;
@ -608,8 +622,8 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
break;
}
case LED_BLUE: {
if(config->ledPinBlue != 0xFF) {
digitalWrite(config->ledPinBlue, state);
if(bluePin != 0xFF) {
digitalWrite(bluePin, state);
return true;
} else {
return false;
@ -617,9 +631,9 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
break;
}
case LED_YELLOW: {
if(config->ledPinRed != 0xFF && config->ledPinGreen != 0xFF) {
digitalWrite(config->ledPinRed, state);
digitalWrite(config->ledPinGreen, state);
if(redPin != 0xFF && greenPin != 0xFF) {
digitalWrite(redPin, state);
digitalWrite(greenPin, state);
return true;
} else {
return false;
@ -629,3 +643,22 @@ bool HwTools::writeLedPin(uint8_t color, uint8_t state) {
}
return false;
}
bool HwTools::isVoltageOptimal(float range) {
if(boardType >= 5 && boardType <= 7 && maxVcc > 2.8) { // Pow-*
float vcc = getVcc();
if(vcc > 3.4 || vcc < 2.8) {
maxVcc = 0; // Voltage is outside the operating range, we have to assume voltage is OK
} else if(vcc > maxVcc) {
maxVcc = vcc;
} else {
float diff = min(maxVcc, (float) 3.3) - vcc;
return diff < range;
}
}
return true;
}
uint8_t HwTools::getBoardType() {
return boardType;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
<script>
import { Router, Route, navigate } from "svelte-navigator";
import { getTariff, tariffStore, sysinfoStore, dataStore, pricesStore, dayPlotStore, monthPlotStore, temperaturesStore } from './lib/DataStores.js';
import { getTariff, tariffStore, sysinfoStore, dataStore, pricesStore, dayPlotStore, monthPlotStore, temperaturesStore, getSysinfo } from './lib/DataStores.js';
import { translationsStore, getTranslations } from "./lib/TranslationService.js";
import Favicon from './assets/favicon.svg'; // Need this for the build
import Header from './lib/Header.svelte';
@ -44,6 +44,8 @@
translations = update;
});
let sito;
let data = {};
let sysinfo = {};
sysinfoStore.subscribe(update => {
sysinfo = update;
@ -70,9 +72,11 @@
if(sysinfo.ui.lang && sysinfo.ui.lang != translations?.language?.code) {
getTranslations(sysinfo.ui.lang);
}
if(sito) clearTimeout(sito);
sito = setTimeout(getSysinfo, !data || !data.u || data.u < 30 || sysinfo?.upgrading ? 10000 : 300000);
});
let data = {};
dataStore.subscribe(update => {
data = update;
updateRealtime(update);
@ -126,9 +130,7 @@
</Route>
</Router>
{#if sysinfo.upgrading}
<Mask active=true message="Device is upgrading, please wait"/>
{:else if sysinfo.booting}
{#if sysinfo.booting}
{#if sysinfo.trying}
<Mask active=true message="Device is booting, please wait. Trying to reach it on {sysinfo.trying}"/>
{:else}

View File

@ -46,6 +46,7 @@ function updateSysinfo(url) {
let tries = 0;
let lastTemp = -127;
let lastPrice = null;
let lastUp = 0;
let data = {};
export const dataStore = readable(data, (set) => {
let timeout;
@ -63,7 +64,7 @@ export const dataStore = readable(data, (set) => {
lastPrice = data.p;
getPrices();
}
if(sysinfo.upgrading) {
if(sysinfo.upgrading && lastUp > data.u) {
window.location.reload();
} else if(!sysinfo || !sysinfo.chip || sysinfo.booting || (tries > 1 && !isBusPowered(sysinfo.board))) {
getSysinfo();
@ -72,6 +73,7 @@ export const dataStore = readable(data, (set) => {
if(monthPlotTimeout) clearTimeout(monthPlotTimeout);
monthPlotTimeout = setTimeout(getMonthPlot, 3000);
}
lastUp = data.u;
if(!dayPlotTimeout) dayPlotTimeout = getDayPlot();
if(!monthPlotTimeout) monthPlotTimeout = getMonthPlot();
@ -212,12 +214,3 @@ export async function getTariff() {
export const tariffStore = writable(tariff, (set) => {
return function stop() {}
});
let releases = [];
export const gitHubReleaseStore = writable(releases);
export async function getGitHubReleases() {
const response = await fetchWithTimeout("https://api.github.com/repos/UtilitechAS/amsreader-firmware/releases");
releases = (await response.json())
gitHubReleaseStore.set(releases);
};

View File

@ -1,7 +1,7 @@
<script>
import { Link } from "svelte-navigator";
import { sysinfoStore, getGitHubReleases, gitHubReleaseStore } from './DataStores.js';
import { upgrade, getNextVersion, upgradeWarningText } from './UpgradeHelper';
import { sysinfoStore } from './DataStores.js';
import { upgrade, upgradeWarningText } from './UpgradeHelper';
import { boardtype, isBusPowered, wiki, bcol } from './Helpers.js';
import { translationsStore } from "./TranslationService.js";
import FavIco from './../assets/favicon.svg';
@ -16,34 +16,30 @@
export let data = {};
let sysinfo = {};
let nextVersion = {};
function askUpgrade() {
if(confirm((translations.header?.upgrade ?? "Upgrade to {0}?").replace('{0}',nextVersion.tag_name))) {
if(!isBusPowered(sysinfo.board) || confirm(upgradeWarningText(boardtype(sysinfo.chip, sysinfo.board)))) {
sysinfoStore.update(s => {
s.upgrading = true;
return s;
});
upgrade(nextVersion.tag_name);
}
if(confirm((translations.header?.upgrade ?? "Upgrade to {0}?").replace('{0}',sysinfo.upgrade.n))) {
upgrade(sysinfo.upgrade.n);
sysinfoStore.update(s => {
s.upgrade.t = sysinfo.upgrade.n;
s.upgrading = true;
return s;
});
}
}
let progress;
sysinfoStore.subscribe(update => {
sysinfo = update;
if(update.fwconsent === 1) {
getGitHubReleases();
}
});
gitHubReleaseStore.subscribe(releases => {
nextVersion = getNextVersion(sysinfo.version, releases);
});
let translations = {};
translationsStore.subscribe(update => {
translations = update;
});
$: {
progress = Math.max(0, sysinfo.upgrade.p);
}
</script>
<nav class="hdr">
@ -91,12 +87,14 @@
<div class="flex-none px-1 mt-1" title={translations.header?.doc ?? ""}>
<a href={wiki('')} target='_blank' rel="noreferrer"><HelpIcon/></a>
</div>
{#if sysinfo.fwconsent === 1 && nextVersion}
<div class="flex-none mr-3 text-yellow-500" title={(translations.header?.new_version ?? "New version") + ': ' + nextVersion.tag_name}>
{#if sysinfo.upgrading}
<div class="flex-none mr-3 mt-1 text-yellow-300">Upgrading to {sysinfo.upgrade.t}, {progress.toFixed(1)}%</div>
{:else if sysinfo.fwconsent === 1 && sysinfo.upgrade.n}
<div class="flex-none mr-3 text-yellow-500" title={(translations.header?.new_version ?? "New version") + ': ' + sysinfo.upgrade.n}>
{#if sysinfo.security == 0 || data.a}
<button on:click={askUpgrade} class="flex"><span class="mt-1">{translations.header?.new_version ?? "New version"}: {nextVersion.tag_name}</span></button>
<button on:click={askUpgrade} class="flex"><span class="mt-1">{translations.header?.new_version ?? "New version"}: {sysinfo.upgrade.n}</span></button>
{:else}
<span>{translations.header?.new_version ?? "New version"}: {nextVersion.tag_name}</span>
<span>{translations.header?.new_version ?? "New version"}: {sysinfo.upgrade.n}</span>
{/if}
</div>
{/if}

View File

@ -1,7 +1,7 @@
<script>
import { metertype, boardtype, isBusPowered, getBaseChip } from './Helpers.js';
import { getSysinfo, gitHubReleaseStore, sysinfoStore } from './DataStores.js';
import { upgrade, getNextVersion, upgradeWarningText } from './UpgradeHelper';
import { getSysinfo, sysinfoStore } from './DataStores.js';
import { upgrade, upgradeWarningText } from './UpgradeHelper';
import { translationsStore } from './TranslationService.js';
import { Link } from 'svelte-navigator';
import Clock from './Clock.svelte';
@ -41,24 +41,15 @@
translationsStore.subscribe(update => {
translations = update;
});
let nextVersion = {};
gitHubReleaseStore.subscribe(releases => {
nextVersion = getNextVersion(sysinfo.version, releases);
if(!nextVersion) {
nextVersion = releases[0];
}
});
function askUpgrade() {
if(confirm((translations.header?.upgrade ?? "Upgrade to {0}?").replace('{0}',nextVersion.tag_name))) {
if((sysinfo.board != 2 && sysinfo.board != 4 && sysinfo.board != 7) || confirm(upgradeWarningText(boardtype(sysinfo.chip, sysinfo.board)))) {
sysinfoStore.update(s => {
s.upgrading = true;
return s;
});
upgrade(nextVersion.tag_name);
}
if(confirm((translations.header?.upgrade ?? "Upgrade to {0}?").replace('{0}',sysinfo.upgrade.n))) {
upgrade(sysinfo.upgrade.n);
sysinfoStore.update(s => {
s.upgrade.t = sysinfo.upgrade.n;
s.upgrading = true;
return s;
});
}
}
@ -156,7 +147,7 @@
<div class="my-2">
{translations.status?.device?.last_boot ?? "Last boot"}:
{#if data.u > 0}
<Clock timestamp={new Date(new Date().getTime() - (data.u * 1000))} fullTimeColor="" />
<Clock timestamp={new Date(new Date().getTime() - (data.u * 1000))} fullTimeColor="" offset={sysinfo.clock_offset}/>
{:else}
-
{/if}
@ -228,11 +219,11 @@
</div>
</div>
{/if}
{#if nextVersion}
{#if sysinfo.upgrade.n}
<div class="my-2 flex">
{translations.status?.firmware?.latest ?? "Latest"}:
<a href={nextVersion.html_url} class="ml-2 text-blue-600 hover:text-blue-800" target='_blank' rel="noreferrer">{nextVersion.tag_name}</a>
{#if (sysinfo.security == 0 || data.a) && sysinfo.fwconsent === 1 && nextVersion && nextVersion.tag_name != sysinfo.version}
<a href={"https://github.com/UtilitechAS/amsreader-firmware/releases/tag/" + sysinfo.upgrade.n} class="ml-2 text-blue-600 hover:text-blue-800" target='_blank' rel="noreferrer">{sysinfo.upgrade.n}</a>
{#if (sysinfo.security == 0 || data.a) && sysinfo.fwconsent === 1 && sysinfo.upgrade.n && sysinfo.upgrade.n != sysinfo.version}
<div class="flex-none ml-2 text-green-500" title={translations.status?.firmware?.install ?? "Install"}>
<button on:click={askUpgrade}>&#8659;</button>
</div>

View File

@ -12,6 +12,7 @@
}
</script>
<option value={-1}>disabled</option>
{#if chip == 'esp8266'}
<option value={3}>UART0</option>
<option value={113}>UART2</option>

View File

@ -6,61 +6,5 @@ export async function upgrade(expected_version) {
const response = await fetch('upgrade?expected_version='+expected_version, {
method: 'POST'
});
await response.json();
}
export function getNextVersion(currentVersion, releases_orig) {
if(!releases_orig || releases_orig.message) return;
if(/^v\d{1,2}\.\d{1,2}\.\d{1,2}$/.test(currentVersion)) {
let v = currentVersion.substring(1).split('.');
let v_major = parseInt(v[0]);
let v_minor = parseInt(v[1]);
let v_patch = parseInt(v[2]);
let releases = [...releases_orig];
releases.reverse();
let next_patch;
let next_minor;
let next_major;
for(let i = 0; i < releases.length; i++) {
let release = releases[i];
let ver2 = release.tag_name;
let v2 = ver2.substring(1).split('.');
let v2_major = parseInt(v2[0]);
let v2_minor = parseInt(v2[1]);
let v2_patch = parseInt(v2[2]);
if(v2_major == v_major) {
if(v2_minor == v_minor) {
if(v2_patch > v_patch) {
next_patch = release;
}
} else if(v2_minor == v_minor+1) {
next_minor = release;
}
} else if(v2_major == v_major+1) {
if(next_major) {
let mv = next_major.tag_name.substring(1).split('.');
let mv_major = parseInt(mv[0]);
let mv_minor = parseInt(mv[1]);
let mv_patch = parseInt(mv[2]);
if(v2_minor == mv_minor) {
next_major = release;
}
} else {
next_major = release;
}
}
};
if(next_minor) {
return next_minor;
} else if(next_major) {
return next_major;
} else if(next_patch) {
return next_patch;
}
return false;
} else {
return releases_orig[0];
}
return await response.json();
}

View File

@ -17,25 +17,25 @@ export default defineConfig({
plugins: [svelte()],
server: {
proxy: {
"/data.json": "http://192.168.233.49",
"/energyprice.json": "http://192.168.233.49",
"/dayplot.json": "http://192.168.233.49",
"/monthplot.json": "http://192.168.233.49",
"/temperature.json": "http://192.168.233.49",
"/sysinfo.json": "http://192.168.233.49",
"/configuration.json": "http://192.168.233.49",
"/tariff.json": "http://192.168.233.49",
"/realtime.json": "http://192.168.233.49",
"/priceconfig.json": "http://192.168.233.49",
"/cloudkey.json": "http://192.168.233.49",
"/save": "http://192.168.233.49",
"/reboot": "http://192.168.233.49",
"/configfile": "http://192.168.233.49",
"/upgrade": "http://192.168.233.49",
"/mqtt-ca": "http://192.168.233.49",
"/mqtt-cert": "http://192.168.233.49",
"/mqtt-key": "http://192.168.233.49",
"/logo.svg": "http://192.168.233.49",
"/data.json": "http://192.168.233.154",
"/energyprice.json": "http://192.168.233.154",
"/dayplot.json": "http://192.168.233.154",
"/monthplot.json": "http://192.168.233.154",
"/temperature.json": "http://192.168.233.154",
"/sysinfo.json": "http://192.168.233.154",
"/configuration.json": "http://192.168.233.154",
"/tariff.json": "http://192.168.233.154",
"/realtime.json": "http://192.168.233.154",
"/priceconfig.json": "http://192.168.233.154",
"/cloudkey.json": "http://192.168.233.154",
"/save": "http://192.168.233.154",
"/reboot": "http://192.168.233.154",
"/configfile": "http://192.168.233.154",
"/upgrade": "http://192.168.233.154",
"/mqtt-ca": "http://192.168.233.154",
"/mqtt-cert": "http://192.168.233.154",
"/mqtt-key": "http://192.168.233.154",
"/logo.svg": "http://192.168.233.154",
}
}
})

View File

@ -14,6 +14,7 @@
#include "AmsData.h"
#include "AmsStorage.h"
#include "AmsDataStorage.h"
#include "AmsFirmwareUpdater.h"
#include "EnergyAccounting.h"
#include "Uptime.h"
#if defined(AMS_REMOTE_DEBUG)
@ -53,7 +54,7 @@ public:
#else
AmsWebServer(uint8_t* buf, Stream* Debug, HwTools* hw, ResetDataContainer* rdc);
#endif
void setup(AmsConfiguration*, GpioConfig*, AmsData*, AmsDataStorage*, EnergyAccounting*, RealtimePlot*);
void setup(AmsConfiguration*, GpioConfig*, AmsData*, AmsDataStorage*, EnergyAccounting*, RealtimePlot*, AmsFirmwareUpdater*);
void loop();
#if defined(_CLOUDCONNECTOR_H)
void setCloud(CloudConnector* cloud);
@ -88,6 +89,7 @@ private:
AmsDataStorage* ds;
EnergyAccounting* ea = NULL;
RealtimePlot* rtp = NULL;
AmsFirmwareUpdater* updater = NULL;
AmsMqttHandler* mqttHandler = NULL;
ConnectionHandler* ch = NULL;
#if defined(_CLOUDCONNECTOR_H)
@ -139,7 +141,6 @@ private:
void handleSave();
void reboot();
void upgrade();
void upgradeFromUrl(String url, String nextVersion);
void firmwareHtml();
void firmwarePost();
void firmwareUpload();
@ -167,7 +168,6 @@ private:
void robotstxt();
void ssdpSchema();
void updaterRequestCallback(HTTPClient*);
void addConditionalCloudHeaders();
void optionsGet();
};

View File

@ -56,7 +56,9 @@
"x": %d,
"e": %d,
"f": "%s",
"t": "%s"
"t": "%s",
"n": "%s",
"p": %.1f
},
"last_month": {
"u" : %.2f,

View File

@ -70,13 +70,14 @@ AmsWebServer::AmsWebServer(uint8_t* buf, Stream* Debug, HwTools* hw, ResetDataCo
}
}
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, AmsData* meterState, AmsDataStorage* ds, EnergyAccounting* ea, RealtimePlot* rtp) {
void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, AmsData* meterState, AmsDataStorage* ds, EnergyAccounting* ea, RealtimePlot* rtp, AmsFirmwareUpdater* updater) {
this->config = config;
this->gpioConfig = gpioConfig;
this->meterState = meterState;
this->ds = ds;
this->ea = ea;
this->rtp = rtp;
this->updater = updater;
String context;
config->getWebConfig(webConfig);
@ -371,7 +372,7 @@ void AmsWebServer::sysinfoJson() {
config->getUiConfig(ui);
UpgradeInformation upinfo;
config->getUpgradeInformation(upinfo);
updater->getUpgradeInformation(upinfo);
String meterModel = meterState->getMeterModel();
if(!meterModel.isEmpty())
@ -417,7 +418,7 @@ void AmsWebServer::sysinfoJson() {
sys.dataCollectionConsent,
hostname.c_str(),
performRestart ? "true" : "false",
rebootForUpgrade ? "true" : "false",
updater->getProgress() > 0.0 ? "true" : "false",
#if defined(ESP8266)
localIp.isSet() ? localIp.toString().c_str() : "",
subnet.isSet() ? subnet.toString().c_str() : "",
@ -470,6 +471,8 @@ void AmsWebServer::sysinfoJson() {
upinfo.errorCode,
upinfo.fromVersion,
upinfo.toVersion,
updater->getNextVersion(),
updater->getProgress(),
ea->getUseLastMonth(),
ea->getCostLastMonth(),
ea->getProducedLastMonth(),
@ -1173,6 +1176,9 @@ void AmsWebServer::handleSave() {
if(!checkSecurity(1))
return;
SystemConfig sys;
config->getSystemConfig(sys);
bool success = true;
if(server.hasArg(F("v")) && server.arg(F("v")) == F("true")) {
int boardType = server.arg(F("vb")).toInt();
@ -1189,8 +1195,6 @@ void AmsWebServer::handleSave() {
config->setGpioConfig(*gpioConfig);
config->setMeterConfig(meterConfig);
SystemConfig sys;
config->getSystemConfig(sys);
sys.boardType = success ? boardType : 0xFF;
sys.vendorConfigured = success;
config->setSystemConfig(sys);
@ -1198,8 +1202,6 @@ void AmsWebServer::handleSave() {
}
if(server.hasArg(F("s")) && server.arg(F("s")) == F("true")) {
SystemConfig sys;
config->getSystemConfig(sys);
MeterConfig meterConfig;
config->getMeterConfig(meterConfig);
@ -1267,8 +1269,6 @@ void AmsWebServer::handleSave() {
performRestart = true;
}
} else if(server.hasArg(F("sf")) && !server.arg(F("sf")).isEmpty()) {
SystemConfig sys;
config->getSystemConfig(sys);
sys.dataCollectionConsent = server.hasArg(F("sf")) && (server.arg(F("sf")) == F("true") || server.arg(F("sf")) == F("1")) ? 1 : 2;
config->setSystemConfig(sys);
}
@ -1564,8 +1564,6 @@ void AmsWebServer::handleSave() {
}
if(server.hasArg(F("c")) && server.arg(F("c")) == F("true")) {
SystemConfig sys;
config->getSystemConfig(sys);
sys.energyspeedometer = server.hasArg(F("ces")) && server.arg(F("ces")) == F("true") ? 7 : 0;
config->setSystemConfig(sys);
@ -1651,7 +1649,7 @@ debugger->printf_P(PSTR("Successfully saved.\n"));
if(config->isNetworkConfigChanged() || performRestart) {
performRestart = true;
} else {
hw->setup(gpioConfig);
hw->setup(&sys, gpioConfig);
}
} else {
success = false;
@ -1726,85 +1724,8 @@ void AmsWebServer::upgrade() {
server.handleClient();
delay(250);
if(server.hasArg(F("url"))) {
customFirmwareUrl = server.arg(F("url"));
}
String url = customFirmwareUrl.isEmpty() || !customFirmwareUrl.startsWith(F("http")) ? F("http://hub.amsleser.no/hub/firmware/update") : customFirmwareUrl;
if(server.hasArg(F("version"))) {
url += "/" + server.arg(F("version"));
}
upgradeFromUrl(url, server.arg(F("expected_version")));
}
}
void AmsWebServer::upgradeFromUrl(String url, String nextVersion) {
config->setUpgradeInformation(0xFF, 0xFF, FirmwareVersion::VersionString, nextVersion.c_str());
WiFiClient client;
#if defined(ESP8266)
String chipType = F("esp8266");
#elif defined(CONFIG_IDF_TARGET_ESP32S2)
String chipType = F("esp32s2");
#elif defined(CONFIG_IDF_TARGET_ESP32S3)
String chipType = F("esp32s3");
#elif defined(CONFIG_IDF_TARGET_ESP32C3)
String chipType = F("esp32c3");
#elif defined(ESP32)
#if defined(CONFIG_FREERTOS_UNICORE)
String chipType = F("esp32solo");
#else
String chipType = F("esp32");
#endif
#endif
#if defined(ESP8266)
ESP8266HTTPUpdate httpUpdate = ESP8266HTTPUpdate(60000);
String currentVersion = FirmwareVersion::VersionString;
ESP.wdtEnable(300000);
#elif defined(ESP32)
HTTPUpdate httpUpdate = HTTPUpdate(60000);
String currentVersion = String(FirmwareVersion::VersionString) + "-" + chipType;
esp_task_wdt_init(300, true);
#endif
httpUpdate.rebootOnUpdate(false);
httpUpdate.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
#if defined(ESP32)
HTTPUpdateResult ret = httpUpdate.update(client, url, currentVersion, std::bind(&AmsWebServer::updaterRequestCallback, this, std::placeholders::_1));
#else
HTTPUpdateResult ret = httpUpdate.update(client, url, currentVersion);
#endif
int lastError = httpUpdate.getLastError();
config->setUpgradeInformation(ret, ret == HTTP_UPDATE_OK ? 0 : lastError, FirmwareVersion::VersionString, nextVersion.c_str());
switch(ret) {
case HTTP_UPDATE_FAILED:
debugger->printf_P(PSTR("Update failed\n"));
break;
case HTTP_UPDATE_NO_UPDATES:
debugger->printf_P(PSTR("No Update\n"));
break;
case HTTP_UPDATE_OK:
debugger->printf_P(PSTR("Update OK\n"));
debugger->flush();
rdc->cause = 4;
ESP.restart();
break;
}
}
void AmsWebServer::updaterRequestCallback(HTTPClient* http) {
SystemConfig sys;
if(config->getSystemConfig(sys)) {
http->addHeader(F("x-AMS-board-type"), String(sys.boardType, 10));
if(meterState->getMeterType() != AmsTypeAutodetect) {
http->addHeader(F("x-AMS-meter-mfg"), String(meterState->getMeterType(), 10));
}
if(!meterState->getMeterModel().isEmpty()) {
http->addHeader(F("x-AMS-meter-model"), meterState->getMeterModel());
}
String version = server.arg(F("expected_version"));
updater->setTargetVersion(version.c_str());
}
}
@ -1828,14 +1749,6 @@ void AmsWebServer::firmwarePost() {
server.send(200);
} else {
config->setUpgradeInformation(0xFF, 0xFF, FirmwareVersion::VersionString, "");
if(server.hasArg(F("url"))) {
String url = server.arg(F("url"));
if(!url.isEmpty() && (url.startsWith(F("http://")) || url.startsWith(F("https://")))) {
upgradeFromUrl(url, "");
server.send(200, MIME_PLAIN, "OK");
return;
}
}
server.sendHeader(HEADER_LOCATION,F("/firmware"));
server.send(303);
}

View File

@ -2,7 +2,7 @@
extra_configs = platformio-user.ini
[common]
lib_deps = EEPROM, LittleFS, DNSServer, 256dpi/MQTT@2.5.2, OneWireNg@0.10.0, DallasTemperature@3.9.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.1, Timezone@1.2.4, FirmwareVersion, AmsConfiguration, AmsData, AmsDataStorage, HwTools, Uptime, AmsDecoder, PriceService, EnergyAccounting, AmsMqttHandler, RawMqttHandler, JsonMqttHandler, DomoticzMqttHandler, HomeAssistantMqttHandler, PassthroughMqttHandler, RealtimePlot, ConnectionHandler, MeterCommunicators
lib_deps = EEPROM, LittleFS, DNSServer, 256dpi/MQTT@2.5.2, OneWireNg@0.10.0, DallasTemperature@3.9.1, https://github.com/gskjold/RemoteDebug.git, Time@1.6.1, Timezone@1.2.4, FirmwareVersion, AmsConfiguration, AmsData, AmsDataStorage, HwTools, Uptime, AmsDecoder, PriceService, EnergyAccounting, AmsMqttHandler, RawMqttHandler, JsonMqttHandler, DomoticzMqttHandler, HomeAssistantMqttHandler, PassthroughMqttHandler, RealtimePlot, ConnectionHandler, MeterCommunicators, AmsFirmwareUpdater
lib_ignore = OneWire
extra_scripts =
pre:scripts/addversion.py

View File

@ -118,6 +118,8 @@ HardwareSerial Debug = Serial;
#include "Timezones.h"
#include "AmsFirmwareUpdater.h"
uint8_t commonBuffer[BUF_SIZE_COMMON];
HwTools hw;
@ -178,6 +180,8 @@ bool ntpEnabled = false;
bool mdnsEnabled = false;
AmsFirmwareUpdater updater(&Debug, &hw, &meterState);
AmsDataStorage ds(&Debug);
#if defined(_CLOUDCONNECTOR_H)
CloudConnector *cloud = NULL;
@ -317,7 +321,7 @@ void setup() {
}
delay(1);
hw.setup(&gpioConfig);
hw.setup(&sysConfig, &gpioConfig);
if(gpioConfig.apPin >= 0) {
pinMode(gpioConfig.apPin, INPUT_PULLUP);
@ -411,6 +415,10 @@ void setup() {
debugI_P(PSTR("AMS bridge started"));
debugI_P(PSTR("Voltage: %.2fV"), vcc);
if(updater.relocateOrRepartitionIfNecessary()) {
ESP.restart();
return;
}
float vccBootLimit = gpioConfig.vccBootLimit == 0 ? 0 : min(3.29, gpioConfig.vccBootLimit / 10.0); // Make sure it is never above 3.3v
if(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)
@ -530,7 +538,7 @@ void setup() {
ea.setup(&ds, eac);
ea.load();
ea.setPriceService(ps);
ws.setup(&config, &gpioConfig, &meterState, &ds, &ea, &rtp);
ws.setup(&config, &gpioConfig, &meterState, &ds, &ea, &rtp, &updater);
UiConfig ui;
if(config.getUiConfig(ui)) {
@ -727,6 +735,12 @@ void loop() {
if(end-start > SLOW_PROC_TRIGGER_MS) {
debugW_P(PSTR("Used %dms to handle language update"), end-start);
}
start = millis();
updater.loop();
end = millis();
if(end-start > SLOW_PROC_TRIGGER_MS) {
debugW_P(PSTR("Used %dms to handle firmware updater"), end-start);
}
}
#if defined(ESP32)
if(now - lastVoltageCheck > 1000) {
@ -962,7 +976,6 @@ void handleEnergyAccountingChanged() {
}
char ntpServerName[64] = "";
float maxVcc = 2.9;
void handleNtpChange() {
NtpConfig ntp;
@ -1025,23 +1038,12 @@ void handleSystem(unsigned long now) {
}
bool handleVoltageCheck() {
if(sysConfig.boardType >= 5 && sysConfig.boardType <= 7 && maxVcc > 2.8) { // Pow-*
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 {
float diff = min(maxVcc, (float) 3.3)-vcc;
if(diff > 0.4) {
if(WiFi.getMode() == WIFI_STA) {
debugW_P(PSTR("Vcc dropped to %.2f, disconnecting WiFi for 5 seconds to preserve power"), vcc);
ch->disconnect(5000);
}
return false;
}
if(!hw.isVoltageOptimal()) {
if(WiFi.getMode() == WIFI_STA) {
debugW_P(PSTR("Vcc dropped below limit, disconnecting WiFi for 5 seconds to preserve power"));
ch->disconnect(5000);
}
return false;
}
return true;
}