refactor(ui-core): move ping state into PingEngine

Single in-flight ping + last result move from UITask to
ui-core/PingEngine.h; MyMesh's ping callback routes to the engine
instead of casting the Listener back to UITask. UITask keeps thin
wrappers (and the "not supported" alert), NearbyScreen is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-24 23:05:39 +02:00
co-authored by Claude Opus 5.5
parent dd826a447e
commit 311a3484e2
5 changed files with 92 additions and 74 deletions
@@ -0,0 +1,76 @@
#pragma once
// Ping engine: one on-device ping in flight at a time (MyMesh::sendPing), its
// result (RTT + SNR both ways) kept after the in-flight slot is released so the
// view can keep showing it. The reply arrives through MyMesh's ping callback,
// which is a plain function pointer -- routed here via a single static instance.
//
// The ping timeout is still decided by the view (NearbyScreen's
// PING_TIMEOUT_MS), which calls clear() when it gives up.
class PingEngine {
public:
enum StartResult : uint8_t { STARTED = 0, BUSY, UNSUPPORTED, SEND_FAILED };
void begin(NodePrefs* prefs) { _prefs = prefs; s_instance = this; }
StartResult start(const uint8_t* pub_key) {
if (_active || !pub_key) return BUSY;
if (_prefs && _prefs->path_hash_mode > 1) return UNSUPPORTED; // no 3-byte path hash support
_active = true;
_tag = 0;
_snr_out_x4 = 0;
_snr_back_x4 = 0;
_rtt_ms = 0;
// Always install the callback before sending so the response cannot race it.
the_mesh.setPingCallback(onPingResult, NULL);
_tag = the_mesh.sendPing(pub_key, _prefs ? _prefs->path_hash_mode + 1 : 1);
if (_tag == 0) {
clear();
return SEND_FAILED;
}
return STARTED;
}
// Release the in-flight slot (result values are kept).
void clear() {
if (_tag != 0) the_mesh.clearPingResult(_tag);
_active = false;
_tag = 0;
}
bool isActive() const { return _active; }
void getResult(int16_t& snr_out_x4, int16_t& snr_back_x4, uint32_t& rtt_ms) const {
snr_out_x4 = _snr_out_x4;
snr_back_x4 = _snr_back_x4;
rtt_ms = _rtt_ms;
}
private:
void handleResult(uint32_t tag, int16_t snr_out_x4, int16_t snr_back_x4, uint32_t rtt_ms) {
if (_active && _tag == tag) {
_snr_out_x4 = snr_out_x4;
_snr_back_x4 = snr_back_x4;
_rtt_ms = rtt_ms;
// Release the in-flight slot immediately; the view keeps the result values.
clear();
}
}
static void onPingResult(uint32_t tag, int16_t snr_out_x4, int16_t snr_back_x4, uint32_t rtt_ms) {
if (s_instance) s_instance->handleResult(tag, snr_out_x4, snr_back_x4, rtt_ms);
}
static PingEngine* s_instance;
NodePrefs* _prefs = nullptr;
bool _active = false;
uint32_t _tag = 0;
int16_t _snr_out_x4 = 0;
int16_t _snr_back_x4 = 0;
uint32_t _rtt_ms = 0;
};
// Header-only Core, single TU (see UiCore.h) -- the one definition lives here.
PingEngine* PingEngine::s_instance = nullptr;
@@ -11,11 +11,13 @@
#include "DmUnreadTable.h"
#include "UiEvents.h"
#include "ClockEngine.h"
#include "PingEngine.h"
class UiCore {
public:
void begin(NodePrefs* prefs) {
clock.begin(prefs, &events);
ping.begin(prefs);
}
// Driven from the frontend's loop(), before it drains `events`.
@@ -27,6 +29,7 @@ public:
// ── Engines ───────────────────────────────────────────────────────────────
ClockEngine clock; // alarm / countdown / ring
PingEngine ping; // single in-flight ping + last result
// ── Models ────────────────────────────────────────────────────────────────
MessageHistory history; // channel + DM rings, delivery state, channel unread
+8 -57
View File
@@ -1646,14 +1646,6 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
}
}
// Initialize ping state
_ping_active = false;
_ping_tag = 0;
_ping_sent_ms = 0;
_ping_snr_out_x4 = 0;
_ping_snr_back_x4 = 0;
_ping_rtt_ms = 0;
_core = new UiCore(); // before any screen -- MessagesScreen binds to its history
_core->begin(node_prefs);
splash = new SplashScreen(this);
@@ -1775,57 +1767,16 @@ void UITask::gotoMapScreen() {
void UITask::gotoLocatorScreen() { setCurrScreen(locator_screen); }
void UITask::gotoAutoAdvertScreen() { setCurrScreen(auto_advert_screen); }
// Public method to handle ping result callback
void UITask::handlePingResult(uint32_t tag, int16_t snr_out_x4, int16_t snr_back_x4, uint32_t rtt_ms) {
if (_ping_active && _ping_tag == tag) {
_ping_snr_out_x4 = snr_out_x4;
_ping_snr_back_x4 = snr_back_x4;
_ping_rtt_ms = rtt_ms;
// Release the in-flight slot immediately; the UI keeps the result values.
clearPing();
}
}
// Static ping callback (for MyMesh)
static void onPingResult(uint32_t tag, int16_t snr_out_x4, int16_t snr_back_x4, uint32_t rtt_ms) {
MyMesh::Listener* ui = the_mesh.getListener();
if (ui) {
UITask* task = static_cast<UITask*>(ui);
task->handlePingResult(tag, snr_out_x4, snr_back_x4, rtt_ms);
}
}
void UITask::clearPing() {
if (_ping_tag != 0) {
the_mesh.clearPingResult(_ping_tag);
}
_ping_active = false;
_ping_tag = 0;
}
bool UITask::startPing(const uint8_t* pub_key) {
if (_ping_active || !pub_key) return false;
if (_node_prefs && _node_prefs->path_hash_mode > 1) {
showAlert("Ping not supported with 3-byte path hashes", 3000);
return false;
}
_ping_active = true;
_ping_tag = 0;
_ping_sent_ms = millis();
_ping_snr_out_x4 = 0;
_ping_snr_back_x4 = 0;
_ping_rtt_ms = 0;
// Always install the callback before sending so the response cannot race it.
the_mesh.setPingCallback(onPingResult, NULL);
_ping_tag = the_mesh.sendPing(pub_key, _node_prefs ? _node_prefs->path_hash_mode + 1 : 1);
if (_ping_tag == 0) {
clearPing();
return false;
}
return true;
PingEngine::StartResult r = _core->ping.start(pub_key);
if (r == PingEngine::UNSUPPORTED) showAlert("Ping not supported with 3-byte path hashes", 3000);
return r == PingEngine::STARTED;
}
bool UITask::isPingActive() const { return _core->ping.isActive(); }
void UITask::getPingResult(int16_t& snr_out_x4, int16_t& snr_back_x4, uint32_t& rtt_ms) const {
_core->ping.getResult(snr_out_x4, snr_back_x4, rtt_ms);
}
void UITask::clearPing() { _core->ping.clear(); }
void UITask::playMelody(const char* melody) {
#ifdef PIN_BUZZER
+3 -15
View File
@@ -168,13 +168,6 @@ class UITask : public AbstractUITask {
uint32_t _next_cog_sample_ms = 0;
void pushCogFix(int32_t lat, int32_t lon);
// Ping state
bool _ping_active = false;
uint32_t _ping_tag = 0;
unsigned long _ping_sent_ms = 0;
int16_t _ping_snr_out_x4 = 0;
int16_t _ping_snr_back_x4 = 0;
uint32_t _ping_rtt_ms = 0;
void userLedHandler();
@@ -447,16 +440,11 @@ public:
bool hasDisplay() const { return _display != NULL; }
DisplayDriver* getDisplay() const { return _display; }
// Ping helpers
// Ping helpers (engine in ui-core/PingEngine.h)
bool startPing(const uint8_t* pub_key);
bool isPingActive() const { return _ping_active; }
void getPingResult(int16_t& snr_out_x4, int16_t& snr_back_x4, uint32_t& rtt_ms) const {
snr_out_x4 = _ping_snr_out_x4;
snr_back_x4 = _ping_snr_back_x4;
rtt_ms = _ping_rtt_ms;
}
bool isPingActive() const;
void getPingResult(int16_t& snr_out_x4, int16_t& snr_back_x4, uint32_t& rtt_ms) const;
void clearPing();
void handlePingResult(uint32_t tag, int16_t snr_out_x4, int16_t snr_back_x4, uint32_t rtt_ms);
// Favourites dial helpers. Slot index 0..FAVOURITES_COUNT-1. A slot holds
// either a contact/room (pubkey prefix) or a channel (index), per