You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

112 lines
4.3 KiB

#pragma once
#include <Arduino.h>
#include <Mesh.h>
#include <helpers/ContactInfo.h>
/* ----------------------------------------------------------------------------
* BotCommands
*
* Built-in "debug bot" for the BLE Companion firmware. When another node on
* the mesh sends this node a direct text message beginning with the command
* prefix ('!' by default), the bot generates a reply and sends it back over
* the mesh - in addition to the companion's normal behaviour of forwarding
* the incoming message to the connected phone app.
*
* All of the logic lives in examples/companion_radio_bot/ (hooked in via the
* BotMesh subclass) so normal companion builds are completely unaffected.
* BotCommands only uses MyMesh's public API (sendMessage, getNodePrefs,
* getRecentlyHeard, advert...), so it stays decoupled from the internals.
*
* Commands:
* !ping - the requester's signal into us (SNR/RSSI/hops)
* !rf - SNR + RSSI of the received request packet
* !path - route/hops the request arrived on (flood hashes)
* !status - node name, freq/SF/BW/CR, TX power, heap, contacts
* !stats - packet RX/TX counts + airtime (mesh health)
* !neighbors - recently-heard nodes (alias: !seen)
* !uptime - time since boot
* !time - node clock (UTC) if the RTC is set
* !ver - firmware version + build date
*
* Runtime configuration is NOT done over the mesh - it's set locally via the
* companion protocol's custom vars ("bot", "bot.channels"), e.g. with
* meshcore-cli, and persisted to flash. See BotConfig.h.
* ------------------------------------------------------------------------- */
#ifndef BOT_CMD_PREFIX
#define BOT_CMD_PREFIX '!'
#endif
// Forward the incoming command message to the connected phone app as well
// (1 = phone still sees the "!cmd" message, 0 = suppress it from the app).
#ifndef BOT_FORWARD_TO_APP
#define BOT_FORWARD_TO_APP 1
#endif
// --- Rate limiting (anti-abuse: a flood of requests must not make us hammer
// the airwaves). All times in milliseconds / counts per window. ---
// Minimum gap between ANY two bot replies, regardless of sender.
#ifndef BOT_MIN_REPLY_INTERVAL_MS
#define BOT_MIN_REPLY_INTERVAL_MS 1200
#endif
// Per-sender cooldown: ignore repeat commands from the same node within this.
#ifndef BOT_SENDER_COOLDOWN_MS
#define BOT_SENDER_COOLDOWN_MS 5000
#endif
// Hard cap on replies emitted within any rolling 60s window.
#ifndef BOT_MAX_REPLIES_PER_MIN
#define BOT_MAX_REPLIES_PER_MIN 20
#endif
#ifndef BOT_SENDER_TABLE_SIZE
#define BOT_SENDER_TABLE_SIZE 8
#endif
class MyMesh;
class BotCommands {
public:
BotCommands() { reset(); }
// Records boot time; call once from BotMesh::begin().
void begin();
// Handle a freshly-received direct text message. Returns true if the text was
// a bot command (recognised or not, including ones dropped by rate limiting),
// so the caller knows it was "consumed" by the bot.
bool handle(MyMesh& mesh, const ContactInfo& from, mesh::Packet* pkt, const char* text);
// Handle a text message posted to a group channel. Replies on the same
// channel if the channel is on the configured allow-list (the bot.channels
// custom var) and the text is a bot command. Returns true if it was a
// command on an allowed channel.
bool handleChannel(MyMesh& mesh, const mesh::GroupChannel& channel, mesh::Packet* pkt,
uint32_t timestamp, const char* text);
private:
void reset();
bool rateLimitOk(const uint8_t* key, unsigned long now);
static bool channelAllowed(const char* name);
// Builds the reply text for a parsed command into `out` (capacity out_sz).
// `requester` is the sender's display name ("" if unknown).
// Returns the length written, or 0 if nothing should be sent.
int buildReply(MyMesh& mesh, mesh::Packet* pkt, const char* requester,
const char* cmd, const char* args, char* out, size_t out_sz);
unsigned long _boot_millis;
struct SenderSlot {
uint8_t prefix[4]; // first 4 bytes of sender public key (0 = empty)
unsigned long last_ms;
};
SenderSlot _senders[BOT_SENDER_TABLE_SIZE];
unsigned long _last_reply_ms; // for global min-interval
unsigned long _window_start_ms; // start of the current 60s window
uint16_t _window_count; // replies emitted in current window
};