Guest User

ESP32 code brrrrrrrrr

a guest
Jan 29th, 2026
37
0
27 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 46.82 KB | Source Code | 0 0
  1. /*
  2.  * ESP32 MAVLink TCP Bridge for SIMCom A7670C
  3.  * Version 9.5.3 - Patched RX Priority Edition
  4.  *
  5.  * Based on v9.5 with critical RX priority fixes:
  6.  * - quickRxCheck() called frequently during TX
  7.  * - Smaller TX chunks (32 bytes) for more RX opportunities
  8.  * - RX checked before/after every TX chunk
  9.  * - Critical command detection and logging
  10.  * - Max 100ms blocking per write call
  11.  */
  12.  
  13. #define TINY_GSM_MODEM_SIM7600
  14. #define TINY_GSM_RX_BUFFER 2048
  15.  
  16. #include <TinyGsmClient.h>
  17. #include <esp_task_wdt.h>
  18.  
  19. // ============== WDT VERSION COMPATIBILITY ==============
  20. #if defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
  21.     #define WDT_API_V5
  22. #endif
  23.  
  24. #define WDT_TIMEOUT_SECONDS 15
  25.  
  26. // ============== CONFIGURATION ==============
  27. const char* targetHost = "45.77.132.119";
  28. const uint16_t targetPort = 5760;
  29. const char manualAPN[] = "";
  30.  
  31. // ============== PIN DEFINITIONS ==============
  32. #define LTE_TX_PIN      17
  33. #define LTE_RX_PIN      18
  34. #define LTE_PWRKEY_PIN  4
  35. #define FC_TX_PIN       11
  36. #define FC_RX_PIN       12
  37. #define FC_BAUD_RATE    57600
  38.  
  39. // ============== TIMING CONSTANTS (ms) ==============
  40. #define MAVLINK_BYTE_TIMEOUT      100
  41. #define STATUS_PRINT_INTERVAL     3000
  42. #define MODEM_BOOT_WAIT_MS        8000
  43. #define NETWORK_WAIT_TIMEOUT      90000
  44. #define MODEM_INIT_TIMEOUT        30000
  45. #define CFUN_TRANSITION_WAIT      5000
  46.  
  47. // === RX PRIORITY TUNING ===
  48. #define WRITE_CHUNK_SIZE          32      // Smaller = more RX checks
  49. #define WRITE_MAX_TIME_MS         100     // Max blocking per write call
  50. #define MAX_TX_BYTES_PER_LOOP     256
  51. #define MAX_RX_BYTES_PER_LOOP     512
  52. #define MAX_FC_BYTES_PER_LOOP     256
  53. #define RX_CHECK_INTERVAL_BYTES   100     // Check RX every N bytes from FC
  54.  
  55. // === HEALTH THRESHOLDS ===
  56. #define WRITE_DURATION_FAST_MS    30
  57. #define WRITE_DURATION_NORMAL_MS  100
  58. #define WRITE_DURATION_SLOW_MS    300
  59. #define WRITE_DURATION_STALL_MS   500
  60.  
  61. #define BUFFER_WARNING_THRESHOLD  2000
  62. #define BUFFER_CRITICAL_THRESHOLD 4000
  63.  
  64. #define RX_SILENCE_WARNING_MS     5000
  65. #define RX_SILENCE_CRITICAL_MS    15000
  66. #define ACTIVITY_TIMEOUT_MS       60000
  67.  
  68. #define WRITE_HISTORY_SIZE        10
  69. #define BUFFER_TREND_SAMPLES      10
  70. #define MAX_CONSECUTIVE_TX_FAILS  15
  71.  
  72. // === OTHER ===
  73. #define CONNECTION_TIMEOUT_MS     10000
  74. #define HEALTH_CHECK_INTERVAL     1000
  75. #define KEEPALIVE_INTERVAL_MS     1000
  76.  
  77. #define TX_BUFFER_HIGH_WATER      4000
  78. #define TX_BUFFER_LOW_WATER       1000
  79. #define MIN_FREE_HEAP_KB          20
  80. #define LOOP_TIME_LIMIT_MS        100
  81.  
  82. #define INITIAL_HEARTBEAT_COUNT   5
  83. #define INITIAL_HEARTBEAT_DELAY   200
  84.  
  85. // ============== RETRY LIMITS ==============
  86. #define MAX_TCP_RETRIES           5
  87. #define MAX_NETWORK_RETRIES       3
  88. #define MAX_HARD_RESET_RETRIES    2
  89. #define MAX_WAKEUP_RETRIES        3
  90.  
  91. // ============== BUFFER SIZES ==============
  92. #define SERIAL_RX_BUF_SIZE   4096
  93. #define RING_TX_BUF_SIZE     8192
  94. #define MAVLINK_MAX_FRAME    280
  95.  
  96. // ============== MAVLINK MESSAGE IDS ==============
  97. #define MAVLINK_MSG_ID_HEARTBEAT          0
  98. #define MAVLINK_MSG_ID_SET_MODE           11
  99. #define MAVLINK_MSG_ID_COMMAND_LONG       76
  100. #define MAVLINK_MSG_ID_COMMAND_INT        75
  101. #define MAVLINK_MSG_ID_MANUAL_CONTROL     69
  102. #define MAVLINK_MSG_ID_RC_CHANNELS_OVERRIDE 70
  103.  
  104. #define MAV_CMD_COMPONENT_ARM_DISARM      400
  105. #define MAV_CMD_NAV_RETURN_TO_LAUNCH      20
  106. #define MAV_CMD_DO_SET_MODE               176
  107.  
  108. // ============== HARDWARE SERIAL ==============
  109. HardwareSerial SerialLTE(1);
  110. HardwareSerial SerialFC(2);
  111.  
  112. // ============== TINYGSM ==============
  113. TinyGsm lteModem(SerialLTE);
  114. TinyGsmClient tcpClient(lteModem);
  115.  
  116. // ============== ENUMS ==============
  117. enum ModemStatus {
  118.     MODEM_OFF, MODEM_STANDBY, MODEM_BOOTING, MODEM_READY,
  119.     MODEM_SEARCHING, MODEM_REGISTERED, MODEM_CONNECTED, MODEM_DEGRADED
  120. };
  121.  
  122. const char* modemStatusNames[] = {
  123.     "OFF", "STANDBY", "BOOTING", "READY",
  124.     "SEARCHING", "REGISTERED", "CONNECTED", "DEGRADED"
  125. };
  126.  
  127. enum BridgeState {
  128.     ST_POWER_ON, ST_WAIT_MODEM, ST_CHECK_STATUS, ST_WAKEUP_MODEM,
  129.     ST_CLEAR_CONFIG, ST_CHECK_SIM, ST_GET_IMSI, ST_DETECT_APN,
  130.     ST_WAIT_NETWORK, ST_GPRS_CONNECT, ST_TCP_CONNECT, ST_SEND_HEARTBEATS,
  131.     ST_RUNNING, ST_TCP_RECONNECT, ST_NETWORK_RECOVER, ST_HARD_RESET,
  132.     ST_SYSTEM_REBOOT
  133. };
  134.  
  135. const char* stateNames[] = {
  136.     "POWER_ON", "WAIT_MODEM", "CHECK_STATUS", "WAKEUP_MODEM",
  137.     "CLEAR_CONFIG", "CHECK_SIM", "GET_IMSI", "DETECT_APN",
  138.     "WAIT_NETWORK", "GPRS_CONNECT", "TCP_CONNECT", "SEND_HEARTBEATS",
  139.     "RUNNING", "TCP_RECONNECT", "NET_RECOVER", "HARD_RESET", "SYS_REBOOT"
  140. };
  141.  
  142. enum ConnectionHealth {
  143.     CONN_EXCELLENT, CONN_GOOD, CONN_SLOW, CONN_DEGRADED, CONN_STALLED, CONN_DEAD
  144. };
  145.  
  146. const char* healthNames[] = {
  147.     "EXCELLENT", "GOOD", "SLOW", "DEGRADED", "STALLED", "DEAD"
  148. };
  149.  
  150. // ============== APN DATABASE ==============
  151. struct APNConfig {
  152.     const char* mccMnc;
  153.     const char* carrier;
  154.     const char* apnName;
  155. };
  156.  
  157. const APNConfig apnList[] = {
  158.     {"45412", "CMHK", "cmhk"},
  159.     {"45413", "CMHK", "cmhk"},
  160.     {"45400", "CSL", "hkcsl"},
  161.     {"45403", "3HK", "mobile.three.com.hk"},
  162.     {"46000", "CMCC", "cmnet"},
  163.     {"46001", "CU", "3gnet"},
  164.     {"", "Generic", "internet"},
  165. };
  166. const int apnListCount = sizeof(apnList) / sizeof(apnList[0]);
  167.  
  168. // ============== WATCHDOG ==============
  169. void initWatchdog() {
  170. #ifdef WDT_API_V5
  171.     esp_task_wdt_config_t wdt_config = {
  172.         .timeout_ms = WDT_TIMEOUT_SECONDS * 1000,
  173.         .idle_core_mask = 0,
  174.         .trigger_panic = true
  175.     };
  176.     esp_err_t err = esp_task_wdt_reconfigure(&wdt_config);
  177.     if (err != ESP_OK) {
  178.         esp_task_wdt_deinit();
  179.         esp_task_wdt_init(&wdt_config);
  180.     }
  181.     esp_task_wdt_add(NULL);
  182. #else
  183.     esp_task_wdt_init(WDT_TIMEOUT_SECONDS, true);
  184.     esp_task_wdt_add(NULL);
  185. #endif
  186.     Serial.printf("[WDT] Initialized (%d sec)\n", WDT_TIMEOUT_SECONDS);
  187. }
  188.  
  189. void feedWatchdog() {
  190.     esp_task_wdt_reset();
  191. }
  192.  
  193. // ============== RING BUFFER ==============
  194. template<size_t CAPACITY>
  195. class CircularBuffer {
  196. private:
  197.     uint8_t data[CAPACITY];
  198.     volatile size_t writeIdx = 0;
  199.     volatile size_t readIdx = 0;
  200.  
  201. public:
  202.     void reset() { writeIdx = readIdx = 0; }
  203.  
  204.     size_t count() const {
  205.         size_t w = writeIdx, r = readIdx;
  206.         return (w >= r) ? (w - r) : (CAPACITY - r + w);
  207.     }
  208.  
  209.     size_t space() const { return CAPACITY - count() - 1; }
  210.     bool isEmpty() const { return writeIdx == readIdx; }
  211.  
  212.     bool write(uint8_t byte) {
  213.         size_t next = (writeIdx + 1) % CAPACITY;
  214.         if (next == readIdx) return false;
  215.         data[writeIdx] = byte;
  216.         writeIdx = next;
  217.         return true;
  218.     }
  219.  
  220.     bool writeFrame(const uint8_t* buf, size_t len) {
  221.         if (space() < len) return false;
  222.         for (size_t i = 0; i < len; i++) write(buf[i]);
  223.         return true;
  224.     }
  225.  
  226.     int read() {
  227.         if (writeIdx == readIdx) return -1;
  228.         uint8_t byte = data[readIdx];
  229.         readIdx = (readIdx + 1) % CAPACITY;
  230.         return byte;
  231.     }
  232.  
  233.     size_t readBytes(uint8_t* buf, size_t maxLen) {
  234.         size_t n = 0;
  235.         while (n < maxLen && !isEmpty()) {
  236.             int b = read();
  237.             if (b >= 0) buf[n++] = (uint8_t)b;
  238.             else break;
  239.         }
  240.         return n;
  241.     }
  242.  
  243.     void discardAll() { readIdx = writeIdx; }
  244.  
  245.     size_t discardOldest(size_t targetRemain) {
  246.         size_t current = count();
  247.         if (current <= targetRemain) return 0;
  248.         size_t toDiscard = current - targetRemain;
  249.         readIdx = (readIdx + toDiscard) % CAPACITY;
  250.         return toDiscard;
  251.     }
  252. };
  253.  
  254. // ============== MAVLINK PARSER ==============
  255. class MavlinkFrameParser {
  256. private:
  257.     enum State { IDLE, GOT_STX, GOT_LEN, PAYLOAD } state = IDLE;
  258.     uint8_t frameBuf[MAVLINK_MAX_FRAME];
  259.     uint16_t frameIdx = 0;
  260.     uint16_t totalLen = 0;
  261.     uint8_t stxByte = 0;
  262.     unsigned long lastByteMs = 0;
  263.  
  264. public:
  265.     void reset() { state = IDLE; frameIdx = 0; totalLen = 0; }
  266.  
  267.     uint16_t feed(uint8_t byte) {
  268.         unsigned long now = millis();
  269.         if (state != IDLE && (now - lastByteMs) > MAVLINK_BYTE_TIMEOUT) reset();
  270.         lastByteMs = now;
  271.  
  272.         switch (state) {
  273.             case IDLE:
  274.                 if (byte == 0xFE || byte == 0xFD) {
  275.                     frameBuf[0] = byte;
  276.                     stxByte = byte;
  277.                     frameIdx = 1;
  278.                     state = GOT_STX;
  279.                 }
  280.                 break;
  281.             case GOT_STX:
  282.                 frameBuf[frameIdx++] = byte;
  283.                 if (stxByte == 0xFE) {
  284.                     totalLen = 8 + byte;
  285.                     state = PAYLOAD;
  286.                 } else if (frameIdx >= 2) {
  287.                     state = GOT_LEN;
  288.                 }
  289.                 break;
  290.             case GOT_LEN:
  291.                 frameBuf[frameIdx++] = byte;
  292.                 if (frameIdx >= 3) {
  293.                     uint8_t payloadLen = frameBuf[1];
  294.                     uint8_t incompat = frameBuf[2];
  295.                     totalLen = 12 + payloadLen + ((incompat & 0x01) ? 13 : 0);
  296.                     state = PAYLOAD;
  297.                 }
  298.                 break;
  299.             case PAYLOAD:
  300.                 if (frameIdx < MAVLINK_MAX_FRAME) frameBuf[frameIdx++] = byte;
  301.                 if (frameIdx >= totalLen) {
  302.                     uint16_t len = totalLen;
  303.                     reset();
  304.                     return len;
  305.                 }
  306.                 break;
  307.         }
  308.         if (frameIdx >= MAVLINK_MAX_FRAME) reset();
  309.         return 0;
  310.     }
  311.  
  312.     const uint8_t* getFrame() const { return frameBuf; }
  313.    
  314.     uint8_t getMessageId() const {
  315.         if (frameBuf[0] == 0xFE) return frameBuf[5];
  316.         if (frameBuf[0] == 0xFD) return frameBuf[7];
  317.         return 0xFF;
  318.     }
  319.    
  320.     bool isCriticalCommand() const {
  321.         uint8_t msgId = getMessageId();
  322.         return (msgId == MAVLINK_MSG_ID_SET_MODE ||
  323.                 msgId == MAVLINK_MSG_ID_COMMAND_LONG ||
  324.                 msgId == MAVLINK_MSG_ID_COMMAND_INT ||
  325.                 msgId == MAVLINK_MSG_ID_MANUAL_CONTROL ||
  326.                 msgId == MAVLINK_MSG_ID_RC_CHANNELS_OVERRIDE);
  327.     }
  328.    
  329.     uint16_t getCommandId() const {
  330.         if (getMessageId() == MAVLINK_MSG_ID_COMMAND_LONG && frameBuf[0] == 0xFE) {
  331.             return frameBuf[6 + 28] | (frameBuf[6 + 29] << 8);
  332.         }
  333.         return 0;
  334.     }
  335. };
  336.  
  337. // ============== CONNECTION MONITOR ==============
  338. class ConnectionMonitor {
  339. private:
  340.     unsigned long lastRxMs = 0;
  341.     unsigned long lastTxSuccessMs = 0;
  342.     unsigned long lastActivityMs = 0;
  343.    
  344.     unsigned long avgWriteDurationMs = 0;
  345.     unsigned long lastWriteDurationMs = 0;
  346.     unsigned long maxWriteDurationMs = 0;
  347.     unsigned long currentWriteStartMs = 0;
  348.    
  349.     size_t lastBufferSize = 0;
  350.     size_t peakBufferSize = 0;
  351.     int consecutiveGrows = 0;
  352.    
  353.     uint32_t txBytesThisPeriod = 0;
  354.     uint32_t rxBytesThisPeriod = 0;
  355.     uint32_t fcBytesThisPeriod = 0;
  356.     unsigned long periodStartMs = 0;
  357.     float txBytesPerSec = 0;
  358.     float rxBytesPerSec = 0;
  359.     float fcBytesPerSec = 0;
  360.    
  361.     uint32_t consecutiveTxFails = 0;
  362.     uint32_t totalTxAttempts = 0;
  363.     uint32_t totalTxFails = 0;
  364.    
  365.     ConnectionHealth currentHealth = CONN_GOOD;
  366.  
  367. public:
  368.     void reset() {
  369.         unsigned long now = millis();
  370.         lastRxMs = lastTxSuccessMs = lastActivityMs = periodStartMs = now;
  371.         avgWriteDurationMs = lastWriteDurationMs = maxWriteDurationMs = currentWriteStartMs = 0;
  372.         lastBufferSize = peakBufferSize = 0;
  373.         consecutiveGrows = 0;
  374.         txBytesThisPeriod = rxBytesThisPeriod = fcBytesThisPeriod = 0;
  375.         txBytesPerSec = rxBytesPerSec = fcBytesPerSec = 0;
  376.         consecutiveTxFails = totalTxAttempts = totalTxFails = 0;
  377.         currentHealth = CONN_GOOD;
  378.     }
  379.  
  380.     void onWriteStart() { currentWriteStartMs = millis(); }
  381.  
  382.     void onWriteComplete(size_t attempted, size_t sent) {
  383.         unsigned long now = millis();
  384.         unsigned long duration = currentWriteStartMs ? (now - currentWriteStartMs) : 0;
  385.         currentWriteStartMs = 0;
  386.         totalTxAttempts++;
  387.         lastWriteDurationMs = duration;
  388.         if (duration > maxWriteDurationMs) maxWriteDurationMs = duration;
  389.         avgWriteDurationMs = (avgWriteDurationMs * 3 + duration) / 4;
  390.        
  391.         if (sent > 0) {
  392.             lastTxSuccessMs = lastActivityMs = now;
  393.             consecutiveTxFails = 0;
  394.             txBytesThisPeriod += sent;
  395.         } else {
  396.             consecutiveTxFails++;
  397.             totalTxFails++;
  398.         }
  399.     }
  400.  
  401.     void onWriteSuccess(size_t bytes) {
  402.         lastTxSuccessMs = lastActivityMs = millis();
  403.         consecutiveTxFails = 0;
  404.         txBytesThisPeriod += bytes;
  405.     }
  406.  
  407.     void onWriteFail() {
  408.         consecutiveTxFails++;
  409.         totalTxFails++;
  410.     }
  411.  
  412.     void onRxData(size_t bytes) {
  413.         lastRxMs = lastActivityMs = millis();
  414.         rxBytesThisPeriod += bytes;
  415.     }
  416.  
  417.     void onFcData(size_t bytes) { fcBytesThisPeriod += bytes; }
  418.  
  419.     void updateStats(size_t currentBufferSize) {
  420.         if (currentBufferSize > peakBufferSize) peakBufferSize = currentBufferSize;
  421.         if (currentBufferSize > lastBufferSize + 100) consecutiveGrows++;
  422.         else if (currentBufferSize + 100 < lastBufferSize) consecutiveGrows = 0;
  423.         lastBufferSize = currentBufferSize;
  424.  
  425.         unsigned long now = millis();
  426.         unsigned long elapsed = now - periodStartMs;
  427.         if (elapsed >= 1000) {
  428.             txBytesPerSec = (float)txBytesThisPeriod * 1000.0f / elapsed;
  429.             rxBytesPerSec = (float)rxBytesThisPeriod * 1000.0f / elapsed;
  430.             fcBytesPerSec = (float)fcBytesThisPeriod * 1000.0f / elapsed;
  431.             txBytesThisPeriod = rxBytesThisPeriod = fcBytesThisPeriod = 0;
  432.             periodStartMs = now;
  433.         }
  434.     }
  435.  
  436.     ConnectionHealth evaluate(size_t currentBufferSize) {
  437.         updateStats(currentBufferSize);
  438.        
  439.         int score = 100;
  440.         if (avgWriteDurationMs > WRITE_DURATION_STALL_MS) score -= 50;
  441.         else if (avgWriteDurationMs > WRITE_DURATION_SLOW_MS) score -= 30;
  442.         else if (avgWriteDurationMs > WRITE_DURATION_NORMAL_MS) score -= 15;
  443.        
  444.         if (currentBufferSize > BUFFER_CRITICAL_THRESHOLD) score -= 40;
  445.         else if (currentBufferSize > BUFFER_WARNING_THRESHOLD) score -= 20;
  446.        
  447.         if (consecutiveGrows >= 5) score -= 25;
  448.         if (consecutiveTxFails >= 10) score -= 50;
  449.         else if (consecutiveTxFails >= 5) score -= 30;
  450.        
  451.         unsigned long rxAge = millis() - lastRxMs;
  452.         if (rxAge > RX_SILENCE_CRITICAL_MS) score -= 20;
  453.         else if (rxAge > RX_SILENCE_WARNING_MS) score -= 10;
  454.        
  455.         if (fcBytesPerSec > 100 && txBytesPerSec > 0) {
  456.             float ratio = txBytesPerSec / fcBytesPerSec;
  457.             if (ratio < 0.3f) score -= 30;
  458.             else if (ratio < 0.6f) score -= 15;
  459.         }
  460.        
  461.         if ((millis() - lastActivityMs) > ACTIVITY_TIMEOUT_MS) score = 0;
  462.        
  463.         if (score <= 0) currentHealth = CONN_DEAD;
  464.         else if (score <= 20) currentHealth = CONN_STALLED;
  465.         else if (score <= 40) currentHealth = CONN_DEGRADED;
  466.         else if (score <= 60) currentHealth = CONN_SLOW;
  467.         else if (score <= 85) currentHealth = CONN_GOOD;
  468.         else currentHealth = CONN_EXCELLENT;
  469.        
  470.         return currentHealth;
  471.     }
  472.  
  473.     void printDebugInfo() {
  474.         Serial.printf("    Write: avg=%lums last=%lums max=%lums\n",
  475.                      avgWriteDurationMs, lastWriteDurationMs, maxWriteDurationMs);
  476.         Serial.printf("    Buffer: %d peak=%d grows=%d\n",
  477.                      (int)lastBufferSize, (int)peakBufferSize, consecutiveGrows);
  478.         Serial.printf("    TxFails: %d/%d | RxAge: %lums\n",
  479.                      (int)consecutiveTxFails, (int)totalTxAttempts, millis() - lastRxMs);
  480.         Serial.printf("    Rate: FC=%.1f TX=%.1f RX=%.1f KB/s\n",
  481.                      fcBytesPerSec/1024, txBytesPerSec/1024, rxBytesPerSec/1024);
  482.     }
  483.  
  484.     unsigned long getTimeSinceRx() const { return millis() - lastRxMs; }
  485.     unsigned long getTimeSinceTx() const { return millis() - lastTxSuccessMs; }
  486.     float getTxRate() const { return txBytesPerSec; }
  487.     float getRxRate() const { return rxBytesPerSec; }
  488.     float getFcRate() const { return fcBytesPerSec; }
  489.     size_t getPeakBuffer() const { return peakBufferSize; }
  490.     ConnectionHealth getHealth() const { return currentHealth; }
  491.     const char* getHealthString() const { return healthNames[currentHealth]; }
  492. };
  493.  
  494. // ============== GLOBAL VARIABLES ==============
  495. BridgeState bridgeState = ST_POWER_ON;
  496. ModemStatus currentModemStatus = MODEM_OFF;
  497.  
  498. unsigned long stateEntryMs = 0;
  499. unsigned long lastStatusPrintMs = 0;
  500. unsigned long lastHealthCheckMs = 0;
  501. unsigned long lastKeepaliveMs = 0;
  502. unsigned long lastModemCheckMs = 0;
  503. unsigned long loopStartMs = 0;
  504.  
  505. int tcpRetryCount = 0;
  506. int networkRetryCount = 0;
  507. int hardResetCount = 0;
  508. int wakeupRetryCount = 0;
  509. int initialHeartbeatCount = 0;
  510.  
  511. char currentAPN[64] = "";
  512. char currentCarrier[32] = "";
  513. char simIMSI[20] = "";
  514.  
  515. MavlinkFrameParser fcParser;
  516. MavlinkFrameParser tcpParser;
  517. CircularBuffer<RING_TX_BUF_SIZE> txBuffer;
  518. ConnectionMonitor connMonitor;
  519.  
  520. bool networkReady = false;
  521. bool buffersInitialized = false;
  522. volatile bool forceReconnect = false;
  523. bool verboseDebug = true;
  524.  
  525. struct BridgeStats {
  526.     uint32_t mavFramesTx = 0;
  527.     uint32_t mavFramesRx = 0;
  528.     uint32_t criticalCmdsRx = 0;
  529.     uint32_t bytesSentTcp = 0;
  530.     uint32_t bytesRecvTcp = 0;
  531.     uint32_t bytesSentFc = 0;
  532.     uint32_t bytesRecvFc = 0;
  533.     uint32_t bufferOverflows = 0;
  534.     uint32_t discardedBytes = 0;
  535.     uint32_t writeStalls = 0;
  536.     uint32_t connectionDeaths = 0;
  537.     uint32_t tcpReconnects = 0;
  538.     uint32_t networkReconnects = 0;
  539.     uint32_t hardResets = 0;
  540.     uint32_t heartbeatsSent = 0;
  541.     uint32_t heapWarnings = 0;
  542.     uint32_t rxChecks = 0;
  543.     uint32_t rxInterrupts = 0;
  544.     uint32_t loopOverruns = 0;
  545. } stats;
  546.  
  547. unsigned long sessionStartMs = 0;
  548. uint32_t minFreeHeap = UINT32_MAX;
  549. uint32_t maxRxGapMs = 0;
  550. unsigned long lastRxCheckMs = 0;
  551.  
  552. // ============== HEARTBEAT ==============
  553. uint8_t heartbeatSeq = 0;
  554. static const uint8_t MAVLINK_CRC_EXTRA_HEARTBEAT = 50;
  555.  
  556. void crc_accumulate(uint8_t data, uint16_t* crc) {
  557.     uint8_t tmp = data ^ (uint8_t)(*crc & 0xFF);
  558.     tmp ^= (tmp << 4);
  559.     *crc = (*crc >> 8) ^ ((uint16_t)tmp << 8) ^ ((uint16_t)tmp << 3) ^ ((uint16_t)tmp >> 4);
  560. }
  561.  
  562. size_t buildHeartbeat(uint8_t* buf) {
  563.     buf[0] = 0xFE; buf[1] = 9; buf[2] = heartbeatSeq++;
  564.     buf[3] = 254; buf[4] = 190; buf[5] = 0;
  565.     buf[6] = 0; buf[7] = 0; buf[8] = 0; buf[9] = 0;
  566.     buf[10] = 6; buf[11] = 8; buf[12] = 0; buf[13] = 0; buf[14] = 3;
  567.     uint16_t crc = 0xFFFF;
  568.     for (int i = 1; i <= 14; i++) crc_accumulate(buf[i], &crc);
  569.     crc_accumulate(MAVLINK_CRC_EXTRA_HEARTBEAT, &crc);
  570.     buf[15] = crc & 0xFF; buf[16] = (crc >> 8) & 0xFF;
  571.     return 17;
  572. }
  573.  
  574. // ============== HELPERS ==============
  575. bool checkHeapHealth() {
  576.     uint32_t freeHeap = ESP.getFreeHeap();
  577.     if (freeHeap < minFreeHeap) minFreeHeap = freeHeap;
  578.     if (freeHeap < (MIN_FREE_HEAP_KB * 1024)) {
  579.         stats.heapWarnings++;
  580.         txBuffer.discardAll();
  581.         return false;
  582.     }
  583.     return true;
  584. }
  585.  
  586. void checkLoopTime(const char* phase) {
  587.     if (millis() - loopStartMs > LOOP_TIME_LIMIT_MS) {
  588.         stats.loopOverruns++;
  589.         feedWatchdog();
  590.     }
  591. }
  592.  
  593. String sendATCommand(const char* cmd, unsigned long timeout = 2000) {
  594.     while (SerialLTE.available()) SerialLTE.read();
  595.     SerialLTE.println(cmd);
  596.     String response = "";
  597.     unsigned long start = millis();
  598.     while (millis() - start < timeout) {
  599.         while (SerialLTE.available()) response += (char)SerialLTE.read();
  600.         if (response.indexOf("OK") >= 0 || response.indexOf("ERROR") >= 0) break;
  601.         delay(10);
  602.         feedWatchdog();
  603.     }
  604.     return response;
  605. }
  606.  
  607. bool sendATExpect(const char* cmd, const char* expect, unsigned long timeout = 2000) {
  608.     return sendATCommand(cmd, timeout).indexOf(expect) >= 0;
  609. }
  610.  
  611. // ============== QUICK RX CHECK (CRITICAL!) ==============
  612. // This function is called frequently during TX operations
  613. // It reads from TCP and forwards to FC immediately
  614. bool quickRxCheck() {
  615.     if (bridgeState != ST_RUNNING || !tcpClient.connected()) return false;
  616.    
  617.     // Track RX gap for diagnostics
  618.     unsigned long now = millis();
  619.     unsigned long gap = now - lastRxCheckMs;
  620.     if (gap > maxRxGapMs && lastRxCheckMs > 0) maxRxGapMs = gap;
  621.     lastRxCheckMs = now;
  622.    
  623.     stats.rxChecks++;
  624.    
  625.     int available = tcpClient.available();
  626.     if (available <= 0) return false;
  627.    
  628.     // Read small chunk immediately
  629.     uint8_t buf[64];
  630.     size_t toRead = min(available, 64);
  631.     size_t bytesRead = 0;
  632.    
  633.     for (size_t i = 0; i < toRead; i++) {
  634.         int b = tcpClient.read();
  635.         if (b >= 0) buf[bytesRead++] = (uint8_t)b;
  636.         else break;
  637.     }
  638.    
  639.     if (bytesRead > 0) {
  640.         // Forward to FC IMMEDIATELY - this is the critical path!
  641.         size_t sent = SerialFC.write(buf, bytesRead);
  642.        
  643.         connMonitor.onRxData(bytesRead);
  644.         stats.bytesRecvTcp += bytesRead;
  645.         stats.bytesSentFc += sent;
  646.        
  647.         // Parse for critical commands (logging)
  648.         for (size_t i = 0; i < bytesRead; i++) {
  649.             if (tcpParser.feed(buf[i]) > 0) {
  650.                 stats.mavFramesRx++;
  651.                
  652.                 if (tcpParser.isCriticalCommand()) {
  653.                     stats.criticalCmdsRx++;
  654.                     uint8_t msgId = tcpParser.getMessageId();
  655.                    
  656.                     Serial.printf("[RX] *** CRITICAL CMD @%lu: ", millis());
  657.                    
  658.                     if (msgId == MAVLINK_MSG_ID_SET_MODE) {
  659.                         Serial.println("MODE CHANGE ***");
  660.                     } else if (msgId == MAVLINK_MSG_ID_COMMAND_LONG) {
  661.                         uint16_t cmdId = tcpParser.getCommandId();
  662.                         if (cmdId == MAV_CMD_COMPONENT_ARM_DISARM) Serial.println("ARM/DISARM ***");
  663.                         else if (cmdId == MAV_CMD_NAV_RETURN_TO_LAUNCH) Serial.println("RTL ***");
  664.                         else if (cmdId == MAV_CMD_DO_SET_MODE) Serial.println("DO_SET_MODE ***");
  665.                         else Serial.printf("CMD_%d ***\n", cmdId);
  666.                     } else if (msgId == MAVLINK_MSG_ID_MANUAL_CONTROL) {
  667.                         Serial.println("MANUAL_CONTROL ***");
  668.                     } else if (msgId == MAVLINK_MSG_ID_RC_CHANNELS_OVERRIDE) {
  669.                         Serial.println("RC_OVERRIDE ***");
  670.                     } else {
  671.                         Serial.printf("MsgID=%d ***\n", msgId);
  672.                     }
  673.                 }
  674.             }
  675.         }
  676.         return true;
  677.     }
  678.     return false;
  679. }
  680.  
  681. // ============== SAFE TCP WRITE WITH RX CHECKS ==============
  682. size_t safeTcpWrite(const uint8_t* data, size_t len) {
  683.     if (!tcpClient.connected() || len == 0) return 0;
  684.    
  685.     unsigned long startMs = millis();
  686.     size_t totalSent = 0;
  687.     size_t offset = 0;
  688.    
  689.     while (offset < len) {
  690.         // *** CHECK RX BEFORE EACH CHUNK ***
  691.         if (quickRxCheck()) {
  692.             stats.rxInterrupts++;
  693.         }
  694.        
  695.         // Time limit check
  696.         if (millis() - startMs > WRITE_MAX_TIME_MS) {
  697.             break;
  698.         }
  699.        
  700.         // Send small chunk
  701.         size_t toSend = min(len - offset, (size_t)WRITE_CHUNK_SIZE);
  702.        
  703.         connMonitor.onWriteStart();
  704.         size_t sent = tcpClient.write(data + offset, toSend);
  705.         connMonitor.onWriteComplete(toSend, sent);
  706.        
  707.         if (sent > 0) {
  708.             totalSent += sent;
  709.             offset += sent;
  710.             stats.bytesSentTcp += sent;
  711.         } else {
  712.             // Write failed
  713.             break;
  714.         }
  715.        
  716.         // *** CHECK RX AFTER EACH CHUNK ***
  717.         if (quickRxCheck()) {
  718.             stats.rxInterrupts++;
  719.         }
  720.        
  721.         feedWatchdog();
  722.     }
  723.    
  724.     return totalSent;
  725. }
  726.  
  727. // ============== MODEM FUNCTIONS ==============
  728. ModemStatus getModemStatus() {
  729.     feedWatchdog();
  730.     bool responding = false;
  731.     for (int i = 0; i < 3; i++) {
  732.         if (sendATExpect("AT", "OK", 500)) { responding = true; break; }
  733.         delay(100);
  734.     }
  735.     if (!responding) return MODEM_OFF;
  736.  
  737.     String resp = sendATCommand("AT+CFUN?", 1000);
  738.     int cfun = 1;
  739.     int idx = resp.indexOf("+CFUN:");
  740.     if (idx >= 0) cfun = resp.substring(idx + 6).toInt();
  741.  
  742.     if (cfun == 0 || cfun == 4) return MODEM_STANDBY;
  743.  
  744.     if (cfun == 1) {
  745.         resp = sendATCommand("AT+CPIN?", 3000);
  746.         if (resp.indexOf("ERROR") >= 0) return MODEM_DEGRADED;
  747.  
  748.         resp = sendATCommand("AT+CREG?", 2000);
  749.         if (resp.indexOf(",1") >= 0 || resp.indexOf(",5") >= 0) {
  750.             resp = sendATCommand("AT+CGPADDR=1", 2000);
  751.             if (resp.indexOf(".") >= 0 && resp.indexOf("0.0.0.0") < 0) return MODEM_CONNECTED;
  752.             return MODEM_REGISTERED;
  753.         }
  754.         if (resp.indexOf(",2") >= 0) return MODEM_SEARCHING;
  755.         return MODEM_READY;
  756.     }
  757.     return MODEM_BOOTING;
  758. }
  759.  
  760. ModemStatus quickModemCheck() {
  761.     if (!sendATExpect("AT", "OK", 500)) return MODEM_OFF;
  762.     return MODEM_REGISTERED;
  763. }
  764.  
  765. bool wakeUpModem() {
  766.     Serial.println("[WAKEUP] Waking up modem...");
  767.     for (int i = 0; i < 5; i++) { sendATCommand("AT", 300); delay(100); feedWatchdog(); }
  768.     if (!sendATExpect("AT", "OK", 1000)) return false;
  769.  
  770.     String resp = sendATCommand("AT+CFUN?", 1000);
  771.     int cfun = 1;
  772.     int idx = resp.indexOf("+CFUN:");
  773.     if (idx >= 0) cfun = resp.substring(idx + 6).toInt();
  774.     if (cfun != 1) {
  775.         if (!sendATExpect("AT+CFUN=1", "OK", 10000)) return false;
  776.         delay(CFUN_TRANSITION_WAIT);
  777.         feedWatchdog();
  778.     }
  779.  
  780.     resp = sendATCommand("AT+CPIN?", 5000);
  781.     if (resp.indexOf("ERROR") >= 0) {
  782.         sendATCommand("AT+CFUN=0", 3000);
  783.         delay(2000);
  784.         feedWatchdog();
  785.         if (!sendATExpect("AT+CFUN=1", "OK", 10000)) return false;
  786.         delay(CFUN_TRANSITION_WAIT);
  787.     }
  788.     return (getModemStatus() >= MODEM_READY);
  789. }
  790.  
  791. bool isModemResponding() {
  792.     for (int i = 0; i < 3; i++) {
  793.         if (sendATExpect("AT", "OK", 500)) return true;
  794.         delay(100);
  795.     }
  796.     return false;
  797. }
  798.  
  799. void pulsePowerKey(unsigned long durationMs = 1500) {
  800.     digitalWrite(LTE_PWRKEY_PIN, HIGH);
  801.     delay(durationMs);
  802.     digitalWrite(LTE_PWRKEY_PIN, LOW);
  803.     feedWatchdog();
  804. }
  805.  
  806. bool powerOnModule() {
  807.     if (isModemResponding()) return true;
  808.     pulsePowerKey(1500);
  809.     unsigned long start = millis();
  810.     while (millis() - start < MODEM_BOOT_WAIT_MS) { delay(500); feedWatchdog(); }
  811.     return isModemResponding();
  812. }
  813.  
  814. bool powerOffModule() {
  815.     if (!isModemResponding()) return true;
  816.     sendATCommand("AT+CPOF", 3000);
  817.     delay(2000);
  818.     feedWatchdog();
  819.     if (!isModemResponding()) return true;
  820.     pulsePowerKey(1500);
  821.     delay(3000);
  822.     return !isModemResponding();
  823. }
  824.  
  825. void hardResetModule() {
  826.     stats.hardResets++;
  827.     tcpClient.stop();
  828.     int attempts = 0;
  829.     while (isModemResponding() && attempts < 3) { powerOffModule(); attempts++; feedWatchdog(); }
  830.     delay(2000);
  831.     pulsePowerKey(1500);
  832.     unsigned long start = millis();
  833.     while (millis() - start < MODEM_BOOT_WAIT_MS) { delay(500); feedWatchdog(); }
  834. }
  835.  
  836. bool waitModemReady(unsigned long timeout = MODEM_INIT_TIMEOUT) {
  837.     unsigned long start = millis();
  838.     while (millis() - start < timeout) {
  839.         feedWatchdog();
  840.         if (sendATExpect("AT", "OK", 1000)) { delay(500); return true; }
  841.         delay(500);
  842.     }
  843.     return false;
  844. }
  845.  
  846. void clearModemConfig() {
  847.     feedWatchdog();
  848.     sendATExpect("ATE0", "OK", 500);
  849.     sendATExpect("AT+CMEE=2", "OK", 500);
  850.     sendATCommand("AT+CIPCLOSE=0", 2000);
  851.     sendATCommand("AT+CIPSHUT", 5000);
  852.     feedWatchdog();
  853.     sendATCommand("AT+CGACT=0,1", 3000);
  854.     sendATCommand("AT+NETCLOSE", 3000);
  855.     delay(500);
  856. }
  857.  
  858. // ============== SIM & APN ==============
  859. bool checkSimPresent() {
  860.     currentModemStatus = getModemStatus();
  861.     if (currentModemStatus == MODEM_OFF || currentModemStatus == MODEM_STANDBY ||
  862.         currentModemStatus == MODEM_DEGRADED) return false;
  863.     String resp = sendATCommand("AT+CPIN?", 5000);
  864.     if (resp.indexOf("READY") >= 0) return true;
  865.     resp = sendATCommand("AT+CICCID", 3000);
  866.     return (resp.indexOf("89") >= 0);
  867. }
  868.  
  869. bool waitForSimReady(unsigned long timeout = 20000) {
  870.     unsigned long start = millis();
  871.     while (millis() - start < timeout) { feedWatchdog(); if (checkSimPresent()) return true; delay(2000); }
  872.     return false;
  873. }
  874.  
  875. bool readIMSI() {
  876.     String resp = sendATCommand("AT+CIMI", 2000);
  877.     for (unsigned int i = 0; i < resp.length(); i++) {
  878.         if (isdigit(resp[i])) {
  879.             int len = 0;
  880.             while (i + len < resp.length() && isdigit(resp[i + len]) && len < 15) {
  881.                 simIMSI[len] = resp[i + len]; len++;
  882.             }
  883.             simIMSI[len] = '\0';
  884.             return len >= 5;
  885.         }
  886.     }
  887.     return false;
  888. }
  889.  
  890. bool detectAPN() {
  891.     if (strlen(manualAPN) > 0) {
  892.         strncpy(currentAPN, manualAPN, sizeof(currentAPN) - 1);
  893.         strcpy(currentCarrier, "Manual");
  894.         return true;
  895.     }
  896.     String resp = sendATCommand("AT+CGNAPN", 3000);
  897.     int start = resp.indexOf('"');
  898.     int end = resp.indexOf('"', start + 1);
  899.     if (start >= 0 && end > start + 1) {
  900.         resp.substring(start + 1, end).toCharArray(currentAPN, sizeof(currentAPN));
  901.         strcpy(currentCarrier, "Auto");
  902.         return true;
  903.     }
  904.     if (strlen(simIMSI) >= 5) {
  905.         for (int i = 0; i < apnListCount; i++) {
  906.             if (strlen(apnList[i].mccMnc) >= 5 &&
  907.                 strncmp(simIMSI, apnList[i].mccMnc, strlen(apnList[i].mccMnc)) == 0) {
  908.                 strncpy(currentAPN, apnList[i].apnName, sizeof(currentAPN) - 1);
  909.                 strncpy(currentCarrier, apnList[i].carrier, sizeof(currentCarrier) - 1);
  910.                 return true;
  911.             }
  912.         }
  913.     }
  914.     strcpy(currentAPN, "internet");
  915.     strcpy(currentCarrier, "Generic");
  916.     return true;
  917. }
  918.  
  919. // ============== NETWORK ==============
  920. bool isNetworkRegistered() {
  921.     const char* regCmds[] = {"AT+CREG?", "AT+CGREG?", "AT+CEREG?"};
  922.     for (auto cmd : regCmds) {
  923.         String resp = sendATCommand(cmd, 2000);
  924.         if (resp.indexOf(",1") >= 0 || resp.indexOf(",5") >= 0) return true;
  925.     }
  926.     return false;
  927. }
  928.  
  929. bool activateDataConnection() {
  930.     feedWatchdog();
  931.     char cmd[80];
  932.     snprintf(cmd, sizeof(cmd), "AT+CGDCONT=1,\"IP\",\"%s\"", currentAPN);
  933.     sendATExpect(cmd, "OK", 2000);
  934.     if (lteModem.gprsConnect(currentAPN, "", "")) { delay(1000); feedWatchdog(); }
  935.     else {
  936.         sendATExpect("AT+CGACT=1,1", "OK", 10000);
  937.         feedWatchdog();
  938.         sendATCommand("AT+NETOPEN", 10000);
  939.         delay(2000);
  940.     }
  941.     String resp = sendATCommand("AT+CGPADDR=1", 3000);
  942.     if (resp.indexOf(".") >= 0 && resp.indexOf("0.0.0.0") < 0) return true;
  943.     resp = sendATCommand("AT+IPADDR", 3000);
  944.     return (resp.indexOf(".") >= 0 && resp.indexOf("0.0.0.0") < 0);
  945. }
  946.  
  947. // ============== HEARTBEAT ==============
  948. bool sendHeartbeat() {
  949.     if (!tcpClient.connected()) return false;
  950.    
  951.     // Check RX before sending heartbeat
  952.     quickRxCheck();
  953.    
  954.     uint8_t hb[17];
  955.     size_t len = buildHeartbeat(hb);
  956.    
  957.     connMonitor.onWriteStart();
  958.     size_t sent = tcpClient.write(hb, len);
  959.     connMonitor.onWriteComplete(len, sent);
  960.    
  961.     if (sent == len) {
  962.         stats.heartbeatsSent++;
  963.         stats.bytesSentTcp += sent;
  964.         lastKeepaliveMs = millis();
  965.        
  966.         // Check RX after sending heartbeat
  967.         quickRxCheck();
  968.         return true;
  969.     }
  970.     return false;
  971. }
  972.  
  973. // ============== DATA FORWARDING (RX PRIORITY!) ==============
  974. void processForwarding() {
  975.     loopStartMs = millis();
  976.    
  977.     if (!buffersInitialized) {
  978.         while (SerialFC.available()) SerialFC.read();
  979.         return;
  980.     }
  981.    
  982.     if (!checkHeapHealth()) return;
  983.  
  984.     // === PRIORITY 1: Check RX multiple times ===
  985.     if (bridgeState == ST_RUNNING && tcpClient.connected()) {
  986.         // Check RX up to 5 times to drain any pending data
  987.         for (int i = 0; i < 5; i++) {
  988.             if (!quickRxCheck()) break;  // No more data
  989.         }
  990.     }
  991.  
  992.     // === PRIORITY 2: FC -> TX Buffer ===
  993.     int fcBytesProcessed = 0;
  994.     while (SerialFC.available() && fcBytesProcessed < MAX_FC_BYTES_PER_LOOP) {
  995.         uint8_t byte = SerialFC.read();
  996.         fcBytesProcessed++;
  997.         stats.bytesRecvFc++;
  998.        
  999.         uint16_t frameLen = fcParser.feed(byte);
  1000.  
  1001.         if (frameLen > 0) {
  1002.             stats.mavFramesTx++;
  1003.  
  1004.             if (bridgeState == ST_RUNNING && tcpClient.connected()) {
  1005.                 if (txBuffer.space() < frameLen) {
  1006.                     size_t discarded = txBuffer.discardOldest(txBuffer.count() / 2);
  1007.                     stats.discardedBytes += discarded;
  1008.                     stats.bufferOverflows++;
  1009.                 }
  1010.  
  1011.                 if (!txBuffer.writeFrame(fcParser.getFrame(), frameLen)) {
  1012.                     stats.bufferOverflows++;
  1013.                 }
  1014.             }
  1015.         }
  1016.        
  1017.         // Check RX frequently while reading FC
  1018.         if (fcBytesProcessed % RX_CHECK_INTERVAL_BYTES == 0) {
  1019.             quickRxCheck();
  1020.         }
  1021.     }
  1022.    
  1023.     connMonitor.onFcData(fcBytesProcessed);
  1024.     checkLoopTime("FC");
  1025.    
  1026.     // === Check RX again before TX ===
  1027.     quickRxCheck();
  1028.  
  1029.     // === PRIORITY 3: TX Buffer -> TCP ===
  1030.     if (bridgeState == ST_RUNNING && tcpClient.connected()) {
  1031.         size_t available = txBuffer.count();
  1032.        
  1033.         if (available > 0) {
  1034.             static uint8_t txChunk[MAX_TX_BYTES_PER_LOOP];
  1035.            
  1036.             size_t toSend = min(available, (size_t)MAX_TX_BYTES_PER_LOOP);
  1037.             toSend = min(toSend, sizeof(txChunk));
  1038.             toSend = txBuffer.readBytes(txChunk, toSend);
  1039.  
  1040.             if (toSend > 0) {
  1041.                 size_t sent = safeTcpWrite(txChunk, toSend);
  1042.                
  1043.                 if (sent < toSend) {
  1044.                     stats.discardedBytes += (toSend - sent);
  1045.                 }
  1046.             }
  1047.         }
  1048.     }
  1049.    
  1050.     // === Final RX check ===
  1051.     quickRxCheck();
  1052.    
  1053.     checkLoopTime("TX");
  1054.     feedWatchdog();
  1055. }
  1056.  
  1057. // ============== STATE MACHINE ==============
  1058. void changeState(BridgeState newState) {
  1059.     Serial.printf("[STATE] %s -> %s\n", stateNames[bridgeState], stateNames[newState]);
  1060.     bridgeState = newState;
  1061.     stateEntryMs = millis();
  1062.     forceReconnect = false;
  1063. }
  1064.  
  1065. void handleStateMachine() {
  1066.     unsigned long elapsed = millis() - stateEntryMs;
  1067.  
  1068.     // Always check RX in RUNNING state
  1069.     if (bridgeState == ST_RUNNING) {
  1070.         quickRxCheck();
  1071.     }
  1072.  
  1073.     switch (bridgeState) {
  1074.         case ST_POWER_ON:
  1075.             Serial.println("\n=== ESP32 MAVLink Bridge v9.5.3 ===");
  1076.             Serial.println("=== Patched RX Priority Edition ===\n");
  1077.             Serial.printf("[HEAP] Free: %dKB\n", ESP.getFreeHeap() / 1024);
  1078.             if (powerOnModule()) changeState(ST_WAIT_MODEM);
  1079.             else changeState(ST_HARD_RESET);
  1080.             break;
  1081.  
  1082.         case ST_WAIT_MODEM:
  1083.             if (waitModemReady()) changeState(ST_CHECK_STATUS);
  1084.             else changeState(ST_HARD_RESET);
  1085.             break;
  1086.  
  1087.         case ST_CHECK_STATUS:
  1088.             currentModemStatus = getModemStatus();
  1089.             Serial.printf("[STATUS] Modem: %s\n", modemStatusNames[currentModemStatus]);
  1090.             if (currentModemStatus == MODEM_OFF) changeState(ST_POWER_ON);
  1091.             else if (currentModemStatus == MODEM_STANDBY || currentModemStatus == MODEM_DEGRADED) {
  1092.                 wakeupRetryCount = 0; changeState(ST_WAKEUP_MODEM);
  1093.             }
  1094.             else if (currentModemStatus == MODEM_BOOTING) {
  1095.                 if (elapsed > 10000) changeState(ST_WAKEUP_MODEM); else delay(1000);
  1096.             }
  1097.             else changeState(ST_CLEAR_CONFIG);
  1098.             break;
  1099.  
  1100.         case ST_WAKEUP_MODEM:
  1101.             if (wakeUpModem()) changeState(ST_CLEAR_CONFIG);
  1102.             else {
  1103.                 wakeupRetryCount++;
  1104.                 if (wakeupRetryCount >= MAX_WAKEUP_RETRIES) changeState(ST_HARD_RESET);
  1105.                 else { delay(2000); changeState(ST_CHECK_STATUS); }
  1106.             }
  1107.             break;
  1108.  
  1109.         case ST_CLEAR_CONFIG:
  1110.             clearModemConfig();
  1111.             changeState(ST_CHECK_SIM);
  1112.             break;
  1113.  
  1114.         case ST_CHECK_SIM:
  1115.             currentModemStatus = getModemStatus();
  1116.             if (currentModemStatus == MODEM_STANDBY || currentModemStatus == MODEM_DEGRADED) {
  1117.                 changeState(ST_WAKEUP_MODEM);
  1118.             } else if (waitForSimReady()) changeState(ST_GET_IMSI);
  1119.             else { if (elapsed > 30000) changeState(ST_HARD_RESET); else delay(3000); }
  1120.             break;
  1121.  
  1122.         case ST_GET_IMSI:
  1123.             readIMSI();
  1124.             changeState(ST_DETECT_APN);
  1125.             break;
  1126.  
  1127.         case ST_DETECT_APN:
  1128.             detectAPN();
  1129.             Serial.printf("[APN] %s (%s)\n", currentAPN, currentCarrier);
  1130.             changeState(ST_WAIT_NETWORK);
  1131.             break;
  1132.  
  1133.         case ST_WAIT_NETWORK:
  1134.             feedWatchdog();
  1135.             if (isNetworkRegistered()) changeState(ST_GPRS_CONNECT);
  1136.             else if (elapsed > NETWORK_WAIT_TIMEOUT) {
  1137.                 networkRetryCount++;
  1138.                 if (networkRetryCount >= MAX_NETWORK_RETRIES) changeState(ST_HARD_RESET);
  1139.                 else changeState(ST_CHECK_STATUS);
  1140.             } else delay(2000);
  1141.             break;
  1142.  
  1143.         case ST_GPRS_CONNECT:
  1144.             if (activateDataConnection()) {
  1145.                 networkReady = true;
  1146.                 if (!buffersInitialized) {
  1147.                     txBuffer.reset();
  1148.                     fcParser.reset();
  1149.                     tcpParser.reset();
  1150.                     buffersInitialized = true;
  1151.                 }
  1152.                 changeState(ST_TCP_CONNECT);
  1153.             } else {
  1154.                 networkRetryCount++;
  1155.                 if (networkRetryCount >= MAX_NETWORK_RETRIES) changeState(ST_HARD_RESET);
  1156.                 else { delay(2000); changeState(ST_WAIT_NETWORK); }
  1157.             }
  1158.             break;
  1159.  
  1160.         case ST_TCP_CONNECT:
  1161.             Serial.printf("[TCP] Connecting to %s:%d...\n", targetHost, targetPort);
  1162.             while (SerialLTE.available()) SerialLTE.read();
  1163.             tcpClient.setTimeout(CONNECTION_TIMEOUT_MS);
  1164.             if (tcpClient.connect(targetHost, targetPort)) {
  1165.                 Serial.println("[TCP] Connected!");
  1166.                 connMonitor.reset();
  1167.                 initialHeartbeatCount = 0;
  1168.                 forceReconnect = false;
  1169.                 lastRxCheckMs = millis();
  1170.                 maxRxGapMs = 0;
  1171.                 changeState(ST_SEND_HEARTBEATS);
  1172.             } else {
  1173.                 Serial.println("[TCP] Failed");
  1174.                 stats.tcpReconnects++;
  1175.                 tcpRetryCount++;
  1176.                 if (tcpRetryCount >= MAX_TCP_RETRIES) changeState(ST_NETWORK_RECOVER);
  1177.                 else delay(2000);
  1178.             }
  1179.             break;
  1180.  
  1181.         case ST_SEND_HEARTBEATS:
  1182.             feedWatchdog();
  1183.             if (initialHeartbeatCount < INITIAL_HEARTBEAT_COUNT) {
  1184.                 if (sendHeartbeat()) {
  1185.                     initialHeartbeatCount++;
  1186.                     Serial.printf("[HB] Initial %d/%d\n", initialHeartbeatCount, INITIAL_HEARTBEAT_COUNT);
  1187.                 }
  1188.                 delay(INITIAL_HEARTBEAT_DELAY);
  1189.             } else {
  1190.                 Serial.println("\n*** BRIDGE READY ***");
  1191.                 Serial.println("*** RX Priority Active - Frequent Checks ***\n");
  1192.                 tcpRetryCount = 0;
  1193.                 networkRetryCount = 0;
  1194.                 if (sessionStartMs == 0) sessionStartMs = millis();
  1195.                 changeState(ST_RUNNING);
  1196.             }
  1197.             if (!tcpClient.connected()) {
  1198.                 Serial.println("[HB] Lost connection");
  1199.                 changeState(ST_TCP_RECONNECT);
  1200.             }
  1201.             break;
  1202.  
  1203.         case ST_RUNNING:
  1204.             // RX check already done at start of function
  1205.            
  1206.             if (forceReconnect) {
  1207.                 Serial.println("[TCP] Forced reconnect");
  1208.                 changeState(ST_TCP_RECONNECT);
  1209.                 return;
  1210.             }
  1211.  
  1212.             if (millis() - lastHealthCheckMs > HEALTH_CHECK_INTERVAL) {
  1213.                 lastHealthCheckMs = millis();
  1214.                
  1215.                 if (!tcpClient.connected()) {
  1216.                     Serial.println("[TCP] Disconnected");
  1217.                     changeState(ST_TCP_RECONNECT);
  1218.                     return;
  1219.                 }
  1220.                
  1221.                 ConnectionHealth health = connMonitor.evaluate(txBuffer.count());
  1222.                 if (health == CONN_DEAD) {
  1223.                     Serial.println("[TCP] Connection DEAD!");
  1224.                     stats.connectionDeaths++;
  1225.                     tcpClient.stop();
  1226.                     changeState(ST_TCP_RECONNECT);
  1227.                     return;
  1228.                 }
  1229.                 if (health == CONN_STALLED) stats.writeStalls++;
  1230.             }
  1231.  
  1232.             if (millis() - lastKeepaliveMs > KEEPALIVE_INTERVAL_MS) {
  1233.                 sendHeartbeat();
  1234.             }
  1235.  
  1236.             if (millis() - lastModemCheckMs > 60000) {
  1237.                 lastModemCheckMs = millis();
  1238.                 if (quickModemCheck() == MODEM_OFF) {
  1239.                     Serial.println("[RUN] Modem died!");
  1240.                     tcpClient.stop();
  1241.                     buffersInitialized = false;
  1242.                     changeState(ST_POWER_ON);
  1243.                 }
  1244.             }
  1245.             break;
  1246.  
  1247.         case ST_TCP_RECONNECT:
  1248.             feedWatchdog();
  1249.             tcpClient.stop();
  1250.             delay(500);
  1251.             txBuffer.discardAll();
  1252.             fcParser.reset();
  1253.             stats.tcpReconnects++;
  1254.             tcpRetryCount++;
  1255.             Serial.printf("[TCP] Reconnect %d/%d\n", tcpRetryCount, MAX_TCP_RETRIES);
  1256.  
  1257.             if (!isModemResponding()) changeState(ST_POWER_ON);
  1258.             else if (!isNetworkRegistered()) changeState(ST_NETWORK_RECOVER);
  1259.             else if (tcpRetryCount >= MAX_TCP_RETRIES) changeState(ST_NETWORK_RECOVER);
  1260.             else { delay(1000 * tcpRetryCount); changeState(ST_TCP_CONNECT); }
  1261.             break;
  1262.  
  1263.         case ST_NETWORK_RECOVER:
  1264.             Serial.println("[NET] Recovery...");
  1265.             feedWatchdog();
  1266.             stats.networkReconnects++;
  1267.             tcpClient.stop();
  1268.             txBuffer.discardAll();
  1269.             fcParser.reset();
  1270.             clearModemConfig();
  1271.             networkRetryCount++;
  1272.             tcpRetryCount = 0;
  1273.             if (networkRetryCount >= MAX_NETWORK_RETRIES) changeState(ST_HARD_RESET);
  1274.             else changeState(ST_CHECK_STATUS);
  1275.             break;
  1276.  
  1277.         case ST_HARD_RESET:
  1278.             Serial.printf("[RESET] Hard reset %d/%d\n", hardResetCount + 1, MAX_HARD_RESET_RETRIES);
  1279.             feedWatchdog();
  1280.             buffersInitialized = false;
  1281.             networkReady = false;
  1282.             txBuffer.reset();
  1283.             fcParser.reset();
  1284.             hardResetModule();
  1285.             hardResetCount++;
  1286.             tcpRetryCount = 0;
  1287.             networkRetryCount = 0;
  1288.             wakeupRetryCount = 0;
  1289.             if (hardResetCount >= MAX_HARD_RESET_RETRIES) changeState(ST_SYSTEM_REBOOT);
  1290.             else changeState(ST_WAIT_MODEM);
  1291.             break;
  1292.  
  1293.         case ST_SYSTEM_REBOOT:
  1294.             Serial.println("\n!!! REBOOTING !!!\n");
  1295.             delay(1000);
  1296.             ESP.restart();
  1297.             break;
  1298.     }
  1299. }
  1300.  
  1301. // ============== STATUS ==============
  1302. void printStatus() {
  1303.     if (millis() - lastStatusPrintMs < STATUS_PRINT_INTERVAL) return;
  1304.     lastStatusPrintMs = millis();
  1305.  
  1306.     uint32_t currentHeap = ESP.getFreeHeap();
  1307.    
  1308.     Serial.println("═══════════════════════════════════════");
  1309.     Serial.printf("  State: %s | Health: %s\n",
  1310.                  stateNames[bridgeState], connMonitor.getHealthString());
  1311.  
  1312.     if (bridgeState == ST_RUNNING) {
  1313.         Serial.printf("  TX buf: %d/%d (peak: %d)\n",
  1314.                      txBuffer.count(), RING_TX_BUF_SIZE, (int)connMonitor.getPeakBuffer());
  1315.         Serial.printf("  FC: %.1f KB/s -> TX: %.1f KB/s | RX: %.1f KB/s\n",
  1316.                      connMonitor.getFcRate() / 1024.0f,
  1317.                      connMonitor.getTxRate() / 1024.0f,
  1318.                      connMonitor.getRxRate() / 1024.0f);
  1319.         Serial.printf("  MAV: TX=%lu RX=%lu | HB=%lu\n",
  1320.                      stats.mavFramesTx, stats.mavFramesRx, stats.heartbeatsSent);
  1321.         Serial.printf("  RX: checks=%lu interrupts=%lu critical=%lu\n",
  1322.                      stats.rxChecks, stats.rxInterrupts, stats.criticalCmdsRx);
  1323.         Serial.printf("  Max RX gap: %lu ms\n", maxRxGapMs);
  1324.        
  1325.         if (verboseDebug) {
  1326.             connMonitor.printDebugInfo();
  1327.         }
  1328.     }
  1329.  
  1330.     if (stats.discardedBytes || stats.bufferOverflows) {
  1331.         Serial.printf("  Discarded: %luB | Overflows: %lu\n",
  1332.                      stats.discardedBytes, stats.bufferOverflows);
  1333.     }
  1334.  
  1335.     Serial.printf("  Heap: %dKB (min: %dKB) | Reconnects: %lu\n",
  1336.                  currentHeap / 1024, minFreeHeap / 1024, stats.tcpReconnects);
  1337.     Serial.println("═══════════════════════════════════════\n");
  1338.    
  1339.     // Reset max gap for next period
  1340.     maxRxGapMs = 0;
  1341. }
  1342.  
  1343. // ============== SETUP ==============
  1344. void setup() {
  1345.     Serial.begin(115200);
  1346.     delay(500);
  1347.  
  1348.     Serial.println("\n========================================");
  1349.     Serial.println("ESP32 MAVLink Bridge v9.5.3");
  1350.     Serial.println("Patched RX Priority Edition");
  1351.     Serial.println("----------------------------------------");
  1352.     Serial.println("Changes from v9.5:");
  1353.     Serial.println("- quickRxCheck() every 32 bytes TX");
  1354.     Serial.println("- Max 100ms per write call");
  1355.     Serial.println("- RX checked before/after each chunk");
  1356.     Serial.println("- Critical command logging");
  1357.     Serial.println("========================================\n");
  1358.  
  1359.     initWatchdog();
  1360.  
  1361.     SerialFC.setRxBufferSize(SERIAL_RX_BUF_SIZE);
  1362.     SerialFC.begin(FC_BAUD_RATE, SERIAL_8N1, FC_RX_PIN, FC_TX_PIN);
  1363.  
  1364.     SerialLTE.setRxBufferSize(SERIAL_RX_BUF_SIZE);
  1365.     SerialLTE.begin(115200, SERIAL_8N1, LTE_RX_PIN, LTE_TX_PIN);
  1366.  
  1367.     pinMode(LTE_PWRKEY_PIN, OUTPUT);
  1368.     digitalWrite(LTE_PWRKEY_PIN, LOW);
  1369.  
  1370.     Serial.printf("[INIT] Heap: %dKB\n", ESP.getFreeHeap() / 1024);
  1371.     minFreeHeap = ESP.getFreeHeap();
  1372.  
  1373.     delay(3000);
  1374.     feedWatchdog();
  1375.  
  1376.     stateEntryMs = millis();
  1377.     lastStatusPrintMs = millis();
  1378.     lastHealthCheckMs = millis();
  1379.     lastKeepaliveMs = millis();
  1380.     lastModemCheckMs = millis();
  1381.     lastRxCheckMs = millis();
  1382.     loopStartMs = millis();
  1383.  
  1384.     feedWatchdog();
  1385. }
  1386.  
  1387. // ============== LOOP ==============
  1388. void loop() {
  1389.     feedWatchdog();
  1390.     processForwarding();
  1391.     handleStateMachine();
  1392.     printStatus();
  1393.     yield();
  1394. }
Advertisement
Add Comment
Please, Sign In to add comment