mirror of
https://github.com/pelgraine/Meck.git
synced 2026-05-16 06:15:54 +02:00
298 lines
11 KiB
C++
298 lines
11 KiB
C++
#pragma once
|
|
|
|
// =============================================================================
|
|
// ModemManager - A7682E 4G Modem Driver for T-Deck Pro (V1.1 4G variant)
|
|
//
|
|
// Runs AT commands on a dedicated FreeRTOS task (Core 0, priority 1) to never
|
|
// block the mesh radio loop. Communicates with main loop via lock-free queues.
|
|
//
|
|
// Supports: SMS send/receive, voice call dial/answer/hangup/DTMF,
|
|
// notification tone playback via AT+CCMXPLAY
|
|
//
|
|
// Guard: HAS_4G_MODEM (defined only for the 4G build environment)
|
|
// =============================================================================
|
|
|
|
#ifdef HAS_4G_MODEM
|
|
|
|
#ifndef MODEM_MANAGER_H
|
|
#define MODEM_MANAGER_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"
|
|
#include "ModemBundledSounds.h"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Modem pins (from variant.h, always defined for reference)
|
|
// MODEM_POWER_EN 41 Board 6609 enable
|
|
// MODEM_PWRKEY 40 Power key toggle
|
|
// MODEM_RST 9 Reset (shared with I2S BCLK on audio board)
|
|
// MODEM_RI 7 Ring indicator (shared with I2S DOUT on audio)
|
|
// MODEM_DTR 8 Data terminal ready (shared with I2S LRC on audio)
|
|
// MODEM_RX 10 UART RX (shared with PIN_PERF_POWERON)
|
|
// MODEM_TX 11 UART TX
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// SMS field limits
|
|
#define SMS_PHONE_LEN 20
|
|
#define SMS_BODY_LEN 161 // 160 chars + null
|
|
|
|
// Task configuration
|
|
#define MODEM_TASK_PRIORITY 1 // Below mesh (default loop = priority 1 on core 1)
|
|
#define MODEM_TASK_STACK_SIZE 6144 // Increased for call handling
|
|
#define MODEM_TASK_CORE 0 // Run on core 0 (mesh runs on core 1)
|
|
|
|
// Queue sizes
|
|
#define MODEM_SEND_QUEUE_SIZE 4
|
|
#define MODEM_RECV_QUEUE_SIZE 8
|
|
#define MODEM_CALL_CMD_QUEUE_SIZE 4
|
|
#define MODEM_CALL_EVT_QUEUE_SIZE 4
|
|
|
|
// Notification tone auto-stop timeout (ms) -- stop playback after this
|
|
// even if no +AUDIOSTATE URC received, to avoid stuck audio
|
|
#define NOTIF_TONE_TIMEOUT_MS 4000
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Modem state machine
|
|
// ---------------------------------------------------------------------------
|
|
enum class ModemState {
|
|
OFF,
|
|
POWERING_ON,
|
|
INITIALIZING,
|
|
REGISTERING,
|
|
READY,
|
|
ERROR,
|
|
SENDING_SMS,
|
|
// Voice call states
|
|
DIALING, // ATD sent, waiting for connect/carrier
|
|
RINGING_IN, // Incoming call detected (RING URC)
|
|
IN_CALL // Voice call active
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SMS structures (unchanged)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Outgoing SMS (queued from main loop to modem task)
|
|
struct SMSOutgoing {
|
|
char phone[SMS_PHONE_LEN];
|
|
char body[SMS_BODY_LEN];
|
|
};
|
|
|
|
// Incoming SMS (queued from modem task to main loop)
|
|
struct SMSIncoming {
|
|
char phone[SMS_PHONE_LEN];
|
|
char body[SMS_BODY_LEN];
|
|
uint32_t timestamp; // epoch seconds (from modem RTC or millis-based)
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Voice call structures
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Commands from main loop -> modem task
|
|
enum class CallCmd : uint8_t {
|
|
DIAL, // Initiate outgoing call
|
|
ANSWER, // Answer incoming call
|
|
HANGUP, // End active call or reject incoming
|
|
DTMF, // Send DTMF tone during call
|
|
SET_VOLUME // Set speaker volume
|
|
};
|
|
|
|
struct CallCommand {
|
|
CallCmd cmd;
|
|
char phone[SMS_PHONE_LEN]; // Used by DIAL
|
|
char dtmf; // Used by DTMF (single digit: 0-9, *, #)
|
|
uint8_t volume; // Used by SET_VOLUME (0-5)
|
|
};
|
|
|
|
// Events from modem task -> main loop
|
|
enum class CallEventType : uint8_t {
|
|
INCOMING, // Incoming call ringing (+CLIP parsed)
|
|
CONNECTED, // Call answered / outgoing connected
|
|
ENDED, // Call ended (local hangup, remote hangup, or no carrier)
|
|
MISSED, // Incoming call ended before answer
|
|
BUSY, // Outgoing call got busy signal
|
|
NO_ANSWER, // Outgoing call not answered
|
|
DIAL_FAILED // ATD command failed
|
|
};
|
|
|
|
struct CallEvent {
|
|
CallEventType type;
|
|
char phone[SMS_PHONE_LEN]; // Caller/callee number (from +CLIP or dial)
|
|
uint32_t duration; // Call duration in seconds (for ENDED)
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ModemManager class
|
|
// ---------------------------------------------------------------------------
|
|
|
|
class ModemManager {
|
|
public:
|
|
void begin();
|
|
void shutdown();
|
|
|
|
// --- SMS API (unchanged) ---
|
|
bool sendSMS(const char* phone, const char* body);
|
|
bool recvSMS(SMSIncoming& out);
|
|
|
|
// --- Voice Call API ---
|
|
bool dialCall(const char* phone); // Queue outgoing call
|
|
bool answerCall(); // Answer incoming call
|
|
bool hangupCall(); // End active / reject incoming
|
|
bool sendDTMF(char digit); // Send DTMF during call
|
|
bool setCallVolume(uint8_t level); // Set volume 0-5
|
|
bool pollCallEvent(CallEvent& out); // Poll from main loop
|
|
|
|
// Ringtone control -- called from main loop
|
|
void setRingtoneEnabled(bool en) { _ringtoneEnabled = en; }
|
|
bool isRingtoneEnabled() const { return _ringtoneEnabled; }
|
|
|
|
// --- Notification tone API ---
|
|
// Request playback of a bundled notification tone by index (0-based).
|
|
// Called from main loop (Core 1); playback happens on modem task (Core 0).
|
|
// Pass -1 or out-of-range to be ignored.
|
|
void requestNotifTone(int8_t toneIdx);
|
|
|
|
// Check if bundled tones have been transferred to modem filesystem
|
|
bool areTonesReady() const { return _tonesTransferred; }
|
|
|
|
// Look up a modem tone index by base filename (e.g. "Bell-01").
|
|
// Returns index into modemBundledTones[] or -1 if not found.
|
|
// Matches with or without .wav extension.
|
|
static int8_t findToneByName(const char* name);
|
|
|
|
// --- State queries (lock-free reads) ---
|
|
ModemState getState() const { return _state; }
|
|
int getSignalBars() const; // 0-5
|
|
int getCSQ() const { return _csq; }
|
|
bool isReady() const { return _state == ModemState::READY; }
|
|
bool isInCall() const { return _state == ModemState::IN_CALL; }
|
|
bool isRinging() const { return _state == ModemState::RINGING_IN; }
|
|
bool isDialing() const { return _state == ModemState::DIALING; }
|
|
bool isCallActive() const {
|
|
return _state == ModemState::IN_CALL ||
|
|
_state == ModemState::DIALING ||
|
|
_state == ModemState::RINGING_IN;
|
|
}
|
|
const char* getOperator() const { return _operator; }
|
|
const char* getCallPhone() const { return _callPhone; }
|
|
uint32_t getCallStartTime() const { return _callStartTime; }
|
|
|
|
// --- Device info (populated during init) ---
|
|
const char* getIMEI() const { return _imei; }
|
|
const char* getIMSI() const { return _imsi; }
|
|
const char* getAPN() const { return _apn; }
|
|
const char* getAPNSource() const { return _apnSource; } // "auto", "network", "user", "none"
|
|
|
|
// --- APN configuration ---
|
|
// Set APN manually (overrides auto-detection). Persists to SD.
|
|
void setAPN(const char* apn);
|
|
// Load user-configured APN from SD card. Returns true if found.
|
|
static bool loadAPNConfig(char* apnOut, int maxLen);
|
|
// Save user-configured APN to SD card.
|
|
static void saveAPNConfig(const char* apn);
|
|
|
|
// Pause/resume polling -- used by web reader to avoid Core 0 contention
|
|
// during WiFi TLS handshakes. While paused, the task skips AT commands
|
|
// (SMS poll, CSQ poll) but still drains URCs and handles call commands
|
|
// so incoming calls aren't missed.
|
|
void pausePolling() { _paused = true; }
|
|
void resumePolling() { _paused = false; }
|
|
bool isPaused() const { return _paused; }
|
|
|
|
static const char* stateToString(ModemState s);
|
|
|
|
// Persistent enable/disable config (SD file /sms/modem.cfg)
|
|
static bool loadEnabledConfig();
|
|
static void saveEnabledConfig(bool enabled);
|
|
|
|
private:
|
|
volatile ModemState _state = ModemState::OFF;
|
|
volatile int _csq = 99; // 99 = unknown
|
|
volatile bool _paused = false; // Suppresses AT polling when true
|
|
char _operator[24] = {0};
|
|
|
|
// Device identity (populated during Phase 2 init)
|
|
char _imei[20] = {0}; // IMEI from AT+GSN
|
|
char _imsi[20] = {0}; // IMSI from AT+CIMI (for APN lookup)
|
|
char _apn[64] = {0}; // Active APN
|
|
char _apnSource[8] = {0}; // "auto", "network", "user", "none"
|
|
|
|
// Call state (written by modem task, read by main loop)
|
|
char _callPhone[SMS_PHONE_LEN] = {0}; // Current call number
|
|
volatile uint32_t _callStartTime = 0; // millis() when call connected
|
|
|
|
// Ringtone state
|
|
volatile bool _ringtoneEnabled = false;
|
|
bool _ringing = false; // Shadow of RINGING_IN for tone logic
|
|
unsigned long _nextRingTone = 0; // Next tone burst timestamp (modem task)
|
|
bool _toneActive = false; // Is a tone currently sounding
|
|
|
|
// Notification tone state
|
|
volatile int8_t _pendingToneIdx = -1; // Set by main loop, consumed by modem task
|
|
volatile bool _tonesTransferred = false; // True after all tones written to modem C:/
|
|
bool _notifTonePlaying = false; // Modem is currently playing a notif tone
|
|
unsigned long _notifToneStartTime = 0; // millis() when playback started (for timeout)
|
|
|
|
TaskHandle_t _taskHandle = nullptr;
|
|
|
|
// SMS queues
|
|
QueueHandle_t _sendQueue = nullptr;
|
|
QueueHandle_t _recvQueue = nullptr;
|
|
|
|
// Call queues
|
|
QueueHandle_t _callCmdQueue = nullptr; // main loop -> modem task
|
|
QueueHandle_t _callEvtQueue = nullptr; // modem task -> main loop
|
|
|
|
SemaphoreHandle_t _uartMutex = nullptr;
|
|
|
|
// URC line buffer (accumulated between AT commands)
|
|
static const int URC_BUF_SIZE = 256;
|
|
char _urcBuf[URC_BUF_SIZE];
|
|
int _urcPos = 0;
|
|
|
|
// UART AT command helpers (called only from modem task)
|
|
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);
|
|
void pollCSQ();
|
|
void pollIncomingSMS();
|
|
bool doSendSMS(const char* phone, const char* body);
|
|
|
|
// URC (unsolicited result code) handling
|
|
void drainURCs(); // Read available UART data, process complete lines
|
|
void processURCLine(const char* line); // Handle a single URC line
|
|
|
|
// APN resolution (called from modem task during init)
|
|
void resolveAPN(); // Auto-detect APN from network/IMSI/user config
|
|
|
|
// Call control (called from modem task)
|
|
bool doDialCall(const char* phone);
|
|
bool doAnswerCall();
|
|
bool doHangup();
|
|
bool doSendDTMF(char digit);
|
|
bool doSetVolume(uint8_t level);
|
|
void queueCallEvent(CallEventType type, const char* phone = nullptr, uint32_t duration = 0);
|
|
void handleRingtone(); // Play tone bursts while incoming call rings
|
|
|
|
// Notification tone transfer and playback (called from modem task)
|
|
bool transferTonesToModem(); // Transfer embedded WAVs to modem C:/ filesystem
|
|
bool playModemTone(const char* filename); // AT+CCMXPLAY
|
|
bool stopModemTone(); // AT+CCMXSTOP
|
|
void handleNotifTone(); // Poll _pendingToneIdx, play/stop as needed
|
|
|
|
// FreeRTOS task
|
|
static void taskEntry(void* param);
|
|
void taskLoop();
|
|
};
|
|
|
|
// Global singleton
|
|
extern ModemManager modemManager;
|
|
|
|
#endif // MODEM_MANAGER_H
|
|
#endif // HAS_4G_MODEM
|