mirror of
https://github.com/pelgraine/Meck.git
synced 2026-07-29 04:42:41 +02:00
7df116b4f1
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>
231 lines
6.5 KiB
C++
231 lines
6.5 KiB
C++
#pragma once
|
|
|
|
// =============================================================================
|
|
// CellularMQTT — A7682E Modem + MQTT via native AT commands
|
|
// =============================================================================
|
|
|
|
#ifdef HAS_4G_MODEM
|
|
|
|
#ifndef CELLULAR_MQTT_H
|
|
#define CELLULAR_MQTT_H
|
|
|
|
#include <Arduino.h>
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/task.h>
|
|
#include <freertos/queue.h>
|
|
#include <freertos/semphr.h>
|
|
#include "variant.h"
|
|
#include "ApnDatabase.h"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Configuration
|
|
// ---------------------------------------------------------------------------
|
|
#define MQTT_TOPIC_MAX 80
|
|
#define MQTT_PAYLOAD_MAX 512
|
|
#define MQTT_CLIENT_ID_MAX 32
|
|
|
|
#define CMD_QUEUE_SIZE 4
|
|
#define RSP_QUEUE_SIZE 4
|
|
|
|
#define TELEMETRY_INTERVAL 60000
|
|
|
|
#define CELL_TASK_PRIORITY 1
|
|
#define CELL_TASK_STACK_SIZE 8192
|
|
#define CELL_TASK_CORE 0
|
|
|
|
#define MQTT_RECONNECT_MIN 5000
|
|
#define MQTT_RECONNECT_MAX 300000
|
|
|
|
#define MQTT_PUB_FAIL_MAX 5
|
|
|
|
#define OTA_CHUNK_SIZE 1024
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// State machine
|
|
// ---------------------------------------------------------------------------
|
|
enum class CellState : uint8_t {
|
|
OFF,
|
|
POWERING_ON,
|
|
INITIALIZING,
|
|
REGISTERING,
|
|
DATA_ACTIVATING,
|
|
MQTT_STARTING,
|
|
MQTT_CONNECTING,
|
|
CONNECTED,
|
|
RECONNECTING,
|
|
OTA_IN_PROGRESS,
|
|
ERROR
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Queue message types
|
|
// ---------------------------------------------------------------------------
|
|
|
|
struct MQTTCommand {
|
|
char cmd[MQTT_PAYLOAD_MAX];
|
|
};
|
|
|
|
struct MQTTResponse {
|
|
char topic[MQTT_TOPIC_MAX];
|
|
char payload[MQTT_PAYLOAD_MAX];
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// MQTT config (loaded from SD: /remote/mqtt.cfg)
|
|
// ---------------------------------------------------------------------------
|
|
struct MQTTConfig {
|
|
char broker[80];
|
|
uint16_t port;
|
|
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 csq;
|
|
uint8_t neighbor_count;
|
|
float freq;
|
|
float bw;
|
|
uint8_t sf;
|
|
uint8_t cr;
|
|
uint8_t tx_power;
|
|
char node_name[32];
|
|
char apn[40];
|
|
char oper[24];
|
|
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)
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CellularMQTT class
|
|
// ---------------------------------------------------------------------------
|
|
class CellularMQTT {
|
|
public:
|
|
void begin();
|
|
void stop();
|
|
|
|
// --- 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 == CellState::OTA_IN_PROGRESS; }
|
|
|
|
// --- State queries ---
|
|
CellState getState() const { return _state; }
|
|
bool isConnected() const { return _state == CellState::CONNECTED; }
|
|
int getCSQ() const { return _csq; }
|
|
int getSignalBars() const;
|
|
const char* getOperator() const { return _operator; }
|
|
const char* getIPAddress() const { return _ipAddr; }
|
|
const char* getBroker() const { return _config.broker; }
|
|
const char* getAPN() const { return _apn; }
|
|
const char* getRspTopic() const { return _topicRsp; }
|
|
const char* stateString() const;
|
|
uint32_t getLastCmdTime() const { return _lastCmdTime; }
|
|
|
|
static bool loadConfig(MQTTConfig& cfg);
|
|
|
|
private:
|
|
volatile CellState _state = CellState::OFF;
|
|
volatile int _csq = 99;
|
|
volatile uint32_t _lastCmdTime = 0;
|
|
|
|
char _operator[24] = {0};
|
|
char _ipAddr[20] = {0};
|
|
char _imei[20] = {0};
|
|
char _imsi[20] = {0};
|
|
char _apn[64] = {0};
|
|
|
|
MQTTConfig _config = {};
|
|
TelemetryData _telemetry = {};
|
|
SemaphoreHandle_t _telemetryMutex = nullptr;
|
|
|
|
char _topicCmd[MQTT_TOPIC_MAX] = {0};
|
|
char _topicRsp[MQTT_TOPIC_MAX] = {0};
|
|
char _topicTelem[MQTT_TOPIC_MAX] = {0};
|
|
char _topicOta[MQTT_TOPIC_MAX] = {0};
|
|
|
|
TaskHandle_t _taskHandle = nullptr;
|
|
QueueHandle_t _cmdQueue = nullptr;
|
|
QueueHandle_t _rspQueue = nullptr;
|
|
SemaphoreHandle_t _uartMutex = nullptr;
|
|
|
|
uint8_t _pubFailCount = 0;
|
|
|
|
static const int AT_BUF_SIZE = 512;
|
|
char _atBuf[AT_BUF_SIZE];
|
|
|
|
static const int URC_BUF_SIZE = 600;
|
|
char _urcBuf[URC_BUF_SIZE];
|
|
int _urcPos = 0;
|
|
|
|
enum MqttRxState { RX_IDLE, RX_WAIT_TOPIC, RX_WAIT_PAYLOAD };
|
|
MqttRxState _rxState = RX_IDLE;
|
|
int _rxTopicLen = 0;
|
|
int _rxPayloadLen = 0;
|
|
char _rxTopic[MQTT_TOPIC_MAX];
|
|
char _rxPayload[MQTT_PAYLOAD_MAX];
|
|
|
|
uint32_t _reconnectDelay = MQTT_RECONNECT_MIN;
|
|
|
|
// OTA state
|
|
volatile bool _otaPending = false;
|
|
char _otaUrl[256] = {0};
|
|
|
|
// --- Modem UART helpers ---
|
|
bool modemPowerOn();
|
|
bool sendAT(const char* cmd, const char* expect, uint32_t timeout_ms = 2000);
|
|
bool waitResponse(const char* expect, uint32_t timeout_ms, char* buf = nullptr, size_t bufLen = 0);
|
|
bool waitPrompt(uint32_t timeout_ms = 5000);
|
|
void drainURCs();
|
|
void processURCLine(const char* line);
|
|
|
|
// --- Data connection ---
|
|
void resolveAPN();
|
|
bool activateData();
|
|
|
|
// --- MQTT operations ---
|
|
bool mqttStart();
|
|
bool mqttConnect();
|
|
bool mqttSubscribe(const char* topic);
|
|
bool mqttPublish(const char* topic, const char* payload);
|
|
void mqttDisconnect();
|
|
|
|
// --- URC handlers ---
|
|
void handleMqttRxStart(const char* line);
|
|
void handleMqttRxTopic(const char* data, int len);
|
|
void handleMqttRxPayload(const char* data, int len);
|
|
void handleMqttRxEnd();
|
|
void handleMqttConnLost(const char* line);
|
|
|
|
// --- OTA operations (modem task only) ---
|
|
void performOTA();
|
|
int httpGet(const char* url);
|
|
bool httpReadChunk(int offset, int len, uint8_t* dest, int* bytesRead);
|
|
void httpTerm();
|
|
int readRawBytes(uint8_t* dest, int count, uint32_t timeout_ms);
|
|
|
|
// --- Task ---
|
|
static void taskEntry(void* param);
|
|
void taskLoop();
|
|
};
|
|
|
|
extern CellularMQTT cellularMQTT;
|
|
|
|
#endif // CELLULAR_MQTT_H
|
|
#endif // HAS_4G_MODEM
|