Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e17b1b071 |
No files matched your search
@@ -0,0 +1,7 @@
|
||||
# beta_33 (TEST) — the MQTT freeze fix + the About page no longer stalls itself. On top of beta_32.
|
||||
# One user-facing note per non-blank, non-# line; # lines are section comments.
|
||||
|
||||
# --- Fixed ---
|
||||
The freezes with MQTT enabled are gone. Connecting to the broker (DNS lookup, TCP connect, login) could block the device for 2-10 seconds and retried every 15 seconds while the broker was unreachable, all on the UI thread. Connection attempts now run fully in the background, and writes to a broker that stops responding are skipped instead of waited on (the same treatment beta_32 gave phone/web connections). Tip: if your bridge keeps reconnecting, check the broker address and credentials - a healthy connection does not retry.
|
||||
The About page caused a ~0.4 second hitch every second while it was open: computing the firmware image size checksums the whole ~3 MB app in flash and ran twice per refresh. It is now computed once per boot, so About (and its stall list) no longer disturbs the very measurements it shows.
|
||||
Favoriting a contact when the favorites list is full (16) now says "Favorites full (max 16)" instead of the incorrect "Removed from favorites".
|
||||
@@ -10,11 +10,30 @@ MqttBridge mqtt_bridge;
|
||||
#include <WiFi.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <lwip/sockets.h>
|
||||
#include "esp_random.h"
|
||||
#include "mbedtls/gcm.h"
|
||||
#include "mbedtls/md.h"
|
||||
#include "mbedtls/base64.h"
|
||||
|
||||
// Only hand bytes to lwIP when the socket can accept them right now (zero-timeout
|
||||
// select). Otherwise WiFiClient::write() blocks in 1 s select() retries against a
|
||||
// broker that stopped ACKing — on the loop thread that is a visible UI freeze per
|
||||
// publish. A refused write fails the publish; PubSubClient then flags the
|
||||
// connection down and the (async) reconnect path takes over.
|
||||
size_t MqttNbClient::write(const uint8_t* buf, size_t size) {
|
||||
int fd_ = fd();
|
||||
if (fd_ < 0) return 0;
|
||||
fd_set wset;
|
||||
FD_ZERO(&wset);
|
||||
FD_SET(fd_, &wset);
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 0;
|
||||
if (select(fd_ + 1, NULL, &wset, NULL, &tv) <= 0 || !FD_ISSET(fd_, &wset)) return 0;
|
||||
return WiFiClient::write(buf, size);
|
||||
}
|
||||
|
||||
// Read every field from the "mqtt" NVS namespace into the members. isKey() guards
|
||||
// avoid the [E] NOT_FOUND log spam that getString emits for absent keys on the
|
||||
// USB-CDC companion stream (see TouchPrefsStore prefsGetStr note).
|
||||
@@ -86,13 +105,27 @@ bool MqttBridge::reconnect() {
|
||||
return ok;
|
||||
}
|
||||
|
||||
// reconnect() blocks on DNS + TCP connect + CONNACK (2-10 s against an unreachable
|
||||
// broker) — never run it on the loop thread, it drives LVGL. One-shot task instead;
|
||||
// _connecting hands _mqtt/_wc to the task and back.
|
||||
void MqttBridge::reconnectTask(void* arg) {
|
||||
MqttBridge* self = (MqttBridge*)arg;
|
||||
self->reconnect();
|
||||
self->_connecting = false;
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
void MqttBridge::loop() {
|
||||
if (!_enabled) return;
|
||||
if (!_enabled || _connecting) return;
|
||||
if (!_mqtt.connected()) {
|
||||
uint32_t now = millis();
|
||||
if ((uint32_t)(now - _lastReconnectMs) >= RECONNECT_INTERVAL_MS) {
|
||||
_lastReconnectMs = now;
|
||||
reconnect();
|
||||
_connecting = true;
|
||||
if (xTaskCreatePinnedToCore(reconnectTask, "mqtt_conn", 6144, this,
|
||||
1, nullptr, 0) != pdPASS) {
|
||||
_connecting = false; // task OOM — try again next interval
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -128,7 +161,7 @@ bool MqttBridge::sealToB64(const char* plain, char* out, size_t outCap) {
|
||||
}
|
||||
|
||||
void MqttBridge::pub(const char* subtopic, const char* json) {
|
||||
if (!_enabled || !_mqtt.connected()) return;
|
||||
if (!_enabled || _connecting || !_mqtt.connected()) return;
|
||||
char topic[80];
|
||||
snprintf(topic, sizeof(topic), "wadamesh/%s/%s", _nodeHex, subtopic);
|
||||
if (_encOn) {
|
||||
@@ -158,7 +191,7 @@ void MqttBridge::escapeJson(const char* src, char* dst, size_t dstLen) {
|
||||
|
||||
void MqttBridge::publishDM(const char* senderName, const uint8_t* senderKey32,
|
||||
float snr, uint8_t hops, uint32_t ts, const char* text) {
|
||||
if (!_enabled || !_pubDm || !_mqtt.connected()) return; // DMs are opt-in
|
||||
if (!_enabled || !_pubDm || _connecting || !_mqtt.connected()) return; // DMs are opt-in
|
||||
char keyHex[13] = {};
|
||||
for (int i = 0; i < 6; ++i) snprintf(keyHex + i * 2, 3, "%02x", senderKey32[i]);
|
||||
|
||||
@@ -175,7 +208,7 @@ void MqttBridge::publishDM(const char* senderName, const uint8_t* senderKey32,
|
||||
|
||||
void MqttBridge::publishChannel(int channelIdx, const char* channelName,
|
||||
float snr, uint8_t hops, uint32_t ts, const char* text) {
|
||||
if (!_enabled || !_pubChannel || !_mqtt.connected()) return; // channel publish toggle
|
||||
if (!_enabled || !_pubChannel || _connecting || !_mqtt.connected()) return; // channel publish toggle
|
||||
char safeName[48], safeText[300];
|
||||
escapeJson(channelName, safeName, sizeof(safeName));
|
||||
escapeJson(text, safeText, sizeof(safeText));
|
||||
@@ -204,6 +237,10 @@ void MqttBridge::saveConfig(const char* host, uint16_t port,
|
||||
}
|
||||
|
||||
void MqttBridge::reloadConfig() {
|
||||
// A connect attempt may be in flight on the one-shot task; PubSubClient is not
|
||||
// thread-safe, so wait it out (bounded: DNS + TCP + CONNACK <= ~10 s, and it
|
||||
// only overlaps when Save lands inside an attempt window on a dead broker).
|
||||
while (_connecting) delay(10);
|
||||
if (_mqtt.connected()) _mqtt.disconnect();
|
||||
loadConfig();
|
||||
if (!_enabled) { Serial.println("[MQTT] disabled"); return; }
|
||||
|
||||
@@ -24,6 +24,17 @@ public:
|
||||
#include <PubSubClient.h>
|
||||
#include <WiFiClient.h>
|
||||
|
||||
// WiFiClient whose write() refuses to enter the framework's blocking-select path:
|
||||
// bytes are only handed to lwIP when the socket can take them right now, so a
|
||||
// wedged broker connection (half-open socket, peer stopped ACKing) fails the
|
||||
// publish instead of stalling the loop thread ~1 s per write attempt. Same probe
|
||||
// as the TCP/WS companion servers.
|
||||
class MqttNbClient : public WiFiClient {
|
||||
public:
|
||||
size_t write(const uint8_t* buf, size_t size) override;
|
||||
size_t write(uint8_t b) override { return write(&b, 1); }
|
||||
};
|
||||
|
||||
// Publishes received mesh messages to an MQTT broker over the existing WiFi link.
|
||||
//
|
||||
// PRIVACY MODEL (mirrors how Meshtastic's MQTT module guards content):
|
||||
@@ -73,7 +84,7 @@ public:
|
||||
void reloadConfig();
|
||||
|
||||
private:
|
||||
WiFiClient _wc;
|
||||
MqttNbClient _wc;
|
||||
PubSubClient _mqtt{_wc};
|
||||
char _nodeHex[13] = {}; // 6-byte key → 12 hex chars + '\0'
|
||||
bool _enabled = false;
|
||||
@@ -87,12 +98,16 @@ private:
|
||||
bool _encOn = false;
|
||||
uint16_t _port = 1883;
|
||||
uint32_t _lastReconnectMs = 0;
|
||||
// True while the one-shot connect task owns _mqtt/_wc. The loop thread must
|
||||
// not touch either until it clears (PubSubClient is not thread-safe).
|
||||
volatile bool _connecting = false;
|
||||
|
||||
static const uint32_t RECONNECT_INTERVAL_MS = 15000;
|
||||
|
||||
void loadConfig(); // shared by begin() / reloadConfig()
|
||||
void deriveKey(); // _key = SHA-256(_psk); sets _encOn
|
||||
bool reconnect();
|
||||
static void reconnectTask(void* arg); // one-shot task body wrapping reconnect()
|
||||
void pub(const char* subtopic, const char* json); // seals if _encOn
|
||||
bool sealToB64(const char* plain, char* out, size_t outCap);
|
||||
static void escapeJson(const char* src, char* dst, size_t dstLen);
|
||||
|
||||
+22
-2
@@ -8603,8 +8603,20 @@ static void sysInfoText(char* buf, size_t cap) {
|
||||
const uint32_t sketch_size = 0; // TODO(device): surface the real AppFS app size
|
||||
const uint32_t sketch_free = 0;
|
||||
#else
|
||||
const uint32_t sketch_size = ESP.getSketchSize();
|
||||
const uint32_t sketch_free = ESP.getFreeSketchSpace();
|
||||
// Each of these runs esp_image_verify — a SHA walk over the whole ~3 MB app
|
||||
// image (~190 ms per call). At the 1 Hz About refresh the pair was a constant
|
||||
// ~390 ms/s UI stall (the "ui:sbar" entries field testers photographed) and it
|
||||
// flooded the stall ring, hiding the entries that mattered. The running image
|
||||
// cannot change while running, so pay the walk once and cache.
|
||||
static uint32_t s_sketch_size_c = 0, s_sketch_free_c = 0;
|
||||
static bool s_sketch_known = false;
|
||||
if (!s_sketch_known) {
|
||||
s_sketch_size_c = ESP.getSketchSize();
|
||||
s_sketch_free_c = ESP.getFreeSketchSpace();
|
||||
s_sketch_known = true;
|
||||
}
|
||||
const uint32_t sketch_size = s_sketch_size_c;
|
||||
const uint32_t sketch_free = s_sketch_free_c;
|
||||
#endif
|
||||
p += snprintf(buf + p, cap - p,
|
||||
"Flash\n chip: %u MB\n sketch: %u KB used\n app slot free: %u KB\n\n",
|
||||
@@ -12790,6 +12802,14 @@ static void actionSheetFavoriteCb(lv_event_t* e) {
|
||||
#if defined(ESP32)
|
||||
bool was_fav = touchPrefsIsFavorite(c.id.pub_key);
|
||||
bool now_fav = touchPrefsSetFavorite(c.id.pub_key, !was_fav);
|
||||
if (!was_fav && !now_fav) {
|
||||
// Add refused: the favorites table is at capacity, nothing changed — saying
|
||||
// "Removed from favorites" here (the plain now_fav mapping) is a lie.
|
||||
char full_msg[48];
|
||||
snprintf(full_msg, sizeof full_msg, TR("Favorites full (max %d)"), TOUCH_FAVORITES_MAX);
|
||||
g_lv.task->showAlert(full_msg, 1500);
|
||||
return;
|
||||
}
|
||||
g_lv.task->showAlert(now_fav ? TR("Added to favorites") : TR("Removed from favorites"), 1100);
|
||||
// Force a list rebuild so the star (or its removal) shows immediately — a plain
|
||||
// refresh hits the no-change cache (the contact count is unchanged by a fav toggle).
|
||||
|
||||
Reference in new issue
Block a user