mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-05-02 12:07:02 +00:00
Merge remote-tracking branch 'origin/dev' into core3
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include <esp_ota_ops.h>
|
||||
#include <HTTPClient.h>
|
||||
#endif
|
||||
|
||||
namespace emsesp {
|
||||
@@ -205,11 +206,11 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
|
||||
bool is_admin = AuthenticationPredicates::IS_ADMIN(authentication);
|
||||
|
||||
// call action command
|
||||
bool ok = false;
|
||||
bool ok = true;
|
||||
std::string action = json["action"];
|
||||
|
||||
if (action == "checkUpgrade") {
|
||||
ok = checkUpgrade(root, param); // param could be empty, if so only send back version
|
||||
if (action == "getVersions") {
|
||||
getVersions(root);
|
||||
} else if (action == "setPartition") {
|
||||
ok = EMSESP::system_.set_partition(param.c_str());
|
||||
} else if (action == "export") {
|
||||
@@ -224,10 +225,8 @@ void WebStatusService::action(AsyncWebServerRequest * request, JsonVariant json)
|
||||
ok = setSystemStatus(param.c_str());
|
||||
} else if (action == "resetMQTT" && is_admin) {
|
||||
EMSESP::mqtt_.reset_mqtt();
|
||||
ok = true;
|
||||
} else if (action == "upgradeImportantMessages") {
|
||||
root["upgradeImportantMessageType"] = upgradeImportantMessages(param);
|
||||
ok = true;
|
||||
}
|
||||
|
||||
#if defined(EMSESP_STANDALONE) && !defined(EMSESP_UNITY)
|
||||
@@ -262,7 +261,7 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
|
||||
|
||||
// it's a filename with a .bin or .md extension, try and extract the version from it
|
||||
// e.g. EMS-ESP-3_8_2-dev_13-ESP32-16MB+.bin -> major=3 minor=8 patch=2
|
||||
version::EMSESP_Version latest_version;
|
||||
FirmwareVersion latest_version;
|
||||
if ((version.find(".bin") != std::string::npos) || (version.find(".md") != std::string::npos)) {
|
||||
std::string filename = version;
|
||||
auto pos = filename.find("EMS-ESP-");
|
||||
@@ -283,18 +282,18 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
|
||||
std::string major_version = filename.substr(pos, underscore1 - pos);
|
||||
std::string minor_version = filename.substr(underscore1 + 1, underscore2 - underscore1 - 1);
|
||||
std::string patch_version = filename.substr(underscore2 + 1, dash - underscore2 - 1);
|
||||
latest_version = version::EMSESP_Version(major_version + "." + minor_version + "." + patch_version);
|
||||
latest_version = FirmwareVersion(major_version + "." + minor_version + "." + patch_version);
|
||||
} else {
|
||||
// if it's .json file exit
|
||||
if (version.find(".json") != std::string::npos) {
|
||||
return 0;
|
||||
} else {
|
||||
// treat it like a version string like "3.9.0"
|
||||
latest_version = version::EMSESP_Version(version);
|
||||
latest_version = FirmwareVersion(version);
|
||||
}
|
||||
}
|
||||
|
||||
version::EMSESP_Version current_version(current_version_s); // get current version
|
||||
FirmwareVersion current_version(current_version_s); // get current version
|
||||
|
||||
if ((current_version.major() <= 3 && current_version.minor() <= 8) && (latest_version.major() == 3 && latest_version.minor() == 9)) {
|
||||
return 1; // if moving from below 3.8.x to 3.9.x return 1
|
||||
@@ -311,46 +310,164 @@ uint8_t WebStatusService::upgradeImportantMessages(std::string & version) {
|
||||
return 0; // if it's not a valid version upgrade return 0
|
||||
}
|
||||
|
||||
// action = checkUpgrade
|
||||
// versions holds the latest development version and stable version in one string, comma separated
|
||||
bool WebStatusService::checkUpgrade(JsonObject root, std::string & version) {
|
||||
if (!version.empty()) {
|
||||
version::EMSESP_Version current_version(current_version_s);
|
||||
version::EMSESP_Version latest_dev_version(version.substr(0, version.find(',')));
|
||||
version::EMSESP_Version latest_stable_version(version.substr(version.find(',') + 1));
|
||||
// action = getVersions
|
||||
// returns the device's current version for dev and stable
|
||||
// The remote fetch runs from the main loop task via WebStatusService::loop() so that we never block the AsyncTCP callback
|
||||
void WebStatusService::getVersions(JsonObject root) {
|
||||
FirmwareVersion current_version(current_version_s);
|
||||
bool is_dev = current_version.prerelease().find("dev") != std::string::npos;
|
||||
|
||||
bool dev_upgradeable = latest_dev_version > current_version;
|
||||
bool stable_upgradeable = latest_stable_version > current_version;
|
||||
JsonObject current = root["current"].to<JsonObject>();
|
||||
current["version"] = current_version_s;
|
||||
current["type"] = is_dev ? "dev" : "stable";
|
||||
current["date"] = "";
|
||||
current["upgradeable"] = current_upgradeable(); // false if cache not valid yet
|
||||
|
||||
#if defined(EMSESP_DEBUG)
|
||||
// look for dev in the name to determine if we're using a dev release
|
||||
bool using_dev_version = !current_version.prerelease().find("dev");
|
||||
EMSESP::logger()
|
||||
.debug("Checking version upgrade. This version=%d.%d.%d-%s (%s),latest dev=%d.%d.%d-%s (%s upgradeable),latest stable=%d.%d.%d-%s (%s upgradeable)",
|
||||
current_version.major(),
|
||||
current_version.minor(),
|
||||
current_version.patch(),
|
||||
current_version.prerelease().c_str(),
|
||||
using_dev_version ? "Dev" : "Stable",
|
||||
latest_dev_version.major(),
|
||||
latest_dev_version.minor(),
|
||||
latest_dev_version.patch(),
|
||||
latest_dev_version.prerelease().c_str(),
|
||||
dev_upgradeable ? "is" : "is not",
|
||||
latest_stable_version.major(),
|
||||
latest_stable_version.minor(),
|
||||
latest_stable_version.patch(),
|
||||
latest_stable_version.prerelease().c_str(),
|
||||
stable_upgradeable ? "is" : "is not");
|
||||
#endif
|
||||
|
||||
root["dev_upgradeable"] = dev_upgradeable;
|
||||
root["stable_upgradeable"] = stable_upgradeable;
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// pull the install_date for the running partition (if known)
|
||||
const esp_partition_t * running = esp_ota_get_running_partition();
|
||||
if (running != nullptr) {
|
||||
const auto & info = EMSESP::system_.partition_info_;
|
||||
auto it = info.find(running->label);
|
||||
if (it != info.end() && it->second.install_date > 0) {
|
||||
char time_string[25];
|
||||
time_t d = it->second.install_date;
|
||||
strftime(time_string, sizeof(time_string), "%FT%T", localtime(&d));
|
||||
current["date"] = time_string;
|
||||
}
|
||||
}
|
||||
|
||||
root["emsesp_version"] = current_version_s; // always send back current version
|
||||
if (!versions_cache_valid_) {
|
||||
// no successful fetch yet (no network, fetch pending, or parse error)
|
||||
return;
|
||||
}
|
||||
|
||||
// copies a cached entry into root[key]
|
||||
auto add_section = [&](const char * key, const VersionInfo & info) {
|
||||
if (info.version.empty()) {
|
||||
return;
|
||||
}
|
||||
JsonObject out = root[key].to<JsonObject>();
|
||||
out["version"] = info.version;
|
||||
out["date"] = info.date;
|
||||
out["upgradeable"] = info.upgradeable;
|
||||
};
|
||||
|
||||
add_section("stable", versions_stable_);
|
||||
add_section("dev", versions_dev_);
|
||||
#else
|
||||
// standalone/test build: provide deterministic dummy data
|
||||
JsonObject stable_out = root["stable"].to<JsonObject>();
|
||||
stable_out["version"] = "3.8.2";
|
||||
stable_out["date"] = "2026-04-25";
|
||||
stable_out["upgradeable"] = FirmwareVersion("3.8.2") > current_version;
|
||||
|
||||
JsonObject dev_out = root["dev"].to<JsonObject>();
|
||||
dev_out["version"] = "3.9.0-dev.1";
|
||||
dev_out["date"] = "2026-04-25";
|
||||
dev_out["upgradeable"] = FirmwareVersion("3.9.0-dev.1") > current_version;
|
||||
#endif
|
||||
}
|
||||
|
||||
// periodic refresh (1 hour) of the cached versions.json
|
||||
// runs on the main loop task, which has a much bigger stack than AsyncTCP needed for https
|
||||
void WebStatusService::loop() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// need a network
|
||||
if (!EMSESP::system_.ethernet_connected() && (WiFi.status() != WL_CONNECTED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 0 = idle, nothing scheduled
|
||||
if (versions_next_fetch_ms_ == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// not time yet (signed difference handles uint32 wrap)
|
||||
if ((int32_t)(uuid::get_uptime() - versions_next_fetch_ms_) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = refresh_versions_cache();
|
||||
uint32_t next = uuid::get_uptime() + (ok ? VERSIONS_REFRESH_INTERVAL_MS : VERSIONS_RETRY_INTERVAL_MS);
|
||||
if (next == 0) {
|
||||
next = 1;
|
||||
}
|
||||
versions_next_fetch_ms_ = next;
|
||||
#endif
|
||||
}
|
||||
|
||||
// runs on the main loop task — never call this from an AsyncWebServer handler
|
||||
bool WebStatusService::refresh_versions_cache() {
|
||||
#ifdef EMSESP_STANDALONE
|
||||
return false;
|
||||
#else
|
||||
HTTPClient http;
|
||||
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
|
||||
http.setTimeout(5000);
|
||||
http.useHTTP10(true);
|
||||
|
||||
if (!http.begin(EMSESP_VERSIONS_URL)) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("versions.json: failed to start HTTPS request");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
int httpCode = http.GET();
|
||||
if (httpCode != HTTP_CODE_OK) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("versions.json: HTTP %d", httpCode);
|
||||
#endif
|
||||
http.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
DeserializationError err = deserializeJson(doc, http.getStream());
|
||||
http.end();
|
||||
if (err) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("versions.json: parse error");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
FirmwareVersion current_version(current_version_s);
|
||||
|
||||
auto read_section = [&doc, ¤t_version](const char * key, VersionInfo & out) {
|
||||
JsonObjectConst section = doc[key];
|
||||
if (section.isNull()) {
|
||||
out = {};
|
||||
return;
|
||||
}
|
||||
out.version = section["version"] | "";
|
||||
out.date = section["date"] | "";
|
||||
out.upgradeable = !out.version.empty() && FirmwareVersion(out.version) > current_version;
|
||||
};
|
||||
|
||||
read_section("stable", versions_stable_);
|
||||
read_section("dev", versions_dev_);
|
||||
|
||||
versions_cache_valid_ = true;
|
||||
#if defined(EMSESP_DEBUG)
|
||||
EMSESP::logger().debug("versions.json: refreshed (stable=%s dev=%s), current=%s",
|
||||
versions_stable_.version.c_str(),
|
||||
versions_dev_.version.c_str(),
|
||||
current_version_s.c_str());
|
||||
#endif
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
// returns if current dev/stable is upgradeable
|
||||
bool WebStatusService::current_upgradeable() const {
|
||||
if (!versions_cache_valid_) {
|
||||
return false;
|
||||
}
|
||||
FirmwareVersion current_version(current_version_s);
|
||||
bool is_dev = current_version.prerelease().find("dev") != std::string::npos;
|
||||
return is_dev ? versions_dev_.upgradeable : versions_stable_.upgradeable;
|
||||
}
|
||||
|
||||
// action = allvalues
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
#define EMSESP_SYSTEM_STATUS_SERVICE_PATH "/rest/systemStatus"
|
||||
#define EMSESP_ACTION_SERVICE_PATH "/rest/action"
|
||||
|
||||
#include "../core/EMSESP_Version.h"
|
||||
#define EMSESP_VERSIONS_URL "http://emsesp.org/versions.json"
|
||||
|
||||
#include "../core/firmwareVersion.h"
|
||||
#include "../emsesp_version.h"
|
||||
|
||||
namespace emsesp {
|
||||
@@ -19,6 +21,22 @@ class WebStatusService {
|
||||
return current_version_s;
|
||||
}
|
||||
|
||||
// called from EMSESP::loop() to refresh the cached versions.json from emsesp.org
|
||||
// so that the web request handler never has to do blocking HTTPS on the small AsyncTCP stack
|
||||
void loop();
|
||||
|
||||
// true once we've had at least one successful versions.json fetch
|
||||
bool versions_cache_valid() const {
|
||||
return versions_cache_valid_;
|
||||
}
|
||||
|
||||
// refresh the versions.json cache
|
||||
void schedule_versions_refresh() {
|
||||
versions_next_fetch_ms_ = 1;
|
||||
}
|
||||
|
||||
bool current_upgradeable() const; // true if a newer version is available
|
||||
|
||||
// make action function public so we can test in the debug and standalone mode
|
||||
#ifndef EMSESP_STANDALONE
|
||||
protected:
|
||||
@@ -30,7 +48,7 @@ class WebStatusService {
|
||||
SecurityManager * _securityManager;
|
||||
|
||||
// actions
|
||||
bool checkUpgrade(JsonObject root, std::string & latest_version);
|
||||
void getVersions(JsonObject root);
|
||||
bool exportData(JsonObject root, std::string & type);
|
||||
bool getCustomSupport(JsonObject root);
|
||||
bool uploadURL(const char * url);
|
||||
@@ -39,6 +57,22 @@ class WebStatusService {
|
||||
uint8_t upgradeImportantMessages(std::string & version);
|
||||
|
||||
std::string current_version_s = EMSESP_APP_VERSION;
|
||||
|
||||
// cached emsesp.org/versions.json. Refreshed from the main loop task, which has more stack.
|
||||
struct VersionInfo {
|
||||
std::string version;
|
||||
std::string date;
|
||||
bool upgradeable = false;
|
||||
};
|
||||
VersionInfo versions_stable_;
|
||||
VersionInfo versions_dev_;
|
||||
bool versions_cache_valid_ = false; // true once we've had at least one successful fetch
|
||||
uint32_t versions_next_fetch_ms_ = 0; // uuid::get_uptime() of the next attempt; 0 = idle
|
||||
|
||||
bool refresh_versions_cache(); // does the actual HTTPS fetch + parse, returns true on success
|
||||
|
||||
static constexpr uint32_t VERSIONS_REFRESH_INTERVAL_MS = 60UL * 60UL * 1000UL; // 1 hour on success
|
||||
static constexpr uint32_t VERSIONS_RETRY_INTERVAL_MS = 5UL * 60UL * 1000UL; // 5 min after failure
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
Reference in New Issue
Block a user