mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-26 16:27:57 +00:00
# Conflicts: # MQTT_INTERNALS.md # build.sh # docs/cli_commands.md # examples/simple_repeater/MyMesh.cpp # examples/simple_repeater/MyMesh.h # examples/simple_repeater/UITask.cpp # examples/simple_room_server/MyMesh.cpp # examples/simple_room_server/MyMesh.h # examples/simple_room_server/UITask.cpp # examples/simple_sensor/SensorMesh.cpp # platformio.ini # scripts/generate_webconfig_html.py # src/MeshCore.h # src/helpers/CommonCLI.cpp # src/helpers/CommonCLI.h # src/helpers/CommonCLI_Observer.cpp # src/helpers/ESP32Board.h # src/helpers/MQTTMessageBuilder.cpp # src/helpers/MQTTPresets.h # src/helpers/NRF52Board.cpp # src/helpers/NRF52Board.h # src/helpers/bridges/MQTTBridge.cpp # src/helpers/bridges/MQTTBridge.h # src/helpers/esp32/WebConfigServer.cpp # src/helpers/esp32/WebConfigServer.h # src/helpers/radiolib/RadioLibWrappers.cpp # src/helpers/radiolib/RadioLibWrappers.h # variants/lilygo_tbeam_SX1262/platformio.ini # variants/lilygo_tbeam_SX1276/platformio.ini # variants/lilygo_tlora_v2_1/platformio.ini # webui/index.html
117 lines
4.2 KiB
C++
117 lines
4.2 KiB
C++
#pragma once
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
// Pure queue/backpressure policy shared by the FreeRTOS and circular-buffer
|
|
// MQTT packet queues. Keeping the timing and retry decisions here makes the
|
|
// production behavior deterministic under host tests without mocking either
|
|
// queue implementation or the MQTT client.
|
|
namespace MQTTPacketQueuePolicy {
|
|
|
|
static const uint32_t kDisconnectedStaleMs = 300000UL;
|
|
static const size_t kBacklogThreshold = 5;
|
|
static const uint8_t kGentleDrainCount = 1;
|
|
static const uint8_t kBurstDrainCount = 5;
|
|
static const uint32_t kGentleDrainBudgetMs = 30UL;
|
|
static const uint32_t kBurstDrainBudgetMs = 100UL;
|
|
static const uint8_t kMaxQos0RetryAttempts = 3;
|
|
static const uint32_t kRetryDelayBaseMs = 300UL;
|
|
static const uint32_t kRetryDelayJitterMs = 200UL;
|
|
|
|
// Unsigned subtraction is the standard millis() idiom and remains correct
|
|
// across one 32-bit counter rollover.
|
|
static inline uint32_t elapsedMs(uint32_t now, uint32_t then) {
|
|
return now - then;
|
|
}
|
|
|
|
enum class EnqueueAction : uint8_t {
|
|
Enqueue,
|
|
EvictOldestThenEnqueue,
|
|
Reject
|
|
};
|
|
|
|
static inline EnqueueAction enqueueAction(size_t queue_count, size_t capacity) {
|
|
if (capacity == 0) return EnqueueAction::Reject;
|
|
return queue_count >= capacity
|
|
? EnqueueAction::EvictOldestThenEnqueue
|
|
: EnqueueAction::Enqueue;
|
|
}
|
|
|
|
// disconnected_since == 0 means tracking has not started. The bridge records
|
|
// the first disconnected observation and asks this helper on later cycles.
|
|
static inline bool shouldFlushDisconnected(uint32_t now,
|
|
uint32_t disconnected_since,
|
|
uint32_t stale_ms = kDisconnectedStaleMs) {
|
|
return disconnected_since != 0 && elapsedMs(now, disconnected_since) >= stale_ms;
|
|
}
|
|
|
|
struct DrainBudget {
|
|
uint8_t max_packets;
|
|
uint32_t max_time_ms;
|
|
};
|
|
|
|
static inline DrainBudget drainBudget(size_t queue_count) {
|
|
if (queue_count > kBacklogThreshold) {
|
|
return {kBurstDrainCount, kBurstDrainBudgetMs};
|
|
}
|
|
return {kGentleDrainCount, kGentleDrainBudgetMs};
|
|
}
|
|
|
|
static inline bool drainTimeAvailable(uint32_t now, uint32_t started_at,
|
|
uint32_t budget_ms) {
|
|
// Preserve the bridge's inclusive boundary: work may begin at exactly the
|
|
// configured limit, but not one millisecond later.
|
|
return elapsedMs(now, started_at) <= budget_ms;
|
|
}
|
|
|
|
// retry_attempts distinguishes an unscheduled packet from a scheduled retry
|
|
// whose deadline wrapped to exactly zero. Deadlines are always less than
|
|
// 500 ms away, so the half-range comparison is unambiguous.
|
|
static inline bool retryReady(uint32_t now, uint32_t next_retry_ms,
|
|
uint8_t retry_attempts) {
|
|
if (retry_attempts == 0) return true;
|
|
return elapsedMs(now, next_retry_ms) < 0x80000000UL;
|
|
}
|
|
|
|
enum class RetryAction : uint8_t {
|
|
Complete,
|
|
Schedule,
|
|
Drop
|
|
};
|
|
|
|
struct RetryDecision {
|
|
RetryAction action;
|
|
uint8_t retry_attempts;
|
|
uint32_t delay_ms;
|
|
uint32_t next_retry_ms;
|
|
};
|
|
|
|
// A queued packet counts as delivered if EITHER its structured-packet publish
|
|
// or its raw-frame publish reached at least one slot. Partial success (one
|
|
// succeeds while the other fails or was not attempted) is still success -- the
|
|
// packet completes and is not retried. This is the (packet, raw) outcome pairing
|
|
// fed to retryDecision(); naming it keeps the "partial publish = done" contract
|
|
// explicit and host-tested rather than inline in the bridge's queue drain.
|
|
static inline bool queuedPacketPublished(bool packet_published,
|
|
bool raw_published) {
|
|
return packet_published || raw_published;
|
|
}
|
|
|
|
static inline RetryDecision retryDecision(bool any_published,
|
|
uint8_t retry_attempts,
|
|
uint32_t now) {
|
|
if (any_published) {
|
|
return {RetryAction::Complete, retry_attempts, 0, 0};
|
|
}
|
|
if (retry_attempts >= kMaxQos0RetryAttempts) {
|
|
return {RetryAction::Drop, retry_attempts, 0, 0};
|
|
}
|
|
|
|
const uint32_t delay = kRetryDelayBaseMs + (now % kRetryDelayJitterMs);
|
|
return {RetryAction::Schedule, static_cast<uint8_t>(retry_attempts + 1),
|
|
delay, now + delay};
|
|
}
|
|
|
|
} // namespace MQTTPacketQueuePolicy
|