- @let fanRpm1 = (info.fanrpm ?? 0);
- @let fanRpm2 = (info.fanrpm2 ?? 0);
- @let fanC1HasRpm = fanRpm1 > 0;
- @let fanC2HasRpm = fanRpm2 > 0;
- @let singleFanUsesC2 = !fanC1HasRpm && fanC2HasRpm;
- @let primaryFanPercent = singleFanUsesC2 ? (info.fanspeed2 ?? 0) : (info.fanspeed ?? 0);
- @let primaryFanRpm = singleFanUsesC2 ? (info.fanrpm2 ?? 0) : (info.fanrpm ?? 0);
- @let showFanC1 = (primaryFanPercent > 0) || (primaryFanRpm > 0) || !fanC2HasRpm;
- @let showFanC2 = fanC1HasRpm && fanC2HasRpm;
+ @let showFanC2 = (info.fanCount ?? 1) > 1;
0" role="img" data-bar="vr-temp"
@@ -605,15 +597,15 @@
{{ 'COMMON.SHUTDOWN' | translate }}
- @let fanC1LowRpm = shouldShowLowRpmHint(primaryFanPercent, primaryFanRpm);
-
-
+ [attr.aria-label]="getFanAriaLabel(showFanC2 ? 1 : null, info.fanspeed ?? 0, info.fanrpm ?? 0)">
+
{{ 'HOME.FAN_ASICS_LABEL' | translate }}
{{ 'COMMON.SHUTDOWN' | translate }}
-
- {{ primaryFanRpm | number: '1.0-0' }} RPM
+
+ {{ (info.fanrpm ?? 0) | number: '1.0-0' }} RPM
/
- {{ primaryFanPercent | number: '1.0-0' }} %
+ {{ (info.fanspeed ?? 0) | number: '1.0-0' }} %
diff --git a/main/http_server/axe-os/src/app/pages/home/home.component.scss b/main/http_server/axe-os/src/app/pages/home/home.component.scss
index 454d371f4..f54590247 100644
--- a/main/http_server/axe-os/src/app/pages/home/home.component.scss
+++ b/main/http_server/axe-os/src/app/pages/home/home.component.scss
@@ -655,6 +655,30 @@ nb-card-header{
line-height: 1;
}
+/* Reset-session-stats button: pinned into the tile's top-right corner.
+ Strip the nbButton padding so the icon sits right in the corner and
+ clears the dual-pool allocation % that is rendered on the same row. */
+.pool-reset-btn.pool-reset-btn{
+ position: absolute;
+ top: 1px;
+ right: 1px;
+ z-index: 2;
+ opacity: 0.5;
+ padding: 0 !important;
+ min-width: 0 !important;
+ width: 16px;
+ height: 16px;
+ line-height: 1;
+
+ nb-icon{
+ font-size: 14px;
+ }
+
+ &:hover{
+ opacity: 0.9;
+ }
+}
+
/* Host/Port/Ping/Loss block should be visually smaller (same size as labels) */
.pool-tile-info__bottom{
/* Keep Host/Port/Ping/Loss within the same vertical band as the grey box */
diff --git a/main/http_server/axe-os/src/app/pages/home/home.component.ts b/main/http_server/axe-os/src/app/pages/home/home.component.ts
index 8e97eac07..3bd0ef89c 100644
--- a/main/http_server/axe-os/src/app/pages/home/home.component.ts
+++ b/main/http_server/axe-os/src/app/pages/home/home.component.ts
@@ -212,9 +212,10 @@ export class HomeComponent implements AfterViewChecked, OnInit, OnDestroy {
* Input Voltage warn-band (yellow) should be data-driven (HOME_CFG) and centralized.
* We keep the template free of thresholds by routing through this method.
*/
- public isInputVoltageWarn(voltage: any): boolean {
- const band = HOME_CFG.tiles.inputVoltageBand;
- return isOutsideBand(voltage, band.low, band.high);
+ public isInputVoltageWarn(voltage: any, voltageMin?: number, voltageMax?: number): boolean {
+ const low = voltageMin ?? HOME_CFG.tiles.inputVoltageBand.low;
+ const high = voltageMax ?? HOME_CFG.tiles.inputVoltageBand.high;
+ return isOutsideBand(voltage, low, high);
}
/**
diff --git a/main/http_server/axe-os/src/app/pages/home/home.quicklinks.ts b/main/http_server/axe-os/src/app/pages/home/home.quicklinks.ts
index 9ad3ce0c5..f116d1b19 100644
--- a/main/http_server/axe-os/src/app/pages/home/home.quicklinks.ts
+++ b/main/http_server/axe-os/src/app/pages/home/home.quicklinks.ts
@@ -271,12 +271,29 @@ const POOLS: PoolMeta[] = [
match: (h) => h.includes('powermining.io'),
quickLink: (a) => `https://pool.powermining.io/#/app/${a}`,
},
- {
- id: 'blitzpool',
- name: 'blitzpool.yourdevice.ch',
- match: (h) => h.includes('blitzpool.yourdevice.ch'),
- quickLink: (a) => `https://blitzpool.yourdevice.ch/#/app/${a}`,
- },
+ {
+ id: 'blitzpool',
+ name: 'blitzpool.yourdevice.ch',
+ match: (h) => h.includes('blitzpool.yourdevice.ch'),
+ quickLink: (a) => `https://blitzpool.yourdevice.ch/#/app/${a}`,
+ },
+ {
+ id: 'braiins-solo',
+ name: 'Braiins Solo',
+ match: (h) => h.includes('solo.stratum.braiins.com'),
+ quickLink: (a) => `https://solo.braiins.com/stats/${a}`,
+ faviconHost: 'solo.braiins.com',
+ faviconPath: '/icon.png'
+ },
+ {
+ id: 'mining-dutch',
+ name: 'mining-dutch.nl',
+ // stratum host is a regional subdomain (e.g. europe.mining-dutch.nl);
+ // account-based pool, so link to the main site instead of the wallet
+ match: (h) => h.includes('mining-dutch.nl'),
+ quickLink: () => `https://www.mining-dutch.nl`,
+ faviconHost: 'www.mining-dutch.nl',
+ }
];
/* ------------------------------------------------------------------
diff --git a/main/http_server/axe-os/src/app/services/system.service.ts b/main/http_server/axe-os/src/app/services/system.service.ts
index 8236e5ed0..c35e045f3 100644
--- a/main/http_server/axe-os/src/app/services/system.service.ts
+++ b/main/http_server/axe-os/src/app/services/system.service.ts
@@ -334,7 +334,7 @@ export class SystemService {
}
public sendAlertTest(uri: string = '', totp?: string): Observable
{
- const headers = totp ? new HttpHeaders({ 'X-OTP-Code': totp }) : undefined;
+ const headers = totp ? new HttpHeaders({ 'X-TOTP': totp }) : undefined;
return this.httpClient.post(`${uri}/api/alert/test`, {}, {
responseType: 'text',
headers,
diff --git a/main/http_server/handler_system.cpp b/main/http_server/handler_system.cpp
index 036929ec0..43ac9c0e8 100644
--- a/main/http_server/handler_system.cpp
+++ b/main/http_server/handler_system.cpp
@@ -92,6 +92,10 @@ esp_err_t GET_system_info(httpd_req_t *req)
char *fallbackStratumURL = Config::getStratumFallbackURL();
char *fallbackStratumUser= Config::getStratumFallbackUser();
+ char *sv2_auth = Config::getSV2AuthorityPubkey();
+ char *fb_sv2_auth = Config::getFallbackSV2AuthorityPubkey();
+
+
// static
doc["asicCount"] = board->getAsicCount();
doc["smallCoreCount"] = (board->getAsics()) ? board->getAsics()->getSmallCoreCount() : 0;
@@ -210,14 +214,8 @@ esp_err_t GET_system_info(httpd_req_t *req)
doc["fallbackStratumTLS"] = Config::isStratumFallbackTLS();
doc["stratumProtocol"] = Config::getStratumProtocol();
doc["fallbackStratumProtocol"] = Config::getFallbackStratumProtocol();
- {
- char *sv2_auth = Config::getSV2AuthorityPubkey();
- doc["sv2AuthorityPubkey"] = sv2_auth ? sv2_auth : "";
- safe_free(sv2_auth);
- char *fb_sv2_auth = Config::getFallbackSV2AuthorityPubkey();
- doc["fallbackSv2AuthorityPubkey"] = fb_sv2_auth ? fb_sv2_auth : "";
- safe_free(fb_sv2_auth);
- }
+ doc["sv2AuthorityPubkey"] = sv2_auth;
+ doc["fallbackSv2AuthorityPubkey"] = fb_sv2_auth;
doc["sv2ChannelType"] = Config::getSV2ChannelType();
doc["fallbackSv2ChannelType"] = Config::getFallbackSV2ChannelType();
doc["voltage"] = POWER_MANAGEMENT_MODULE.getVoltage();
@@ -264,6 +262,9 @@ esp_err_t GET_system_info(httpd_req_t *req)
free(fallbackStratumURL);
free(fallbackStratumUser);
+ free(sv2_auth);
+ free(fb_sv2_auth);
+
return ret;
}
diff --git a/main/http_server/http_utils.cpp b/main/http_server/http_utils.cpp
index f60a63ff1..b3f6a2252 100644
--- a/main/http_server/http_utils.cpp
+++ b/main/http_server/http_utils.cpp
@@ -178,7 +178,8 @@ esp_err_t getPostData(httpd_req_t *req)
}
while (cur_len < total_len) {
- received = httpd_req_recv(req, buf + cur_len, total_len);
+ int remaining = total_len - cur_len;
+ received = httpd_req_recv(req, buf + cur_len, remaining);
if (received <= 0) {
/* Respond with 500 Internal Server Error */
ESP_LOGE(TAG, "error receiving data");
diff --git a/main/macros.h b/main/macros.h
index 9db3dcd09..70a8acbfc 100644
--- a/main/macros.h
+++ b/main/macros.h
@@ -1,5 +1,7 @@
#pragma once
+#include // PThreadGuard below uses pthread_mutex_*
+
static inline void* _malloc_psram(size_t sz) {
if (!sz) return NULL;
diff --git a/main/stratum/stratum_manager.cpp b/main/stratum/stratum_manager.cpp
index 9ea0406f1..6f1784eac 100644
--- a/main/stratum/stratum_manager.cpp
+++ b/main/stratum/stratum_manager.cpp
@@ -302,6 +302,9 @@ void StratumManager::loadSettings(bool reconnect)
m_stratumTasks[i]->triggerReconnect();
}
+ // reset session stats for this pool
+ resetPoolSessionStats(i);
+
// reset ping stats
if (m_pingTasks[i]) {
m_pingTasks[i]->reset();
diff --git a/main/stratum/stratum_manager.h b/main/stratum/stratum_manager.h
index b5f526423..0d6a9050c 100644
--- a/main/stratum/stratum_manager.h
+++ b/main/stratum/stratum_manager.h
@@ -98,6 +98,10 @@ class StratumManager {
virtual int getPoolMode() = 0;
+ // reset stats for a single pool (called from loadSettings with mutex held)
+ virtual void resetPoolSessionStats(int pool) {
+ }
+
public:
virtual void resetSessionStats() {
PThreadGuard lock(m_mutex);
diff --git a/main/stratum/stratum_manager_dual_pool.h b/main/stratum/stratum_manager_dual_pool.h
index 44cf56bd3..4492def5b 100644
--- a/main/stratum/stratum_manager_dual_pool.h
+++ b/main/stratum/stratum_manager_dual_pool.h
@@ -97,15 +97,19 @@ class StratumManagerDualPool : public StratumManager {
return (m_balance >= 50) ? m_networkDifficulty[0] : m_networkDifficulty[1];
}
+ virtual void resetPoolSessionStats(int pool) override {
+ StratumManager::resetPoolSessionStats(pool);
+ m_accepted[pool] = 0;
+ m_rejected[pool] = 0;
+ m_bestSessionDiff[pool] = 0;
+ suffixString(std::max(m_bestSessionDiff[0], m_bestSessionDiff[1]), m_bestSessionDiffString, DIFF_STRING_SIZE, 0);
+ if (m_stratumTasks[pool]) m_stratumTasks[pool]->m_poolErrors = 0;
+ }
+
virtual void resetSessionStats() override {
PThreadGuard lock(m_mutex);
- m_foundBlocks = 0;
for (int i = 0; i < 2; i++) {
- m_accepted[i] = 0;
- m_rejected[i] = 0;
- m_bestSessionDiff[i] = 0;
- suffixString(0, m_bestSessionDiffString, DIFF_STRING_SIZE, 0);
- if (m_stratumTasks[i]) m_stratumTasks[i]->m_poolErrors = 0;
+ resetPoolSessionStats(i);
}
}
diff --git a/main/stratum/stratum_manager_fallback.h b/main/stratum/stratum_manager_fallback.h
index 9c702ad72..b298a9eb8 100644
--- a/main/stratum/stratum_manager_fallback.h
+++ b/main/stratum/stratum_manager_fallback.h
@@ -80,15 +80,20 @@ class StratumManagerFallback : public StratumManager {
return m_networkDifficulty;
}
- virtual void resetSessionStats() override {
- PThreadGuard lock(m_mutex);
- m_foundBlocks = 0;
+ virtual void resetPoolSessionStats(int pool) override {
+ StratumManager::resetPoolSessionStats(pool);
+ // fallback mode shares a single counter for both pools
m_accepted = 0;
m_rejected = 0;
m_bestSessionDiff = 0;
suffixString(0, m_bestSessionDiffString, DIFF_STRING_SIZE, 0);
+ if (m_stratumTasks[pool]) m_stratumTasks[pool]->m_poolErrors = 0;
+ }
+
+ virtual void resetSessionStats() override {
+ PThreadGuard lock(m_mutex);
for (int i = 0; i < 2; i++) {
- if (m_stratumTasks[i]) m_stratumTasks[i]->m_poolErrors = 0;
+ resetPoolSessionStats(i);
}
}
diff --git a/main/stratum/stratum_task.cpp b/main/stratum/stratum_task.cpp
index 385463dec..7ee787f47 100644
--- a/main/stratum/stratum_task.cpp
+++ b/main/stratum/stratum_task.cpp
@@ -308,12 +308,15 @@ void StratumTaskBase::task()
m_poolErrors++;
}
+ // gate new submits before tearing the transport down; the
+ // transport's internal lock covers a submit already inside send()
+ m_isConnected = false;
+
// shutdown and reconnect
ESP_LOGIE(m_reconnect, m_tag, "Shutdown socket ...");
m_transport->close();
disconnectedCallback();
- m_isConnected = false;
// skip reconnect delay
if (m_reconnect) {
diff --git a/main/stratum/stratum_task_v2.cpp b/main/stratum/stratum_task_v2.cpp
index 388608ec5..fd2def625 100644
--- a/main/stratum/stratum_task_v2.cpp
+++ b/main/stratum/stratum_task_v2.cpp
@@ -589,10 +589,9 @@ void StratumTaskV2::submitShare(const char *jobid, const char *extranonce_2,
const uint32_t ntime, const uint32_t nonce,
const uint32_t version_rolled, const uint32_t version_base)
{
- sv2_noise_ctx_t *noise = m_noiseTransport.getNoiseCtx();
- esp_transport_handle_t transport = m_noiseTransport.getTransportHandle();
-
- if (!noise || !transport) {
+ // this runs on the ASIC result task; go through m_noiseTransport.send()
+ // so its lock protects the noise ctx against a concurrent close()
+ if (!m_noiseTransport.getNoiseCtx()) {
ESP_LOGE(m_tag, "Cannot submit share: no connection");
return;
}
@@ -636,7 +635,7 @@ void StratumTaskV2::submitShare(const char *jobid, const char *extranonce_2,
m_lastSubmitTimeUs = esp_timer_get_time();
- if (sv2_noise_send(noise, transport, buf, frame_len) != 0) {
+ if (m_noiseTransport.send(buf, frame_len) != frame_len) {
ESP_LOGE(m_tag, "Failed to send share");
}
}
diff --git a/main/stratum/stratum_transport.cpp b/main/stratum/stratum_transport.cpp
index 80b9358d2..cb7f95b96 100644
--- a/main/stratum/stratum_transport.cpp
+++ b/main/stratum/stratum_transport.cpp
@@ -7,13 +7,26 @@
#include "esp_transport_tcp.h"
#include "esp_transport_ssl.h"
+#include "macros.h"
#include "nvs_config.h"
#include "stratum_transport.h"
+// esp_transport_get_socket() exists in IDF 5.3 but is only declared in
+// the private header (esp_transport_internal.h). A forward declaration
+// avoids pulling in the entire private API surface.
+extern "C" int esp_transport_get_socket(esp_transport_handle_t t);
+
static const char* TAG = "stratum_transport";
StratumTransport::StratumTransport(bool use_tls)
- : m_use_tls(use_tls), m_t(nullptr) {}
+ : m_use_tls(use_tls), m_t(nullptr)
+{
+ pthread_mutexattr_t attr;
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&m_lock, &attr);
+ pthread_mutexattr_destroy(&attr);
+}
StratumTransport::~StratumTransport()
{
@@ -22,6 +35,10 @@ StratumTransport::~StratumTransport()
bool StratumTransport::connect(const char* host, const char* ip, uint16_t port)
{
+ // hold the lock across the whole connect so a racing submitShare can't
+ // touch a half-initialized transport (esp. mid-TLS-handshake)
+ PThreadGuard g(m_lock);
+
close();
esp_transport_handle_t t = nullptr;
@@ -64,6 +81,8 @@ bool StratumTransport::connect(const char* host, const char* ip, uint16_t port)
int StratumTransport::send(const void* data, size_t len)
{
+ PThreadGuard g(m_lock);
+
if (!m_t) {
errno = ENOTCONN;
return -1;
@@ -119,6 +138,8 @@ int StratumTransport::recv(void* buf, size_t len)
bool StratumTransport::isConnected()
{
+ PThreadGuard g(m_lock);
+
if (!m_t) {
return false;
}
@@ -127,8 +148,25 @@ bool StratumTransport::isConnected()
return (r >= 0);
}
+void StratumTransport::shutdownSocket_()
+{
+ // only the owner task mutates m_t, so reading it here without the lock
+ // is safe; shutdown() frees nothing but makes a writer blocked inside
+ // esp_transport_write() fail out and release m_lock
+ if (!m_t) {
+ return;
+ }
+ int sock = esp_transport_get_socket(m_t);
+ if (sock >= 0) {
+ shutdown(sock, SHUT_RDWR);
+ }
+}
+
void StratumTransport::close()
{
+ shutdownSocket_();
+
+ PThreadGuard g(m_lock);
if (m_t) {
esp_transport_close(m_t);
esp_transport_destroy(m_t);
@@ -138,19 +176,19 @@ void StratumTransport::close()
void StratumTransport::applyKeepAlive_()
{
- esp_transport_keep_alive_t ka = {};
- ka.keep_alive_enable = Config::isStratumKeepaliveEnabled();
- ka.keep_alive_idle = 10;
- ka.keep_alive_interval = 5;
- ka.keep_alive_count = 3;
+ m_keepAlive = {};
+ m_keepAlive.keep_alive_enable = Config::isStratumKeepaliveEnabled();
+ m_keepAlive.keep_alive_idle = 10;
+ m_keepAlive.keep_alive_interval = 5;
+ m_keepAlive.keep_alive_count = 3;
- if (!ka.keep_alive_enable) {
+ if (!m_keepAlive.keep_alive_enable) {
return;
}
if (m_use_tls) {
- esp_transport_ssl_set_keep_alive(m_t, &ka);
+ esp_transport_ssl_set_keep_alive(m_t, &m_keepAlive);
} else {
- esp_transport_tcp_set_keep_alive(m_t, &ka);
+ esp_transport_tcp_set_keep_alive(m_t, &m_keepAlive);
}
}
diff --git a/main/stratum/stratum_transport.h b/main/stratum/stratum_transport.h
index bcb6a3a82..b14c2f07b 100644
--- a/main/stratum/stratum_transport.h
+++ b/main/stratum/stratum_transport.h
@@ -2,6 +2,7 @@
#include
#include
+#include
#include
#include
@@ -24,7 +25,21 @@ class StratumTransport {
void applyKeepAlive_();
protected:
+ void shutdownSocket_();
+
esp_transport_handle_t m_t;
+
+ // Guards m_t (and the noise ctx in the derived class) between the task
+ // owning the connection (connect/close) and the ASIC result task
+ // submitting shares (send/isConnected). recv() is only called by the
+ // owner task and stays unlocked so a blocking read can't stall submits.
+ // Recursive because connect() calls close().
+ pthread_mutex_t m_lock;
+
+ // esp_transport_*_set_keep_alive() stores this pointer (no copy) and
+ // dereferences it later inside esp_transport_connect(), so the config
+ // must outlive applyKeepAlive_()
+ esp_transport_keep_alive_t m_keepAlive = {};
};
class TcpStratumTransport : public StratumTransport {
diff --git a/main/stratum/stratum_transport_noise.cpp b/main/stratum/stratum_transport_noise.cpp
index 518ca0cde..7d51cb5d0 100644
--- a/main/stratum/stratum_transport_noise.cpp
+++ b/main/stratum/stratum_transport_noise.cpp
@@ -2,6 +2,8 @@
#include "esp_log.h"
+#include "macros.h"
+
extern "C" {
#include "sv2_noise.h"
#include "sv2_protocol.h"
@@ -38,21 +40,10 @@ bool NoiseStratumTransport::connect(const char *host, const char *ip, uint16_t p
return false;
}
-#if 0
- // TODO doesn't compile with esp-idf 5.3
- // Set socket options for SV2 (longer recv timeout for mining)
- int sock = esp_transport_get_socket(m_t);
- if (sock >= 0) {
- struct timeval snd_timeout = { .tv_sec = 5, .tv_usec = 0 };
- struct timeval rcv_timeout = { .tv_sec = 60 * 3, .tv_usec = 0 };
- setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &snd_timeout, sizeof(snd_timeout));
- setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &rcv_timeout, sizeof(rcv_timeout));
- }
-#endif
-
- // Create Noise context
- m_noise_ctx = sv2_noise_create();
- if (!m_noise_ctx) {
+ // Create Noise context; publish it to m_noise_ctx only after the
+ // handshake succeeded so a concurrent send() can't use it mid-handshake
+ sv2_noise_ctx_t *ctx = sv2_noise_create();
+ if (!ctx) {
ESP_LOGE(TAG, "Failed to create Noise context");
StratumTransport::close();
return false;
@@ -60,22 +51,28 @@ bool NoiseStratumTransport::connect(const char *host, const char *ip, uint16_t p
// Perform Noise_NX handshake
ESP_LOGI(TAG, "Starting Noise handshake");
- int ret = sv2_noise_handshake(m_noise_ctx, m_t,
+ int ret = sv2_noise_handshake(ctx, m_t,
m_has_authority_pubkey ? m_authority_pubkey : nullptr);
if (ret != 0) {
ESP_LOGE(TAG, "Noise handshake failed");
- sv2_noise_destroy(m_noise_ctx);
- m_noise_ctx = nullptr;
+ sv2_noise_destroy(ctx);
StratumTransport::close();
return false;
}
+ {
+ PThreadGuard g(m_lock);
+ m_noise_ctx = ctx;
+ }
+
ESP_LOGI(TAG, "Encrypted channel established (ChaCha20-Poly1305)");
return true;
}
int NoiseStratumTransport::send(const void *data, size_t len)
{
+ PThreadGuard g(m_lock);
+
if (!m_noise_ctx || !m_t) {
return -1;
}
@@ -98,6 +95,11 @@ int NoiseStratumTransport::recv(void *buf, size_t len)
void NoiseStratumTransport::close()
{
+ // unblock a submit that may be stuck inside sv2_noise_send() so it
+ // releases m_lock before we destroy the noise ctx under it
+ shutdownSocket_();
+
+ PThreadGuard g(m_lock);
if (m_noise_ctx) {
sv2_noise_destroy(m_noise_ctx);
m_noise_ctx = nullptr;
diff --git a/main/system.cpp b/main/system.cpp
index 39be1cb12..6ac75b190 100644
--- a/main/system.cpp
+++ b/main/system.cpp
@@ -290,7 +290,7 @@ void System::task() {
uint32_t foundBlocks = STRATUM_MANAGER->getFoundBlocks();
// trigger the overlay only once when block is found
- if (foundBlocks != lastFoundBlocks && foundBlocks) {
+ if (foundBlocks > lastFoundBlocks) {
m_display->showFoundBlockOverlay();
}
lastFoundBlocks = foundBlocks;
diff --git a/main/tasks/ping_task.cpp b/main/tasks/ping_task.cpp
index 8626eed18..0efd3171d 100644
--- a/main/tasks/ping_task.cpp
+++ b/main/tasks/ping_task.cpp
@@ -1,3 +1,5 @@
+#include
+
#include "ping_task.h"
#include "esp_log.h"
#include "global_state.h"
@@ -102,10 +104,15 @@ PingResult PingTask::perform_ping(const char *ip_str, const char *hostname_str)
return result;
}
- PingStats stats{};
- stats.hostname = hostname_str;
- stats.min_rtt = 1e6;
- stats.tag = m_tag;
+ // esp_ping_stop()/esp_ping_delete_session() only clear flags; the ping
+ // thread re-checks them at the top of its loop and can still run one
+ // callback up to a full receive timeout later. The callback context
+ // therefore lives in the object, not on this stack frame, and the
+ // hostname is copied because the config strings may be replaced by then.
+ m_stats = {};
+ snprintf(m_stats.hostname, sizeof(m_stats.hostname), "%s", hostname_str ? hostname_str : "?");
+ m_stats.min_rtt = 1e6;
+ m_stats.tag = m_tag;
// Configure ping session
esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG();
@@ -116,7 +123,7 @@ PingResult PingTask::perform_ping(const char *ip_str, const char *hostname_str)
// Set callback to accumulate successful replies
esp_ping_callbacks_t cbs = {};
- cbs.cb_args = &stats;
+ cbs.cb_args = &m_stats;
cbs.on_ping_success = on_ping_task_success;
// Configure and start ping session
@@ -155,6 +162,11 @@ PingResult PingTask::perform_ping(const char *ip_str, const char *hostname_str)
// Stop session
esp_ping_stop(ping);
+ // the ping thread only sees the stop flag at the top of its loop; let a
+ // possible in-flight receive (up to PING_TIMEOUT_MS) and its callback
+ // finish before reading the stats and deleting the session
+ vTaskDelay(pdMS_TO_TICKS(PING_TIMEOUT_MS + 200));
+
// Final verification: check if any replies were missing
esp_ping_get_profile(ping, ESP_PING_PROF_REPLY, &replies, sizeof(replies));
esp_ping_get_profile(ping, ESP_PING_PROF_REQUEST, &sent, sizeof(sent));
@@ -163,13 +175,13 @@ PingResult PingTask::perform_ping(const char *ip_str, const char *hostname_str)
esp_ping_delete_session(ping);
// Store results if valid replies exist
- if (stats.replies > 0) {
- double round_avg = (double) stats.total_time_ms / stats.replies;
+ if (m_stats.replies > 0) {
+ double round_avg = (double) m_stats.total_time_ms / m_stats.replies;
result.success = true;
- result.replies = stats.replies;
+ result.replies = m_stats.replies;
result.avg_rtt_ms = round_avg;
- result.min_rtt_ms = stats.min_rtt;
- result.max_rtt_ms = stats.max_rtt;
+ result.min_rtt_ms = m_stats.min_rtt;
+ result.max_rtt_ms = m_stats.max_rtt;
m_last_ping_rtt_ms = result.avg_rtt_ms;
}
diff --git a/main/tasks/ping_task.h b/main/tasks/ping_task.h
index 3b251354d..338eb14fb 100644
--- a/main/tasks/ping_task.h
+++ b/main/tasks/ping_task.h
@@ -25,7 +25,7 @@ struct PingStats
uint32_t total_time_ms;
double min_rtt;
double max_rtt;
- const char *hostname;
+ char hostname[64];
bool header_shown;
const char* tag;
};
@@ -41,6 +41,11 @@ class PingTask {
const char* m_tag = nullptr;
StratumManager *m_manager = nullptr;
+ // callback context for esp_ping; must not live on perform_ping's stack
+ // because the ping thread can still fire callbacks after
+ // esp_ping_stop()/esp_ping_delete_session() returned
+ PingStats m_stats = {};
+
void record_ping_result(uint16_t sent, uint16_t received);
int init_ping_history();
double get_recent_packet_loss();