Files
Nic Boet 7df116b4f1 Fix case-sensitive filesystem build failures (Linux) for ESP32 LilyGo variants
The repo has apparently only ever been built on case-insensitive
filesystems (macOS/Windows): every #include in the codebase uses
intended PascalCase/CamelCase header names (e.g. "SettingsScreen.h",
"WiFiMQTT.h"), but 28 of the actual files on disk were saved with
inconsistent casing (e.g. "Settingsscreen.h", "wifimqtt.h"). On a
case-sensitive filesystem (Linux) this is a hard compile failure, not
a cosmetic mismatch -- confirmed by running `pio run -e meck_audio_ble`
on Gentoo Linux, which failed immediately on "target.h: No such file
or directory" and a cascade of similar errors as each fix exposed the
next one.

Root causes, two flavors of the same underlying bug:

1. Header filename casing (29 files renamed via `git mv` to preserve
   history): examples/companion_radio/ui-new/*, examples/simple_repeater/*,
   and two variant-local headers (PCF85063Clock.h, TCA8418Keyboard.h x2).
   Verified safe before renaming: every file has exactly one consistent
   intended casing across all the places that #include it (checked via
   a repo-wide scan comparing every #include against on-disk filenames,
   zero conflicts found), so each rename is a pure no-op for behavior.

2. PlatformIO config paths using the wrong case for variant directories
   that are actually lowercase on disk (variants/lilygo_tdeck_pro,
   variants/lilygo_t5s3_epaper_pro):
   - `-I variants/LilyGo_TDeck_Pro` / `-I variants/LilyGo_T5S3_EPaper_Pro`
     in build_flags (3 occurrences, including lilygo_tdeck_max's
     reference to TDeck Pro's shared headers) -- broke header resolution
     for target.h and friends.
   - `+<../variants/LilyGo_TDeck_Pro>` / `+<../variants/LilyGo_T5S3_EPaper_Pro>`
     in build_src_filter (2 occurrences) -- silently excluded the board-init
     .cpp files (TDeckBoard.cpp etc.) from compilation entirely, which
     didn't fail until the *link* stage ("undefined reference to
     radio_init()", `TDeckBoard::begin()`, etc.) since PlatformIO's glob
     just matched nothing rather than erroring.

Verified fix: `pio run -e meck_audio_ble` now compiles, links, and
produces a firmware image cleanly (RAM 53.1%, Flash 49.6%).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 14:44:46 -05:00

195 lines
5.7 KiB
C++

#pragma once
// =============================================================================
// WiFiMQTT — WiFi + MQTT for audio variant remote repeater
//
// Same interface as CellularMQTT but uses ESP32 native WiFi + PubSubClient.
// No modem, no AT commands, no FreeRTOS task — runs in the main loop.
//
// Supports multiple WiFi networks in wifi.cfg (SSID/password pairs).
// Tries each in order on connect/reconnect.
//
// Guard: MECK_WIFI_REMOTE (set in platformio env build_flags)
// =============================================================================
#ifdef MECK_WIFI_REMOTE
#ifndef WIFI_MQTT_H
#define WIFI_MQTT_H
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#if defined(HAS_SDCARD) || defined(SDCARD_CS)
#include <SD.h>
#endif
#include <SPIFFS.h>
// ---------------------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------------------
#define MQTT_TOPIC_MAX 80
#define MQTT_PAYLOAD_MAX 512
#define MQTT_CLIENT_ID_MAX 32
#define CMD_QUEUE_SIZE 8
#define RSP_QUEUE_SIZE 8
#define MAX_WIFI_NETWORKS 4
#define TELEMETRY_INTERVAL 60000 // 60 seconds
#define WIFI_RECONNECT_MS 10000 // 10 seconds between WiFi reconnect attempts
#define MQTT_RECONNECT_MS 5000 // 5 seconds between MQTT reconnect attempts
// ---------------------------------------------------------------------------
// State machine
// ---------------------------------------------------------------------------
enum class WiFiMQTTState : uint8_t {
OFF,
WIFI_CONNECTING,
WIFI_CONNECTED,
MQTT_CONNECTING,
CONNECTED,
OTA_IN_PROGRESS,
ERROR
};
// ---------------------------------------------------------------------------
// Queue message types (same as CellularMQTT for compatibility)
// ---------------------------------------------------------------------------
struct MQTTCommand {
char cmd[MQTT_PAYLOAD_MAX];
};
struct MQTTResponse {
char topic[MQTT_TOPIC_MAX];
char payload[MQTT_PAYLOAD_MAX];
};
// ---------------------------------------------------------------------------
// Config (loaded from SD)
// ---------------------------------------------------------------------------
struct WiFiNetwork {
char ssid[40];
char password[64];
};
struct WiFiMQTTConfig {
WiFiNetwork networks[MAX_WIFI_NETWORKS];
int networkCount;
char broker[80];
uint16_t port; // 8883 for MQTT TLS
char username[40];
char password[40];
char deviceId[MQTT_CLIENT_ID_MAX];
};
// ---------------------------------------------------------------------------
// Telemetry snapshot
// ---------------------------------------------------------------------------
struct TelemetryData {
uint32_t uptime_secs;
uint16_t battery_mv;
uint8_t battery_pct;
int16_t temperature;
int rssi;
uint8_t neighbor_count;
float freq;
float bw;
uint8_t sf;
uint8_t cr;
uint8_t tx_power;
char node_name[32];
bool mqtt_connected;
// Routing settings (v1.8+)
uint8_t loop_detect; // 0=off, 1=minimal, 2=moderate, 3=strict
uint8_t path_hash_mode; // 0=1-byte, 1=2-byte, 2=3-byte
uint8_t flood_max; // max flood hop count (0-64)
};
// ---------------------------------------------------------------------------
// WiFiMQTT class
// ---------------------------------------------------------------------------
class WiFiMQTT {
public:
void begin();
void loop(); // Call from main loop — handles WiFi, MQTT, publish/subscribe
// --- Queue API (called from main loop) ---
bool recvCommand(MQTTCommand& out);
bool sendResponse(const char* topic, const char* payload);
// --- Telemetry ---
void updateTelemetry(const TelemetryData& data);
// --- OTA ---
void requestOTA(const char* url);
bool isOTAInProgress() const { return _state == WiFiMQTTState::OTA_IN_PROGRESS; }
// --- State queries ---
WiFiMQTTState getState() const { return _state; }
bool isConnected() const { return _state == WiFiMQTTState::CONNECTED; }
int getRSSI() const { return _rssi; }
int getSignalBars() const;
const char* getSSID() const { return _config.networks[_activeNetwork].ssid; }
const char* getIPAddress() const { return _ipAddr; }
const char* getBroker() const { return _config.broker; }
const char* getRspTopic() const { return _topicRsp; }
const char* stateString() const;
static bool loadConfig(WiFiMQTTConfig& cfg);
private:
WiFiMQTTState _state = WiFiMQTTState::OFF;
int _rssi = 0;
int _activeNetwork = 0;
char _ipAddr[20] = {0};
WiFiMQTTConfig _config = {};
TelemetryData _telemetry = {};
// Topic strings
char _topicCmd[MQTT_TOPIC_MAX] = {0};
char _topicRsp[MQTT_TOPIC_MAX] = {0};
char _topicTelem[MQTT_TOPIC_MAX] = {0};
char _topicOta[MQTT_TOPIC_MAX] = {0};
// Command/response ring buffers (no FreeRTOS queues needed — single-threaded)
MQTTCommand _cmdBuf[CMD_QUEUE_SIZE];
int _cmdHead = 0, _cmdTail = 0;
MQTTResponse _rspBuf[RSP_QUEUE_SIZE];
int _rspHead = 0, _rspTail = 0;
// MQTT client stack
WiFiClientSecure _wifiClient;
PubSubClient _mqttClient;
// Timers
unsigned long _lastWifiAttempt = 0;
unsigned long _lastMqttAttempt = 0;
unsigned long _lastTelem = 0;
unsigned long _lastRSSI = 0;
// OTA state
bool _otaPending = false;
char _otaUrl[256] = {0};
// --- Internal ---
bool connectWiFi();
bool connectMQTT();
void publishTelemetry();
void publishQueuedResponses();
void performOTA();
// PubSubClient callback (static → instance)
static void mqttCallback(char* topic, byte* payload, unsigned int length);
void onMessage(char* topic, byte* payload, unsigned int length);
};
extern WiFiMQTT wifiMQTT;
#endif // WIFI_MQTT_H
#endif // MECK_WIFI_REMOTE