mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2026-05-02 12:07:02 +00:00
This commit is contained in:
@@ -57,6 +57,3 @@ export const alovaInstance = createAlova({
|
||||
onSuccess: handleResponse
|
||||
}
|
||||
});
|
||||
|
||||
export const DOCS_BASE_URL =
|
||||
process.env.NODE_ENV === 'development' ? '/emsesp.org' : 'https://emsesp.org';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { LogSettings, SystemStatus } from 'types';
|
||||
|
||||
import { DOCS_BASE_URL, alovaInstance } from './endpoints';
|
||||
import { alovaInstance } from './endpoints';
|
||||
|
||||
// systemStatus - also used to ping in System Monitor for pinging
|
||||
export const readSystemStatus = () =>
|
||||
@@ -13,14 +13,6 @@ export const updateLogSettings = (data: LogSettings) =>
|
||||
alovaInstance.Post('/rest/logSettings', data);
|
||||
export const fetchLogES = () => alovaInstance.Get('/es/log');
|
||||
|
||||
// get versions from emsesp.org/versions.json
|
||||
// uses native fetch (no custom headers) to keep this as a "simple" CORS
|
||||
export const getVersions = async <T = unknown>(): Promise<T> => {
|
||||
const res = await fetch(`${DOCS_BASE_URL}/versions.json`);
|
||||
if (!res.ok) throw new Error(res.statusText);
|
||||
return res.json() as Promise<T>;
|
||||
};
|
||||
|
||||
const UPLOAD_TIMEOUT = 60000; // 1 minute
|
||||
|
||||
export const uploadFile = (file: File) => {
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
import { memo, useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { Link } from 'react-router';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
@@ -34,7 +26,6 @@ import {
|
||||
|
||||
import * as SystemApi from 'api/system';
|
||||
import { API, callAction } from 'api/app';
|
||||
import { getVersions } from 'api/system';
|
||||
|
||||
import { dialogStyle } from 'CustomTheme';
|
||||
import { useRequest } from 'alova/client';
|
||||
@@ -79,21 +70,24 @@ interface VersionData {
|
||||
developer_mode: boolean;
|
||||
}
|
||||
|
||||
interface UpgradeCheckData {
|
||||
emsesp_version: string;
|
||||
dev_upgradeable: boolean;
|
||||
stable_upgradeable: boolean;
|
||||
}
|
||||
|
||||
interface VersionInfo {
|
||||
version: string;
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface Versions {
|
||||
stable: VersionInfo;
|
||||
dev: VersionInfo;
|
||||
last_updated: string;
|
||||
interface RemoteVersionInfo extends VersionInfo {
|
||||
upgradeable: boolean;
|
||||
}
|
||||
|
||||
interface CurrentVersionInfo extends VersionInfo {
|
||||
type: 'stable' | 'dev';
|
||||
}
|
||||
|
||||
// Response payload from the `getVersions` action
|
||||
interface VersionsResponse {
|
||||
current: CurrentVersionInfo;
|
||||
stable?: RemoteVersionInfo;
|
||||
dev?: RemoteVersionInfo;
|
||||
}
|
||||
|
||||
// Memoized components for better performance
|
||||
@@ -465,15 +459,6 @@ const Version = () => {
|
||||
const [showVersionInfo, setShowVersionInfo] = useState<number>(0); // 1 = stable, 2 = dev, 3 = partition
|
||||
const [firmwareSize, setFirmwareSize] = useState<number>(0);
|
||||
|
||||
const { send: sendCheckUpgrade } = useRequest(
|
||||
(versions: string) => callAction({ action: 'checkUpgrade', param: versions }),
|
||||
{ immediate: false }
|
||||
).onSuccess((event) => {
|
||||
const data = event.data as UpgradeCheckData;
|
||||
setDevUpgradeAvailable(data.dev_upgradeable);
|
||||
setStableUpgradeAvailable(data.stable_upgradeable);
|
||||
});
|
||||
|
||||
const { send: sendSetPartition } = useRequest(
|
||||
(partition: string) => callAction({ action: 'setPartition', param: partition }),
|
||||
{ immediate: false }
|
||||
@@ -490,7 +475,6 @@ const Version = () => {
|
||||
if (systemData.arduino_version.startsWith('Tasmota')) {
|
||||
setDownloadOnly(true);
|
||||
}
|
||||
setUsingDevVersion(systemData.emsesp_version.includes('dev'));
|
||||
});
|
||||
|
||||
const { send: sendUploadURL } = useRequest(
|
||||
@@ -498,32 +482,33 @@ const Version = () => {
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
// Fetch versions.json from emsesp.org once on mount.
|
||||
// Uses plain fetch (not alova) so the request stays a "simple" CORS request and avoids a preflight that emsesp.org rejects.
|
||||
// sendCheckUpgrade is stored in a ref because alova's useRequest returns a new function reference each render
|
||||
const sendCheckUpgradeRef = useRef(sendCheckUpgrade);
|
||||
sendCheckUpgradeRef.current = sendCheckUpgrade;
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
getVersions<Versions>()
|
||||
.then((versions) => {
|
||||
if (cancelled) return;
|
||||
setLatestVersion(versions.stable);
|
||||
setLatestDevVersion(versions.dev);
|
||||
sendCheckUpgradeRef.current(
|
||||
`${versions.stable.version},${versions.dev.version}`
|
||||
);
|
||||
setInternetLive(true);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (cancelled) return;
|
||||
toast.error(error instanceof Error ? error.message : 'An error occurred');
|
||||
setInternetLive(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
// Fetch latest stable/dev versions via the device. The ESP32 calls
|
||||
// emsesp.org/versions.json itself and includes its own `current` info plus
|
||||
// upgradeable flags. If the device has no internet, `stable`/`dev` are
|
||||
// absent and we surface that as "internet not live".
|
||||
useRequest(() => callAction({ action: 'getVersions' }))
|
||||
.onSuccess((event) => {
|
||||
const versions = event.data as VersionsResponse;
|
||||
setUsingDevVersion(versions.current?.type === 'dev');
|
||||
if (versions.stable) {
|
||||
setLatestVersion({
|
||||
version: versions.stable.version,
|
||||
date: versions.stable.date
|
||||
});
|
||||
setStableUpgradeAvailable(versions.stable.upgradeable);
|
||||
}
|
||||
if (versions.dev) {
|
||||
setLatestDevVersion({
|
||||
version: versions.dev.version,
|
||||
date: versions.dev.date
|
||||
});
|
||||
setDevUpgradeAvailable(versions.dev.upgradeable);
|
||||
}
|
||||
setInternetLive(Boolean(versions.stable || versions.dev));
|
||||
})
|
||||
.onError(() => {
|
||||
setInternetLive(false);
|
||||
});
|
||||
|
||||
const { send: sendAPI } = useRequest((data: APIcall) => API(data), {
|
||||
immediate: false
|
||||
|
||||
@@ -229,8 +229,7 @@ export default defineConfig(
|
||||
changeOrigin: true,
|
||||
secure: false
|
||||
},
|
||||
'/rest': 'http://localhost:3080',
|
||||
'/emsesp.org': 'http://localhost:3080'
|
||||
'/rest': 'http://localhost:3080'
|
||||
}
|
||||
},
|
||||
build: {
|
||||
|
||||
@@ -6,7 +6,6 @@ const router = AutoRouter();
|
||||
|
||||
const REST_ENDPOINT_ROOT = '/rest/';
|
||||
const API_ENDPOINT_ROOT = '/api/';
|
||||
const EMSESP_DOCS_ENDPOINT = '/emsesp.org/'; // for mock emsesp.org/versions.json
|
||||
|
||||
// HTTP HEADERS for msgpack
|
||||
const headers = {
|
||||
@@ -302,10 +301,10 @@ function updateMask(entity: any, de: any, dd: any) {
|
||||
const old_custom_name = dd.nodes[dd_objIndex].cn;
|
||||
console.log(
|
||||
'comparing names, old (' +
|
||||
old_custom_name +
|
||||
') with new (' +
|
||||
new_custom_name +
|
||||
')'
|
||||
old_custom_name +
|
||||
') with new (' +
|
||||
new_custom_name +
|
||||
')'
|
||||
);
|
||||
if (old_custom_name !== new_custom_name) {
|
||||
changed = true;
|
||||
@@ -415,36 +414,54 @@ function upgradeImportantMessages(version: string) {
|
||||
return { upgradeImportantMessageType: upgradeImportantMessageType_n };
|
||||
}
|
||||
|
||||
// called by Action endpoint checkUpgrade
|
||||
function check_upgrade(version: string) {
|
||||
let data = {};
|
||||
if (version) {
|
||||
const dev_version = version.split(',')[0];
|
||||
const stable_version = version.split(',')[1];
|
||||
// called by Action endpoint getVersions
|
||||
// Mirrors the C++ WebStatusService::getVersions() payload:
|
||||
// { current: { version, type, date },
|
||||
// stable?: { version, date, upgradeable },
|
||||
// dev?: { version, date, upgradeable } }
|
||||
// Set MOCK_OFFLINE = true to simulate a device with no internet (omits stable/dev).
|
||||
const MOCK_OFFLINE = false;
|
||||
function get_versions() {
|
||||
const isDev = THIS_VERSION.includes('dev');
|
||||
const data: {
|
||||
current: { version: string; type: 'stable' | 'dev'; date: string };
|
||||
stable?: { version: string; date: string; upgradeable: boolean };
|
||||
dev?: { version: string; date: string; upgradeable: boolean };
|
||||
} = {
|
||||
current: {
|
||||
version: THIS_VERSION,
|
||||
type: isDev ? 'dev' : 'stable',
|
||||
date: '2026-04-25T12:00:00'
|
||||
}
|
||||
};
|
||||
|
||||
console.log(
|
||||
'Upgrade this version (' +
|
||||
THIS_VERSION +
|
||||
') to dev (' +
|
||||
dev_version +
|
||||
') is ' +
|
||||
(DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') +
|
||||
' and to stable (' +
|
||||
stable_version +
|
||||
') is ' +
|
||||
(STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO')
|
||||
);
|
||||
data = {
|
||||
emsesp_version: THIS_VERSION,
|
||||
dev_upgradeable: DEV_VERSION_IS_UPGRADEABLE,
|
||||
stable_upgradeable: STABLE_VERSION_IS_UPGRADEABLE
|
||||
if (!MOCK_OFFLINE) {
|
||||
data.stable = {
|
||||
version: LATEST_STABLE_VERSION,
|
||||
date: '2026-04-25',
|
||||
upgradeable: STABLE_VERSION_IS_UPGRADEABLE
|
||||
};
|
||||
} else {
|
||||
console.log('requesting ems-esp version (' + THIS_VERSION + ')');
|
||||
data = {
|
||||
emsesp_version: THIS_VERSION
|
||||
data.dev = {
|
||||
version: LATEST_DEV_VERSION,
|
||||
date: '2026-04-25',
|
||||
upgradeable: DEV_VERSION_IS_UPGRADEABLE
|
||||
};
|
||||
}
|
||||
|
||||
console.log(
|
||||
'getVersions: current=' +
|
||||
THIS_VERSION +
|
||||
' stable=' +
|
||||
LATEST_STABLE_VERSION +
|
||||
' (upgradeable=' +
|
||||
(STABLE_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') +
|
||||
') dev=' +
|
||||
LATEST_DEV_VERSION +
|
||||
' (upgradeable=' +
|
||||
(DEV_VERSION_IS_UPGRADEABLE ? 'YES' : 'NO') +
|
||||
')' +
|
||||
(MOCK_OFFLINE ? ' [offline]' : '')
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -706,7 +723,6 @@ const EMSESP_ACTION_ENDPOINT = REST_ENDPOINT_ROOT + 'action';
|
||||
|
||||
// these are used in the API calls only
|
||||
const EMSESP_SYSTEM_INFO_ENDPOINT = API_ENDPOINT_ROOT + 'system/info';
|
||||
const EMSESP_VERSIONS_ENDPOINT = EMSESP_DOCS_ENDPOINT + 'versions.json';
|
||||
|
||||
const emsesp_info = {
|
||||
System: {
|
||||
@@ -5173,13 +5189,9 @@ router
|
||||
} else if (action === 'getCustomSupport') {
|
||||
// send custom support
|
||||
return custom_support();
|
||||
} else if (action === 'checkUpgrade') {
|
||||
// check upgrade
|
||||
// check if content has a param
|
||||
if (!content.param) {
|
||||
return check_upgrade('');
|
||||
}
|
||||
return check_upgrade(content.param);
|
||||
} else if (action === 'getVersions') {
|
||||
// get versions
|
||||
return get_versions();
|
||||
} else if (action === 'uploadURL') {
|
||||
// upload URL
|
||||
console.log('upload File from URL', content.param);
|
||||
@@ -5234,23 +5246,6 @@ router
|
||||
return status(404); // not found
|
||||
});
|
||||
|
||||
// Mock emsesp.org/versions.json
|
||||
router.get(EMSESP_VERSIONS_ENDPOINT, () => {
|
||||
const data = {
|
||||
stable: {
|
||||
version: LATEST_STABLE_VERSION,
|
||||
date: '2026-04-25'
|
||||
},
|
||||
dev: {
|
||||
version: LATEST_DEV_VERSION,
|
||||
date: '2026-04-25'
|
||||
},
|
||||
last_updated: new Date().toISOString()
|
||||
};
|
||||
console.log('sending versions.json: ', data);
|
||||
return data;
|
||||
});
|
||||
|
||||
// const logger: ResponseHandler = (response, request) => {
|
||||
// console.log(
|
||||
// response.status,
|
||||
|
||||
@@ -1865,6 +1865,7 @@ void EMSESP::loop() {
|
||||
}
|
||||
|
||||
// loop through the services
|
||||
webStatusService.loop(); // periodic refresh of cached versions.json
|
||||
rxservice_.loop(); // process any incoming Rx telegrams
|
||||
shower_.loop(); // check for shower on/off
|
||||
temperaturesensor_.loop(); // read sensor temperatures
|
||||
|
||||
@@ -899,9 +899,7 @@ void System::heartbeat_json(JsonObject output) {
|
||||
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S2
|
||||
output["temperature"] = (int)temperature_;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (!ethernet_connected_) {
|
||||
int8_t rssi = WiFi.RSSI();
|
||||
output["rssi"] = rssi;
|
||||
@@ -909,6 +907,11 @@ void System::heartbeat_json(JsonObject output) {
|
||||
output["wifireconnects"] = EMSESP::esp32React.getWifiReconnects();
|
||||
}
|
||||
#endif
|
||||
|
||||
// see if there is a newer version available
|
||||
if (EMSESP::webStatusService.versions_cache_valid()) {
|
||||
output["upgradeable"] = EMSESP::webStatusService.current_upgradeable();
|
||||
}
|
||||
}
|
||||
|
||||
// send periodic MQTT message with system information
|
||||
|
||||
@@ -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)
|
||||
@@ -311,46 +310,169 @@ 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 info plus the cached "stable" and "dev"
|
||||
// entries from emsesp.org/versions.json. The remote fetch is NOT done here: it
|
||||
// runs from the main loop task via WebStatusService::loop() so we never block
|
||||
// the AsyncTCP callback (which has a tiny ~6 KB stack — far too small for an
|
||||
// HTTPS handshake). If we have no cached data yet (no internet, fetch still
|
||||
// pending, parse error) the "stable" and "dev" sections are simply omitted so
|
||||
// the client can detect the offline case.
|
||||
void WebStatusService::getVersions(JsonObject root) {
|
||||
version::EMSESP_Version 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]. The upgradeable bool was computed
|
||||
// once during refresh_versions_cache() so we just read it here.
|
||||
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"] = version::EMSESP_Version("3.8.2") > current_version;
|
||||
|
||||
JsonObject dev_out = root["dev"].to<JsonObject>();
|
||||
dev_out["version"] = "3.8.3-dev.2";
|
||||
dev_out["date"] = "2026-04-25";
|
||||
dev_out["upgradeable"] = version::EMSESP_Version("3.8.3-dev.2") > 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, so it's safe to do HTTPS here.
|
||||
void WebStatusService::loop() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
// need a network
|
||||
if (!EMSESP::system_.ethernet_connected() && (WiFi.status() != WL_CONNECTED)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t now = uuid::get_uptime();
|
||||
|
||||
// first call after we have a network: schedule the initial fetch a little
|
||||
// later so we give NTP / DNS a chance to settle
|
||||
if (versions_next_fetch_ms_ == 0) {
|
||||
versions_next_fetch_ms_ = now + VERSIONS_INITIAL_DELAY_MS;
|
||||
if (versions_next_fetch_ms_ == 0) {
|
||||
versions_next_fetch_ms_ = 1; // avoid the "never scheduled" sentinel
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// not time yet (signed difference handles uint32 wrap)
|
||||
if ((int32_t)(now - 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("https://emsesp.org/versions.json")) {
|
||||
EMSESP::logger().debug("versions.json: failed to start HTTPS request");
|
||||
return false;
|
||||
}
|
||||
|
||||
int httpCode = http.GET();
|
||||
if (httpCode != HTTP_CODE_OK) {
|
||||
EMSESP::logger().debug("versions.json: HTTP %d", httpCode);
|
||||
http.end();
|
||||
return false;
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
DeserializationError err = deserializeJson(doc, http.getStream());
|
||||
http.end();
|
||||
if (err) {
|
||||
EMSESP::logger().debug("versions.json: parse error (%s)", err.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
version::EMSESP_Version 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() && version::EMSESP_Version(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)", versions_stable_.version.c_str(), versions_dev_.version.c_str());
|
||||
#endif
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
// returns if current dev/stable is upgradeable
|
||||
bool WebStatusService::current_upgradeable() const {
|
||||
if (!versions_cache_valid_) {
|
||||
return false;
|
||||
}
|
||||
version::EMSESP_Version 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
|
||||
|
||||
@@ -19,6 +19,17 @@ 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_;
|
||||
}
|
||||
|
||||
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 +41,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 +50,23 @@ 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 = ASAP
|
||||
|
||||
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
|
||||
static constexpr uint32_t VERSIONS_INITIAL_DELAY_MS = 30UL * 1000UL; // wait 30s after boot
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
Reference in New Issue
Block a user