Files
MeshCore-mqtt-observer/src/helpers/AlertReporter.h
T
agessaman 16dc49fa10 Enhance fault alert system with region-based scoping and banned channels
Updated the fault alert functionality to include an optional region name for scoping alert floods, allowing operators to override the default scope. Introduced a list of banned channels (e.g., Public PSK, `#test`, `#bot`) to prevent spamming community channels with alerts. The implementation ensures that alerts are only sent to private PSKs or non-banned hashtags. Relevant changes were made across multiple files, including updates to the CLI for setting and retrieving the new `alert.region` preference.
2026-05-11 13:45:01 -07:00

110 lines
3.9 KiB
C++

#pragma once
#include <Arduino.h>
#include <Mesh.h>
#include "CommonCLI.h"
#ifdef WITH_MQTT_BRIDGE
#include "bridges/MQTTBridge.h"
#endif
/**
* Returns the label of a banned alert channel if \a secret16 matches one of
* the channels in the BANNED_ALERT_CHANNELS table (e.g. "PUBLIC", "#test",
* "#bot"), or nullptr otherwise. Centralized here so both AlertReporter and
* the CommonCLI `set alert.psk` / `set alert.hashtag` handlers can share one
* source of truth — adding a new banned channel is a one-line table edit.
*/
const char* alertReporterBannedChannelMatch(const uint8_t* secret16);
/**
* Convenience: base64-decodes \a psk_b64 and forwards to
* alertReporterBannedChannelMatch. Returns nullptr if not banned (or if
* the input doesn't decode to a 16-byte key — non-16-byte keys cannot
* match any banned entry anyway).
*/
const char* alertReporterBannedChannelMatchB64(const char* psk_b64);
/**
* \brief Send-only group-channel "fault alert" reporter for repeater/observer
* builds.
*
* Polls WiFi and per-MQTT-slot outage timers from MQTTBridge. When any timer
* exceeds its configured threshold, floods a single PAYLOAD_TYPE_GRP_TXT
* message on the configured alert channel ("WiFi down 47m — MyObserver"),
* then arms a "recovered" message for the next state transition.
*
* The alert channel must be explicitly configured to either a private base64
* PSK (`set alert.psk`) or a hashtag name (`set alert.hashtag`); the
* well-known PUBLIC group key (and a small list of other auto-responder
* channels — see BANNED_ALERT_CHANNELS in AlertReporter.cpp) are rejected on
* purpose so fault alerts never spam community channels.
*
* Edge-triggered + rate-limited via NodePrefs::alert_min_interval_min so a
* flapping link cannot spam the channel.
*
* Designed to compile and run on any repeater build:
* - The channel-send path uses only mesh::Mesh primitives that already
* exist in the Dispatcher hierarchy (createGroupDatagram + sendFlood).
* - WiFi/MQTT polling is #ifdef WITH_MQTT_BRIDGE-gated; without it, the
* reporter still supports manual `alert test` sends.
*/
class AlertReporter {
public:
AlertReporter();
/**
* Wire up the reporter. Must be called from MyMesh::begin() after prefs
* are loaded. \a callbacks is optional — when non-null the reporter uses
* it to resolve a TransportKey scope for outgoing alert floods (so the
* packet rides the repeater's default scope or an `alert.region` override).
*/
void begin(NodePrefs* prefs, mesh::Mesh* mesh, CommonCLICallbacks* callbacks = nullptr);
#ifdef WITH_MQTT_BRIDGE
/** Bridge can be (re)created lazily; pass nullptr to detach. */
void setBridge(MQTTBridge* bridge);
#endif
/**
* Re-derive the cached GroupChannel from \a alert_psk_b64. Call from the
* CLI hot-reload hook after `set alert.psk` / `set alert.hashtag` / `set alert on|off`.
*/
void onConfigChanged();
/**
* Cooperative tick. Fast: returns immediately if disabled, throttled
* internally to ~5 s checks. Safe to call every loop().
*/
void onLoop(unsigned long now_ms);
/**
* Send an arbitrary text immediately (used by `alert test` CLI). Returns
* false when disabled, PSK invalid, or the underlying flood-send fails.
* Bypasses the rate limiter and edge logic.
*/
bool sendText(const char* text);
private:
bool resolveChannel(mesh::GroupChannel& out) const;
bool sendChannel(const char* text);
void formatAge(unsigned long age_ms, char* out, size_t out_size) const;
enum FaultState { OK, FIRING };
struct Fault {
FaultState state;
unsigned long fired_at_ms; // millis() when we last sent a "down" alert
unsigned long last_outage_started_ms; // remembered so the recovered msg can quote duration
};
NodePrefs* _prefs;
mesh::Mesh* _mesh;
CommonCLICallbacks* _callbacks;
#ifdef WITH_MQTT_BRIDGE
MQTTBridge* _bridge;
Fault _wifi;
Fault _mqtt[RUNTIME_MQTT_SLOTS];
#endif
unsigned long _next_check_ms;
};