#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 #include #include #include #include #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