/* LVGL Multi-Sensor Display (RTC + QMI8658 + SHT41 + Quectel L76K GPS)
 * I2C: SDA=8, SCL=7 (Shared for all sensors)
 * Display: ST7789 240x240 (1.54-inch)
 * Layout:
 * - Top-Left: PCF85063 (Date/Time) - Blue Title
 * - Top-Right: Quectel L76K (GPS Data) - Green Title
 * - Left Middle-Bottom: QMI8658 (IMU Data) - Yellow Title
 * - Right Middle-Bottom: SHT41 (Temp/Hum) - Red Title
 * Detection: RTC, AXP2101, QMI8658, SHT41, ES8311, Quectel L76K, WiFi AP, LoRa (E22)
 * UI Style:Black Background + Colored Titles + Black Data + Clean Layout
 */

#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include <Adafruit_MCP23X17.h>
#include <Wire.h>
#include <Arduino.h>
#include <WiFi.h>
#include "esp_wifi.h"
#include "esp_netif.h"
#include <time.h>
#include <string.h>
#include "esp_check.h"
#include "esp_task_wdt.h"
#include "driver/ledc.h"  // LEDC PWM support
#include "driver/gpio.h"  // GPIO functions (gpio_reset_pin, gpio_set_direction, etc.)
#include "soc/gpio_struct.h"  // GPIO register structure for GPIO.in access
#include "esp_efuse.h"  // ESP32 eFuse (for MAC address)
#include "esp_mac.h"  // ESP32 MAC address functions
 
 // -------------------------- Library Includes --------------------------
 // AXP2101
 #define XPOWERS_CHIP_AXP2101
 #include "XPowersLib.h"
 XPowersPMU power;
 
 // QMI8658
 #include "SensorQMI8658.hpp"
 SensorQMI8658 qmi;
 
 // PCF85063
 #include "SensorPCF85063.hpp"
 SensorPCF85063 rtc;
 
 // SHT41
 #define SHT41_ADDR_44 0x44
 #define SHT41_ADDR_45 0x45
 #define SHT41_CMD_MEASURE_HIGH 0xFD
 #define SHT41_CMD_SOFT_RESET 0x94
 
 // ES8311
 #include "ESP_I2S.h"
 #include "es8311.h"
 
 // Quectel L76K GPS
 #define GPS_TX_PIN 43
 #define GPS_RX_PIN 44
 #define GPS_BAUDRATE 9600
 HardwareSerial GPS_SERIAL(2);
 
 // LoRa Module (E22-900MM22S) - Using SPI Interface
 // According to schematic: SPI pins directly connected, control pins via EXIO (MCP23017)
 // SPI pins: SCK=GPIO12, NSS=GPIO14, MOSI=GPIO13, MISO=GPIO11
 // Control pins: NRST=EXIO3, BUSY=EXIO10, DIO1=EXIO9
 // Module: E22-900MM22S (based on SX1262/SX1268)
 // Frequency: 850-930MHz (typically 915MHz for North America or 868MHz for Europe)
 
 #include <SPI.h>
 
 // SPI interface definitions
 // LoRa Module Pin Definition (E22-900MM22S)
 // According to the schematic: E22 is connected via SPI
 // SPI pins (shared with LCD SPI bus, but with a different CS):
 #define E22_SCK_PIN  12   // E22_SCK  -> GPIO12 (shared with LCD)
 #define E22_NSS_PIN  14   // E22_NSS  -> GPIO14
 #define E22_MOSI_PIN 13   // E22_MOSI -> GPIO13 (shared with LCD)
 #define E22_MISO_PIN 11   // E22_MISO -> GPIO11 (shared with LCD)
// Control pins (via MCP23017 I/O expander):
// MCP23017 has two ports: GPA (GPA0-GPA7, 0-7) and GPB (GPB0-GPB7, 8-15)
// According to the schematic:
//   EXIO3  = GPA3 (GPA port, bit 3)
//   EXIO9  = GPB1 (GPB port, bit 1)
//   EXIO10 = GPB2 (GPB port, bit 2)
// Note: pin numbering uses 0-15 (GPA0-7 = 0-7, GPB0-7 = 8-15)
#define E22_NRST_EXIO 3   // E22_NRST -> EXIO3 (MCP23017 GPA3)
#define E22_BUSY_EXIO 10  // E22_BUSY -> EXIO10 (MCP23017 GPB2)
#define E22_DIO1_EXIO 9   // E22_DIO1 -> EXIO9 (MCP23017 GPB1)

// EXIO pin definitions (controlled via MCP23017)
// Note: EXIO pins 0-7 map to MCP23017 GPA bits 0-7, EXIO pins 8-15 map to GPB bits 0-7
// Based on schematic: EXIO3=NRST, EXIO10=BUSY, EXIO9=DIO1, EXIO8=DIO2
// EXIO3 is GPA bit 3, EXIO10 is GPB bit 2, EXIO9 is GPB bit 1, EXIO8 is GPB bit 0
// DIO2 controls both TXEN and RXEN:
//   - DIO2=LOW  → TXEN=LOW(TX disabled), RXEN=HIGH(RX enabled) via MOSFET Q4
//   - DIO2=HIGH → TXEN=HIGH(TX enabled), RXEN=LOW(RX disabled) via MOSFET Q4
#define LORA_EXIO_NRST  3   // EXIO3 → MCP23017 GPA3
#define LORA_EXIO_BUSY  10  // EXIO10 → MCP23017 GPB2 (has 10K pull-up, default HIGH)
#define LORA_EXIO_DIO1  9   // EXIO9 → MCP23017 GPB1
#define LORA_EXIO_DIO2  8   // EXIO8 → MCP23017 GPB0 (controls TXEN directly and RXEN via MOSFET)
 
 // LoRa module parameters (both devices must have same settings)
 #define LORA_FREQUENCY  915.0    // MHz (or 868.0 for Europe)
 #define LORA_BANDWIDTH  125.0    // kHz
 #define LORA_SPREADING  7        // SF7 (6-12)
 #define LORA_CODING_RATE 5       // CR 4/5 (5-8)
 #define LORA_SYNC_WORD  0x12     // Sync word (both devices must match)
 #define LORA_OUTPUT_POWER 22     // dBm (0-22)
 #define LORA_PREAMBLE_LEN 8      // Preamble length
 #define LORA_CRC_ENABLED 1       // Enable CRC
 
 // LoRa module state tracking
 struct LoRaModuleState {
   bool initialized;
 } loraModuleState = {false};
 
 // Note: UART mode removed - using SPI interface only
 
 // -------------------------- Pin Definitions --------------------------
 #define GFX_BL 6
 #define SPI_MISO 11
 #define SPI_MOSI 13
 #define SPI_SCLK 12
 #define LCD_CS 10
 #define LCD_DC 16
 #define LCD_RST 10
 #define LCD_HOR_RES 240
 #define LCD_VER_RES 240
 #define I2C_SDA_PIN 8
 #define I2C_SCL_PIN 7
 #define COMM_RETRY_COUNT 3
 #define MEASURE_DELAY 200
 #define BUTTON_PIN 0  // GPIO0, boot button with internal pullup

// Button interrupt variables (volatile for ISR)
volatile bool button_interrupt_flag = false;  // Flag set by ISR when button state changes
volatile unsigned long button_interrupt_time = 0;  // Time when interrupt occurred (using xTaskGetTickCountFromISR)
volatile bool button_interrupt_state = false;  // Button state captured at interrupt time (false=LOW/pressed, true=HIGH/released)
volatile bool button_last_state = true;  // Last known button state (true=HIGH/released, false=LOW/pressed) for debouncing

// Button state machine (non-blocking)
enum ButtonState {
  BUTTON_IDLE,
  BUTTON_PRESSED,
  BUTTON_SHORT_PRESS,
  BUTTON_LONG_PRESS
};

struct ButtonHandler {
  ButtonState state;
  unsigned long press_start_time;
  unsigned long last_check_time;
  bool long_press_handled;
  unsigned long last_state_change_time;
  static const unsigned long DEBOUNCE_TIME = 30;      // 30ms debounce
  static const unsigned long SHORT_PRESS_MIN = 10;   // 10ms minimum
  static const unsigned long SHORT_PRESS_MAX = 2000; // 2000ms maximum
  static const unsigned long LONG_PRESS_TIME = 2000; // 2000ms for long press
};

ButtonHandler button_handler = {BUTTON_IDLE, 0, 0, false, 0};

// LoRa module initialization state machine (non-blocking)
enum LoRaInitState {
  LORA_INIT_IDLE,
  LORA_INIT_STANDBY,
  LORA_INIT_RF_SWITCH,
  LORA_INIT_PACKET_TYPE,
  LORA_INIT_SYNC_WORD,
  LORA_INIT_FREQUENCY,
  LORA_INIT_PA_CONFIG,
  LORA_INIT_TX_PARAMS,
  LORA_INIT_MODULATION,
  LORA_INIT_SYMB_TIMEOUT,
  LORA_INIT_PACKET_PARAMS,
  LORA_INIT_BUFFER_ADDR,
  LORA_INIT_CLEAR_IRQ,
  LORA_INIT_DIO_IRQ,
  LORA_INIT_FINAL_STANDBY,
  LORA_INIT_COMPLETE
};

struct LoRaInitHandler {
  LoRaInitState state;
  unsigned long last_step_time;
  bool is_tx_mode;
  static const unsigned long STEP_DELAY = 5;  // 5ms between steps (non-blocking)
};

LoRaInitHandler lora_init_handler = {LORA_INIT_IDLE, 0, false};

// Startup test state machine (non-blocking)
enum StartupTestState {
  STARTUP_TEST_IDLE,
  STARTUP_TEST_WAIT_LORA_INIT,
  STARTUP_TEST_SEND_DATA,
  STARTUP_TEST_WAIT_TX_COMPLETE,
  STARTUP_TEST_SWITCH_TO_RX,
  STARTUP_TEST_WAIT_RX,
  STARTUP_TEST_COMPLETE
};

struct StartupTestHandler {
  StartupTestState state;
  unsigned long start_time;
  unsigned long last_action_time;
  bool lora_initialized;
  bool tx_completed;
  static const unsigned long RX_WAIT_TIME = 5000;  // 5 seconds
  static const unsigned long ACTION_DELAY = 50;     // 50ms between actions
};

StartupTestHandler startup_test = {STARTUP_TEST_IDLE, 0, 0, false, false};
 
 // -------------------------- Global Objects & Data --------------------------
 // Display
 // MCP23017 address: A2=0, A1=0, A0 depends on configuration (default 0x20, can be 0x21)
 // Default: 0x20 (A0=0, A1=0, A2=0, all grounded via R50)
 // Can also be: 0x21 (A0=1, A1=0, A2=0) if A0 is pulled high
 Adafruit_MCP23X17 mcp;
 uint8_t mcp23017_address = 0x20;  // Default address, will be auto-detected (0x20 or 0x21)
 Arduino_DataBus *bus = new Arduino_ESP32SPI(LCD_DC, LCD_CS, SPI_SCLK, SPI_MOSI, SPI_MISO);
 Arduino_GFX *gfx = new Arduino_ST7789(bus, LCD_RST, 0, true, LCD_HOR_RES, LCD_VER_RES);
 
 // LVGL Buffer
 uint32_t screenWidth, screenHeight, bufSize;
 lv_disp_draw_buf_t draw_buf;
 lv_color_t *disp_draw_buf1;
 lv_color_t *disp_draw_buf2;
 lv_disp_drv_t disp_drv;
 
 // UI Elements (Grouped by Sensor)
 // PCF85063 (Top-Left)
 lv_obj_t *label_rtc_title;
 lv_obj_t *label_rtc_datetime;
 lv_obj_t *label_rtc_weekday;
 
 // QMI8658 (Left Middle-Bottom)
 lv_obj_t *label_qmi_title;
 lv_obj_t *label_qmi_accel;
 lv_obj_t *label_qmi_gyro;
 lv_obj_t *label_qmi_temp;
 
 // Quectel L76K GPS (Top-Right)
 lv_obj_t *label_gps_title;
 lv_obj_t *label_gps_time;
 lv_obj_t *label_gps_lat;
 lv_obj_t *label_gps_lon;
 lv_obj_t *label_gps_sat;
 lv_obj_t *label_gps_alt;
 
 // SHT41 (Right Middle-Bottom)
 lv_obj_t *label_sht_title;
 lv_obj_t *label_sht_temp;
 lv_obj_t *label_sht_hum;
 
// Main Screen Bottom Prompt
lv_obj_t *label_main_lora_prompt;

// Main Screen MAC Address Display
lv_obj_t *label_main_mac_address;
 
 // Detection Screen UI Elements
 lv_obj_t *label_detect_title;
 lv_obj_t *screen_detect;
 static lv_style_t detect_label_style;
 #define DETECT_LABEL_START_Y 30
 #define DETECT_LABEL_SPACING 18
 lv_obj_t *screen_main;
 lv_obj_t *screen_lora_tx;  // LoRa TX mode screen
 lv_obj_t *screen_lora_rx;  // LoRa RX mode screen
 
 // LoRa Screen UI Elements (shared between TX and RX modes, updated when switching) - Fully reference 16_lvgl_LoRaTest.ino
 lv_obj_t *label_lora_title;
 lv_obj_t *label_lora_status;
 lv_obj_t *label_lora_freq;
 lv_obj_t *label_lora_power;
 lv_obj_t *label_lora_mode;
 lv_obj_t *label_lora_tx_count;
 lv_obj_t *label_lora_rx_count;
 lv_obj_t *label_lora_rssi;
 lv_obj_t *label_lora_message;
 
 // Timers
 lv_timer_t *sensor_timer = NULL;
 lv_timer_t *lora_timer = NULL;
 
 // Data Structures
 struct SHT41Data {
   float temperature;
   float humidity;
   bool isValid;
   uint8_t detectedAddr;
 } sht41Data = {0, 0, false, 0};
 
 struct QMI8658Data {
   float ax, ay, az;
   float gx, gy, gz;
   float temp;
   bool isValid;
 } qmiData = {0,0,0,0,0,0,0,false};
 
 // ES8311 Global Objects
 I2SClass i2s;
 es8311_handle_t g_es_handle = NULL;
 
// ES8311 Audio Parameters
#define EXAMPLE_SAMPLE_RATE (44100)
#define EXAMPLE_MCLK_MULTIPLE (256)
#define EXAMPLE_MCLK_FREQ_HZ (EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE)
#define EXAMPLE_VOICE_VOLUME (70)  // Volume 0-100

// Speaker/Buzzer Pin Definition (Use I2S_DOUT pin or dedicated GPIO)
#define SPEAKER_PIN 5  // GPIO5 (I2S_DOUT_PIN) - Can be used for PWM output
#define BEEP_FREQUENCY 1500  // Buzzer frequency (Hz) - Reduced to 1500Hz, easier for most speakers to respond
#define BEEP_DURATION 300    // Single beep duration (ms) - Increased to 300ms
#define BEEP_PAUSE 150       // Beep pause interval (ms)
 
 
 // GPS Data Structure
 struct GPSData {
   String utcTime;
   String utcDate;  // DDMMYY format from GPRMC
   String latitude;
   String latDir;
   String longitude;
   String lonDir;
   uint8_t fixQuality;
   uint8_t satCount;
   float hdop;
   float altitude;
   String status;
   bool timeSynced;  // Flag to prevent multiple syncs
 } gpsData;
 
 // LoRa Data Structure
 struct LoRaData {
   String status;
   String frequency;
   String power;
   String mode;
   uint32_t tx_count;
   uint32_t rx_count;
   int16_t rssi;
   String last_message;
   bool module_detected;
   bool tx_mode;
   bool rx_mode;
 };
 
LoRaData loraData = {
  "Disconnected",  // status
  "915/868MHz",    // frequency
  "22dBm",         // power
  "SPI",           // mode
  0,               // tx_count
  0,               // rx_count
  0,               // rssi
  "None",          // last_message
  false,           // module_detected
  false,           // tx_mode
  false            // rx_mode
};
 
 // Global flag to force RX mode reset when switching from TX to RX
 static bool force_rx_reset = false;
 
 // -------------------------- Common Helper Functions --------------------------
 // Float to String (LVGL %f Fix)
 void float_to_str(char *buf, float value, int decimals) {
   if (isnan(value) || !isfinite(value)) {
     strcpy(buf, "--.-");
     return;
   }
   if (value < 0) {
     *buf++ = '-';
     value = -value;
   }
   long integer_part = (long)value;
   long fractional_part = (long)((value - integer_part) * pow(10, decimals) + 0.5);
   sprintf(buf, "%ld.", integer_part);
   buf += strlen(buf);
   sprintf(buf, "%0*d", decimals, fractional_part);
 }
 
// LCD Reset
void lcd_reset(void) {
  mcp.digitalWrite(1, HIGH);
  delay(10);
  mcp.digitalWrite(1, LOW);
  delay(10);
  mcp.digitalWrite(1, HIGH);
  delay(50);  // Reduced delay for faster screen clearing
}
 
 // -------------------------- PCF85063 (RTC) Functions --------------------------
 bool pcf85063_init() {
   if (!rtc.begin(Wire)) {
     Serial.println("PCF85063 not found! Check wiring.");
     return false;
   }
   RTC_DateTime datetime = rtc.getDateTime();
   // If RTC time is invalid (year < 2025), set to initial default time
   // RTC will continue counting from this initial value until GPS time is available
   if (datetime.getYear() < 2025) {
     rtc.setDateTime(2025, 1, 1, 12, 0, 0);
     Serial.println("RTC set to initial default time: 2025-01-01 12:00:00");
     Serial.println("RTC will continue counting from this time until GPS sync");
   } else {
     Serial.printf("RTC current time: %04d-%02d-%02d %02d:%02d:%02d\n",
                   datetime.getYear(), datetime.getMonth(), datetime.getDay(),
                   datetime.getHour(), datetime.getMinute(), datetime.getSecond());
   }
   Serial.println("PCF85063 initialized");
   return true;
 }
 
 void read_pcf85063(char *datetime_buf, char *weekday_buf) {
   RTC_DateTime datetime = rtc.getDateTime();
   int year = datetime.getYear();
   int month = datetime.getMonth();
   int day = datetime.getDay();
   int hour = datetime.getHour();
   int minute = datetime.getMinute();
   int second = datetime.getSecond();
 
   // Calculate Weekday (use temporary variables to avoid modifying original values)
   int calc_month = month;
   int calc_year = year;
   if (calc_month < 3) { calc_month += 12; calc_year--; }
   int k = calc_year % 100;
   int j = calc_year / 100;
   int weekday = (day + 13 * (calc_month + 1) / 5 + k + k / 4 + j / 4 + 5 * j) % 7;
   const char* weekday_names[] = {"Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"};
   strcpy(weekday_buf, weekday_names[weekday]);
 
   // Format DateTime (use original month and year values)
   sprintf(datetime_buf, "%04d-%02d-%02d %02d:%02d:%02d",
           year, month, day, hour, minute, second);
 }
 
 // -------------------------- AXP2101 (Power) Functions --------------------------
 bool axp2101_init() {
   Serial.println("Initializing AXP2101...");
   bool result = power.begin(Wire, AXP2101_SLAVE_ADDRESS, I2C_SDA_PIN, I2C_SCL_PIN);
   if (!result) {
     Serial.println("AXP2101 initialization failed!");
     return false;
   }
 
   // Core Configuration
   power.setVbusVoltageLimit(XPOWERS_AXP2101_VBUS_VOL_LIM_4V36);
   power.setVbusCurrentLimit(XPOWERS_AXP2101_VBUS_CUR_LIM_1500MA);
   power.setSysPowerDownVoltage(2600);
   power.enableBattDetection();
   power.enableVbusVoltageMeasure();
   power.enableBattVoltageMeasure();
 
   Serial.println("AXP2101 initialized");
   return true;
 }
 
// -------------------------- ES8311 (Audio) Functions --------------------------
void initPAByMCP23017() {
  // MCP23017: Set GPA7 (pin 7) HIGH for PA enable
  // According to ESP-IDF driver: OLATA=0x14 (output latch), IODIRA=0x00 (direction)
  // BANK=0 mode register map:
  //   IODIRA = 0x00 (direction: 0=output, 1=input)
  //   IODIRB = 0x01
  //   GPIOA = 0x12 (input port A, read-only)
  //   GPIOB = 0x13 (input port B, read-only)
  //   OLATA = 0x14 (output latch A, write output value)
  //   OLATB = 0x15 (output latch B, write output value)
  const uint8_t MCP23017_ADDR = 0x20;
  const uint8_t MCP23017_IODIRA = 0x00;  // Direction register A
  const uint8_t MCP23017_OLATA = 0x14;   // Output latch A (for writing output)
  
  // Step 1: Configure GPA7 as output (set IODIRA bit 7 to 0)
  Wire.beginTransmission(MCP23017_ADDR);
  Wire.write(MCP23017_IODIRA);
  Wire.endTransmission();
  Wire.requestFrom(MCP23017_ADDR, (uint8_t)1);
  uint8_t iodira = Wire.read();
  
  // Clear bit 7 to make GPA7 an output (0=output, 1=input)
  iodira &= ~0x80;
  
  Wire.beginTransmission(MCP23017_ADDR);
  Wire.write(MCP23017_IODIRA);
  Wire.write(iodira);
  Wire.endTransmission();
  delay(5);
  
  // Step 2: Set GPA7 output HIGH via OLATA register
  Wire.beginTransmission(MCP23017_ADDR);
  Wire.write(MCP23017_OLATA);
  Wire.endTransmission();
  Wire.requestFrom(MCP23017_ADDR, (uint8_t)1);
  uint8_t olata = Wire.read();
  
  // Set bit 7 (GPA7) HIGH
  olata |= 0x80;
  
  // Write to OLATA register (output latch)
  Wire.beginTransmission(MCP23017_ADDR);
  Wire.write(MCP23017_OLATA);
  Wire.write(olata);
  Wire.endTransmission();
  delay(10);
}
 
 bool es8311_init() {
   Serial.println("Initializing ES8311...");
   
  // Initialize PA
  initPAByMCP23017();
   
   // Create ES8311 handle
   g_es_handle = es8311_create(I2C_NUM_0, ES8311_ADDRESS_0);
   if (g_es_handle == NULL) {
     Serial.println("ES8311 create failed!");
     return false;
   }
 
   // Configure clock
   const es8311_clock_config_t es_clk = {
     .mclk_inverted = false,
     .sclk_inverted = false,
     .mclk_from_mclk_pin = true,
     .mclk_frequency = 44100 * 256,
     .sample_frequency = 44100
   };
 
   // Initialize ES8311
   esp_err_t ret = es8311_init(g_es_handle, &es_clk, ES8311_RESOLUTION_16, ES8311_RESOLUTION_16);
   if (ret != ESP_OK) {
     Serial.printf("ES8311 init failed: %s\n", esp_err_to_name(ret));
     return false;
   }
 
   // Configure microphone
   es8311_microphone_config(g_es_handle, false);
   
   // Set volume
   es8311_voice_volume_set(g_es_handle, 70, NULL);
   es8311_voice_mute(g_es_handle, false);
 
   // Configure LINE OUT
   Wire.beginTransmission(ES8311_ADDRESS_0);
   Wire.write(0x13);
   Wire.write(0x00);
   uint8_t wire_result = Wire.endTransmission();
   if (wire_result != 0) {
     Serial.printf("Warning: LINE OUT config failed, Wire.endTransmission() returned %d\n", wire_result);
   }
   delay(10);
 
   Serial.println("ES8311 initialized");
   return true;
 }
 
bool setupI2S() {
  const int I2S_MCK_PIN = 1;
  const int I2S_BCK_PIN = 2;
  const int I2S_LRCK_PIN = 4;
  const int I2S_DOUT_PIN = 5;
  const int I2S_DIN_PIN = 3;
  
  i2s.setPins(I2S_BCK_PIN, I2S_LRCK_PIN, I2S_DOUT_PIN, I2S_DIN_PIN, I2S_MCK_PIN);
  
  if (!i2s.begin(I2S_MODE_STD, EXAMPLE_SAMPLE_RATE, I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO, I2S_STD_SLOT_BOTH)) {
    Serial.println("I2S init failed!");
    return false;
  }

  Serial.println("I2S initialized");
  return true;
}

// -------------------------- Button Interrupt Service Routine --------------------------
// ISR must be short and fast - only set flags, no delays or complex operations
void IRAM_ATTR buttonISR() {
  // Read GPIO0 state immediately in ISR using register access
  // For ESP32-S3, GPIO0 is in GPIO.in register (bit 0)
  // GPIO.in is a uint32_t, so we can directly read it
  bool current_gpio_state = (bool)((GPIO.in >> 0) & 0x01);  // GPIO0 state: 1=HIGH(released), 0=LOW(pressed)
  
  // Only trigger if state actually changed (debounce in ISR)
  if (current_gpio_state != button_last_state) {
    button_last_state = current_gpio_state;  // Update last state
    button_interrupt_flag = true;  // Set flag to indicate interrupt occurred
    // Use xTaskGetTickCountFromISR for safe time reading in ISR
    TickType_t ticks = xTaskGetTickCountFromISR();
    button_interrupt_time = (unsigned long)(ticks * portTICK_PERIOD_MS);
    button_interrupt_state = current_gpio_state;  // Store current state
  }
}

// -------------------------- Non-blocking Button Handler --------------------------
// Process button events without blocking - called from loop()
void processButtonEvents() {
  unsigned long current_time = millis();
  
  // Check if interrupt occurred
  if (button_interrupt_flag) {
    noInterrupts();
    unsigned long interrupt_time = button_interrupt_time;
    bool current_state = button_interrupt_state;
    button_interrupt_flag = false;
    interrupts();
    
    // Debounce check
    if (current_time - button_handler.last_state_change_time >= button_handler.DEBOUNCE_TIME) {
      button_handler.last_state_change_time = current_time;
      
      if (current_state == false) {  // Button pressed
        if (button_handler.state == BUTTON_IDLE) {
          button_handler.state = BUTTON_PRESSED;
          button_handler.press_start_time = interrupt_time;
          button_handler.long_press_handled = false;
          Serial.printf("[Button] Pressed at %lu\n", interrupt_time);
        }
      } else {  // Button released
        if (button_handler.state == BUTTON_PRESSED) {
          unsigned long press_duration = interrupt_time - button_handler.press_start_time;
          
          if (press_duration >= button_handler.SHORT_PRESS_MIN && 
              press_duration < button_handler.SHORT_PRESS_MAX) {
            button_handler.state = BUTTON_SHORT_PRESS;
            Serial.printf("[Button] Short press detected: %lu ms\n", press_duration);
          } else {
            button_handler.state = BUTTON_IDLE;
            Serial.printf("[Button] Release ignored (duration: %lu ms)\n", press_duration);
          }
        } else {
          button_handler.state = BUTTON_IDLE;
        }
      }
    }
  }
  
  // Check for long press while button is still pressed
  if (button_handler.state == BUTTON_PRESSED) {
    unsigned long press_duration = current_time - button_handler.press_start_time;
    
    if (!button_handler.long_press_handled && 
        press_duration >= button_handler.LONG_PRESS_TIME) {
      button_handler.state = BUTTON_LONG_PRESS;
      button_handler.long_press_handled = true;
      Serial.printf("[Button] Long press detected: %lu ms\n", press_duration);
    }
  }
  
  // Process button events (non-blocking, immediate screen switch)
  lv_obj_t *current_screen = lv_scr_act();
  
  if (button_handler.state == BUTTON_SHORT_PRESS) {
    button_handler.state = BUTTON_IDLE;
    
    if (current_screen == screen_main) {
      // Short press on main screen: Enter TX mode
      if (loraData.module_detected) {
        Serial.println("[Button] Switching to TX mode");
        loraData.tx_mode = true;
        loraData.rx_mode = false;
        
        // Stop any existing LoRa timer
        if (lora_timer != NULL) {
          lv_timer_del(lora_timer);
          lora_timer = NULL;
        }
        
        // Delete old screen if exists
        if (screen_lora_tx != NULL) {
          lv_obj_del(screen_lora_tx);
          screen_lora_tx = NULL;
        }
        
        // Initialize and switch to TX screen immediately
        lvgl_lora_tx_ui_init();
        lv_scr_load(screen_lora_tx);
        
        // Start LoRa initialization (non-blocking)
        lora_init_handler.state = LORA_INIT_STANDBY;
        lora_init_handler.last_step_time = current_time;
        lora_init_handler.is_tx_mode = true;
      } else {
        Serial.println("[Button] LoRa module not detected, cannot enter TX mode");
      }
    } else if (current_screen == screen_lora_tx) {
      // Short press on TX screen: Return to main screen
      Serial.println("[Button] Returning to main screen from TX");
      
      // Stop LoRa timer
      if (lora_timer != NULL) {
        lv_timer_del(lora_timer);
        lora_timer = NULL;
      }
      
      // Switch immediately
      loraData.tx_mode = false;
      loraData.rx_mode = false;
      lv_scr_load(screen_main);
      
      // Set module to standby (non-blocking)
      if (loraData.module_detected) {
        e22SetStandby();
      }
      
      // Reset init handler
      lora_init_handler.state = LORA_INIT_IDLE;
    } else if (current_screen == screen_lora_rx) {
      // Short press on RX screen: Switch to TX mode
      if (loraData.module_detected) {
        Serial.println("[Button] Switching from RX to TX mode");
        loraData.tx_mode = true;
        loraData.rx_mode = false;
        
        // Stop any existing LoRa timer
        if (lora_timer != NULL) {
          lv_timer_del(lora_timer);
          lora_timer = NULL;
        }
        
        // Delete old screen if exists
        if (screen_lora_tx != NULL) {
          lv_obj_del(screen_lora_tx);
          screen_lora_tx = NULL;
        }
        
        // Initialize and switch to TX screen immediately
        lvgl_lora_tx_ui_init();
        lv_scr_load(screen_lora_tx);
        
        // Start LoRa initialization (non-blocking)
        lora_init_handler.state = LORA_INIT_STANDBY;
        lora_init_handler.last_step_time = current_time;
        lora_init_handler.is_tx_mode = true;
      }
    }
  } else if (button_handler.state == BUTTON_LONG_PRESS) {
    button_handler.state = BUTTON_IDLE;
    
    if (current_screen == screen_main) {
      // Long press on main screen: Enter RX mode
      if (loraData.module_detected) {
        Serial.println("[Button] Switching to RX mode (long press)");
        loraData.tx_mode = false;
        loraData.rx_mode = true;
        
        // Clear previous received data when switching to RX mode
        loraData.last_message = "None";
        
        // Stop any existing LoRa timer
        if (lora_timer != NULL) {
          lv_timer_del(lora_timer);
          lora_timer = NULL;
        }
        
        // Delete old screen if exists
        if (screen_lora_rx != NULL) {
          lv_obj_del(screen_lora_rx);
          screen_lora_rx = NULL;
        }
        
        // Initialize and switch to RX screen immediately
        lvgl_lora_rx_ui_init();
        lv_scr_load(screen_lora_rx);
        
        // Clear Data label display immediately
        if (label_lora_rx_count) {
          lv_label_set_text(label_lora_rx_count, "Data: --");
        }
        
        // Set force reset flag for RX mode
        force_rx_reset = true;
        
        // Start LoRa initialization for RX (non-blocking)
        lora_init_handler.state = LORA_INIT_STANDBY;
        lora_init_handler.last_step_time = current_time;
        lora_init_handler.is_tx_mode = false;
      } else {
        Serial.println("[Button] LoRa module not detected, cannot enter RX mode");
      }
    }
  }
}

// -------------------------- Startup LoRa Test Handler --------------------------
// Process startup LoRa test (TX -> RX) non-blocking
void processStartupTest() {
  if (startup_test.state == STARTUP_TEST_IDLE || 
      startup_test.state == STARTUP_TEST_COMPLETE) {
    return;
  }
  
  unsigned long current_time = millis();
  
  // Check if enough time has passed since last action
  if (current_time - startup_test.last_action_time < startup_test.ACTION_DELAY) {
    return;
  }
  
  startup_test.last_action_time = current_time;
  
  switch (startup_test.state) {
    case STARTUP_TEST_WAIT_LORA_INIT:
      // Wait for LoRa module to be initialized
      if (!startup_test.lora_initialized) {
        // Start initialization for TX mode if not started yet
        if (lora_init_handler.state == LORA_INIT_IDLE && loraData.module_detected) {
          Serial.println("[Startup] Starting LoRa TX initialization...");
          lora_init_handler.state = LORA_INIT_STANDBY;
          lora_init_handler.last_step_time = current_time;
          lora_init_handler.is_tx_mode = true;
        }
        // Check if TX initialization is complete
        if (lora_init_handler.state == LORA_INIT_COMPLETE && 
            lora_init_handler.is_tx_mode) {
          Serial.println("[Startup] LoRa TX initialization complete, sending test message...");
          startup_test.lora_initialized = true;
          startup_test.state = STARTUP_TEST_SEND_DATA;
        }
      }
      break;
      
    case STARTUP_TEST_SEND_DATA:
      // Send test message
      Serial.println("[Startup] Sending startup test message...");
      if (loraData.module_detected) {
        // Temporarily set tx_mode to allow e22SendData to work
        bool old_tx_mode = loraData.tx_mode;
        loraData.tx_mode = true;
        
        // Prepare test message
        char test_msg[] = "Startup Test";
        uint8_t msg_data[20];
        uint8_t msg_len = strlen(test_msg);
        memcpy(msg_data, test_msg, msg_len);
        
        // Send data (non-blocking, will check completion in next state)
        if (e22SendData(msg_data, msg_len)) {
          Serial.println("[Startup] Test message sent, waiting for TX completion...");
          startup_test.state = STARTUP_TEST_WAIT_TX_COMPLETE;
          startup_test.tx_completed = false;
          startup_test.start_time = current_time;  // Reset timer for TX wait
        } else {
          Serial.println("[Startup] Failed to send test message, skipping to RX mode");
          loraData.tx_mode = old_tx_mode;  // Restore
          startup_test.state = STARTUP_TEST_SWITCH_TO_RX;
        }
      } else {
        Serial.println("[Startup] LoRa module not detected, skipping test");
        startup_test.state = STARTUP_TEST_COMPLETE;
      }
      break;
      
    case STARTUP_TEST_WAIT_TX_COMPLETE:
      // Wait for TX to complete (check IRQ status)
      if (loraData.module_detected) {
        uint16_t irq_status = e22GetIRQStatus();
        if (irq_status & 0x01) {  // TX_DONE bit
          e22ClearIRQStatus(0xFFFF);
          e22SetStandby();
          Serial.println("[Startup] TX completed, switching to RX mode...");
          startup_test.tx_completed = true;
          loraData.tx_mode = false;  // Reset tx_mode
          startup_test.state = STARTUP_TEST_SWITCH_TO_RX;
        } else {
          // Check timeout (max 2 seconds for TX)
          if (current_time - startup_test.start_time > 2000) {
            Serial.println("[Startup] TX timeout, switching to RX mode...");
            loraData.tx_mode = false;  // Reset tx_mode
            startup_test.state = STARTUP_TEST_SWITCH_TO_RX;
          }
        }
      } else {
        startup_test.state = STARTUP_TEST_COMPLETE;
      }
      break;
      
    case STARTUP_TEST_SWITCH_TO_RX:
      // Switch to RX mode
      Serial.println("[Startup] Configuring LoRa for RX mode...");
      if (loraData.module_detected) {
        // Initialize LoRa for RX mode
        lora_init_handler.state = LORA_INIT_STANDBY;
        lora_init_handler.last_step_time = current_time;
        lora_init_handler.is_tx_mode = false;
        startup_test.state = STARTUP_TEST_WAIT_RX;
        startup_test.start_time = current_time;  // Reset timer for RX wait
      } else {
        startup_test.state = STARTUP_TEST_COMPLETE;
      }
      break;
      
    case STARTUP_TEST_WAIT_RX:
      // Wait for RX mode initialization and then wait 5 seconds
      if (lora_init_handler.state == LORA_INIT_COMPLETE && 
          !lora_init_handler.is_tx_mode) {
        // RX mode initialized, now wait 5 seconds
        unsigned long rx_wait_time = current_time - startup_test.start_time;
        if (rx_wait_time >= startup_test.RX_WAIT_TIME) {
          Serial.println("[Startup] RX wait complete, switching to main screen...");
          startup_test.state = STARTUP_TEST_COMPLETE;
          
          // Switch to main screen
          lv_scr_load(screen_main);
          sensor_data_callback(NULL);
          
          // Reset LoRa mode flags
          loraData.tx_mode = false;
          loraData.rx_mode = false;
          
          // Set module to standby
          if (loraData.module_detected) {
            e22SetStandby();
          }
          
          Serial.println("[Startup] Startup test complete, main screen displayed");
        } else {
          // Still waiting, check for received data
          lora_check_received();
        }
      } else if (lora_init_handler.state == LORA_INIT_IDLE) {
        // RX initialization not started yet, wait
      }
      break;
      
    default:
      startup_test.state = STARTUP_TEST_COMPLETE;
      break;
  }
}

// -------------------------- Non-blocking LoRa Initialization --------------------------
// Process LoRa module initialization step by step without blocking
void processLoRaInit() {
  if (lora_init_handler.state == LORA_INIT_IDLE || 
      lora_init_handler.state == LORA_INIT_COMPLETE) {
    return;
  }
  
  unsigned long current_time = millis();
  
  // Check if enough time has passed since last step
  if (current_time - lora_init_handler.last_step_time < lora_init_handler.STEP_DELAY) {
    return;
  }
  
  lora_init_handler.last_step_time = current_time;
  
  // Process current step
  switch (lora_init_handler.state) {
    case LORA_INIT_STANDBY:
      e22SetStandby();
      lora_init_handler.state = LORA_INIT_RF_SWITCH;
      break;
      
    case LORA_INIT_RF_SWITCH:
      e22SetRFSwitchMode(0x01);
      lora_init_handler.state = LORA_INIT_PACKET_TYPE;
      break;
      
    case LORA_INIT_PACKET_TYPE:
      e22SetPacketType(0x01);
      lora_init_handler.state = LORA_INIT_SYNC_WORD;
      break;
      
    case LORA_INIT_SYNC_WORD:
      e22SetSyncWord(0x12);
      lora_init_handler.state = LORA_INIT_FREQUENCY;
      break;
      
    case LORA_INIT_FREQUENCY:
      e22SetRFFrequency(915000000);
      delay(10);
      // Set frequency display to fixed value (915/868MHz)
      loraData.frequency = "915/868MHz";
      lora_init_handler.state = LORA_INIT_PA_CONFIG;
      break;
      
    case LORA_INIT_PA_CONFIG:
      e22SetPAConfig(0x04, 0x07, 0x00, 0x01);
      lora_init_handler.state = LORA_INIT_TX_PARAMS;
      break;
      
    case LORA_INIT_TX_PARAMS:
      e22SetTXParams(22, 0x04);
      lora_init_handler.state = LORA_INIT_MODULATION;
      break;
      
    case LORA_INIT_MODULATION:
      e22SetModulationParams(0x0C, 0x04, 0x01, 0x01);
      lora_init_handler.state = LORA_INIT_SYMB_TIMEOUT;
      break;
      
    case LORA_INIT_SYMB_TIMEOUT:
      e22SetLoRaSymbTimeout(0x00, 0x80);
      lora_init_handler.state = LORA_INIT_PACKET_PARAMS;
      break;
      
    case LORA_INIT_PACKET_PARAMS:
      e22SetPacketParams(0x00, 0x40, 0x00, 0xFF, 0x01, 0x00);
      lora_init_handler.state = LORA_INIT_BUFFER_ADDR;
      break;
      
    case LORA_INIT_BUFFER_ADDR:
      e22SetBufferBaseAddress(0x00, 0x80);
      lora_init_handler.state = LORA_INIT_CLEAR_IRQ;
      break;
      
    case LORA_INIT_CLEAR_IRQ:
      e22ClearIRQStatus(0xFFFF);
      lora_init_handler.state = LORA_INIT_DIO_IRQ;
      break;
      
    case LORA_INIT_DIO_IRQ:
      if (lora_init_handler.is_tx_mode) {
        e22ConfigDIOIRQ(0x0001, 0x0001, 0x0000, 0x0000);  // TX mode
      } else {
        e22ConfigDIOIRQ(0x0063, 0x0063, 0x0000, 0x0000);  // RX mode
      }
      lora_init_handler.state = LORA_INIT_FINAL_STANDBY;
      break;
      
    case LORA_INIT_FINAL_STANDBY:
      if (lora_init_handler.is_tx_mode) {
        e22SetStandby();
        lora_init_handler.state = LORA_INIT_COMPLETE;
        Serial.println("TX: Module fully initialized (non-blocking)");
      } else {
        e22SetRX();
        lora_init_handler.state = LORA_INIT_COMPLETE;
        Serial.println("RX: Module fully initialized (non-blocking)");
      }
      break;
      
    default:
      lora_init_handler.state = LORA_INIT_IDLE;
      break;
  }
}

// -------------------------- Forward Declarations --------------------------
void hardwareDiagnostics();  // Forward declaration
void quickHardwareCheck();    // Forward declaration

// -------------------------- Speaker/Buzzer Test Function --------------------------
// Use ES8311 and I2S interface to play beep sound (speaker connected via CON3)
// Reference implementation from 02_es8311_example.ino
void beep_speaker(int count = 3) {
  Serial.printf("Testing speaker via ES8311/I2S: %d beeps (Freq: %dHz, Dur: %dms)\n", 
                count, BEEP_FREQUENCY, BEEP_DURATION);
  
  // Step 1: Initialize ES8311 and I2S (if not already initialized)
  if (g_es_handle == NULL) {
    Serial.println("  Initializing ES8311...");
    if (!es8311_init()) {
      Serial.println("  ERROR: ES8311 initialization failed!");
      return;
    }
  }
  
  // Re-initialize I2S to ensure correct configuration
  Serial.println("  Initializing/Reinitializing I2S...");
  if (!setupI2S()) {
    Serial.println("  ERROR: I2S initialization failed!");
    return;
  }
  
  // Step 2: Configure ES8311 for playback mode (reference example program)
  if (g_es_handle != NULL) {
    Serial.println("  Configuring ES8311 for playback...");
    
    // Ensure DAC is not muted
    esp_err_t ret = es8311_voice_mute(g_es_handle, false);
    if (ret != ESP_OK) {
      Serial.printf("  WARNING: es8311_voice_mute failed: %s\n", esp_err_to_name(ret));
    } else {
      Serial.println("  DAC unmuted");
    }
    
    // Set volume (reference example program, use 70)
    int current_volume = 0;
    es8311_voice_volume_get(g_es_handle, &current_volume);
    Serial.printf("  Current volume: %d (target: %d)\n", current_volume, EXAMPLE_VOICE_VOLUME);
    if (current_volume != EXAMPLE_VOICE_VOLUME) {
      ret = es8311_voice_volume_set(g_es_handle, EXAMPLE_VOICE_VOLUME, NULL);
      if (ret != ESP_OK) {
        Serial.printf("  WARNING: es8311_voice_volume_set failed: %s\n", esp_err_to_name(ret));
      } else {
        Serial.printf("  Volume set to %d\n", EXAMPLE_VOICE_VOLUME);
      }
    }

    // Ensure output routing is LINE OUT (direct register write, reference example program)
    Wire.beginTransmission(ES8311_ADDRESS_0);
    Wire.write(0x13);  // SYSTEM_REG13
    Wire.write(0x00);  // LINE OUT
    uint8_t wire_result = Wire.endTransmission();
    if (wire_result != 0) {
      Serial.printf("  WARNING: LINE OUT config failed, Wire.endTransmission() returned %d\n", wire_result);
    } else {
      Serial.println("  LINE OUT configured");
    }
    delay(10);
    
    // Confirm PA is enabled again (important: ensure PA amplifier is enabled)
    initPAByMCP23017();
    Serial.println("  PA enabled");
    
    delay(50);  // Give configuration some time to take effect
  } else {
    Serial.println("  ERROR: ES8311 handle is NULL!");
    return;
  }
  
  // Step 3: Generate PCM audio data and play via I2S
  // Calculate required number of samples (stereo, 16-bit, 44100Hz sample rate)
  uint32_t samples_per_beep = (BEEP_DURATION * EXAMPLE_SAMPLE_RATE) / 1000;
  
  Serial.printf("  Generating %lu samples per beep (sample rate: %dHz)\n", samples_per_beep, EXAMPLE_SAMPLE_RATE);
  
  // Generate and play each beep sound
  for (int i = 0; i < count; i++) {
    Serial.printf("  Beep %d/%d (freq=%dHz, %lums)\n", i + 1, count, BEEP_FREQUENCY, BEEP_DURATION);
    
    // Generate square wave PCM data (16-bit stereo)
    int16_t *audio_buffer = (int16_t *)malloc(samples_per_beep * 2 * sizeof(int16_t));
    if (audio_buffer == NULL) {
      Serial.println("  ERROR: Failed to allocate audio buffer!");
      return;
    }
    
    // Generate square wave (left and right channels are the same)
    for (uint32_t j = 0; j < samples_per_beep; j++) {
      // Calculate current sample position in the cycle
      float phase = (float)(j * BEEP_FREQUENCY) / EXAMPLE_SAMPLE_RATE;
      phase = phase - floor(phase);  // Normalize to 0-1
      
      // Generate square wave (between 0-1)
      int16_t sample_value = (phase < 0.5) ? 16000 : -16000;  // Square wave amplitude (about 50% of maximum)
      
      // Stereo: left and right channels are the same
      audio_buffer[j * 2] = sample_value;      // Left channel
      audio_buffer[j * 2 + 1] = sample_value;  // Right channel
    }
    
    // Play audio via I2S (reference example program uses i2s.write())
    size_t bytes_to_write = samples_per_beep * 2 * sizeof(int16_t);
    size_t bytes_written = i2s.write((uint8_t *)audio_buffer, bytes_to_write);
    Serial.printf("    Wrote %lu bytes to I2S (expected: %lu bytes)\n", bytes_written, bytes_to_write);
    
    // Free buffer
    free(audio_buffer);
    
    // If not the last beep, add silence interval
    if (i < count - 1) {
      // Generate silence data
      uint32_t silence_samples = (BEEP_PAUSE * EXAMPLE_SAMPLE_RATE) / 1000;
      int16_t *silence_buffer = (int16_t *)calloc(silence_samples * 2, sizeof(int16_t));
      if (silence_buffer != NULL) {
        i2s.write((uint8_t *)silence_buffer, silence_samples * 2 * sizeof(int16_t));
        free(silence_buffer);
      }
      delay(50);  // Additional delay to ensure playback completes
    }
  }
  
  // Wait for I2S buffer to clear
  delay(200);
  Serial.println("  Speaker test completed successfully");
}
 
 
 // -------------------------- Quectel L76K GPS Functions --------------------------
 void initL76K() {
   GPS_SERIAL.println("$PQBAUD,9600*25");
   delay(100);
   GPS_SERIAL.println("$PQCUSP,0,1,1,0,0,0,0,0,0,0,0,0*41");
   delay(100);
   GPS_SERIAL.println("$PQCUPDATE,1*21");
   delay(100);
   Serial.println("L76K GPS initialized");
 }
 
// Convert NMEA format (DDMM.MMMMM) to decimal degrees (DD.DDDDDD)
float nmeaToDecimal(String nmeaStr) {
  if (nmeaStr.length() == 0) return 0.0;
  
  float nmeaValue = nmeaStr.toFloat();
  int degrees = (int)(nmeaValue / 100);  // Extract degrees
  float minutes = nmeaValue - (degrees * 100);  // Extract minutes
  float decimal = degrees + (minutes / 60.0);  // Convert to decimal degrees
  
  return decimal;
}

void parseGNGGA(String nmeaFrame) {
  int commaIndex[15];
  int idx = 0;
  commaIndex[idx++] = nmeaFrame.indexOf(',');
  
  while (commaIndex[idx-1] != -1 && idx < 15) {
    commaIndex[idx] = nmeaFrame.indexOf(',', commaIndex[idx-1] + 1);
    idx++;
  }

  if (idx < 10) return;

  gpsData.utcTime = nmeaFrame.substring(commaIndex[0]+1, commaIndex[1]);
  gpsData.latitude = nmeaFrame.substring(commaIndex[1]+1, commaIndex[2]);
  gpsData.latDir = nmeaFrame.substring(commaIndex[2]+1, commaIndex[3]);
  gpsData.longitude = nmeaFrame.substring(commaIndex[3]+1, commaIndex[4]);
  gpsData.lonDir = nmeaFrame.substring(commaIndex[4]+1, commaIndex[5]);
  gpsData.fixQuality = nmeaFrame.substring(commaIndex[5]+1, commaIndex[6]).toInt();
  gpsData.satCount = nmeaFrame.substring(commaIndex[6]+1, commaIndex[7]).toInt();
  gpsData.hdop = nmeaFrame.substring(commaIndex[7]+1, commaIndex[8]).toFloat();
  gpsData.altitude = nmeaFrame.substring(commaIndex[8]+1, commaIndex[9]).toFloat();
  gpsData.status = (gpsData.fixQuality > 0) ? "Valid" : "Invalid";
}
 
 void parseGPRMC(String nmeaFrame) {
   int commaIndex[12];
   int idx = 0;
   commaIndex[idx++] = nmeaFrame.indexOf(',');
   
   while (commaIndex[idx-1] != -1 && idx < 12) {
     commaIndex[idx] = nmeaFrame.indexOf(',', commaIndex[idx-1] + 1);
     idx++;
   }
 
   if (idx < 10) return;
   String rmcStatus = nmeaFrame.substring(commaIndex[1]+1, commaIndex[2]);
   if (rmcStatus == "A") gpsData.status = "Valid";
   else if (rmcStatus == "V") gpsData.status = "Invalid";
   
   // Parse UTC time (HHMMSS)
   if (commaIndex[0] + 1 < commaIndex[1]) {
     gpsData.utcTime = nmeaFrame.substring(commaIndex[0]+1, commaIndex[1]);
   }
   
   // Parse UTC date (DDMMYY)
   if (commaIndex[8] + 1 < commaIndex[9]) {
     gpsData.utcDate = nmeaFrame.substring(commaIndex[8]+1, commaIndex[9]);
   }
 }
 
 bool quectel_l76k_init() {
   Serial.println("Initializing Quectel L76K...");
   GPS_SERIAL.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
   delay(1000);
   initL76K();
   // Initialize GPS data with default values
   gpsData.status = "Invalid";
   gpsData.fixQuality = 0;
   gpsData.satCount = 0;
   gpsData.timeSynced = false;
   Serial.println("Quectel L76K initialized");
   return true;
 }
 
 // -------------------------- RTC Time Sync Functions --------------------------
 void sync_rtc_from_gps() {
   // Check if GPS has valid time data
   if (gpsData.status != "Valid" || gpsData.fixQuality == 0) return;
   if (gpsData.utcTime.length() < 6 || gpsData.utcDate.length() < 6) return;
   
   // Parse UTC time: HHMMSS
   int hour = gpsData.utcTime.substring(0, 2).toInt();
   int minute = gpsData.utcTime.substring(2, 4).toInt();
   int second = gpsData.utcTime.substring(4, 6).toInt();
   
   // Parse UTC date: DDMMYY
   int day = gpsData.utcDate.substring(0, 2).toInt();
   int month = gpsData.utcDate.substring(2, 4).toInt();
   int year = 2000 + gpsData.utcDate.substring(4, 6).toInt();
   
   // Convert UTC to local time (assuming UTC+8, adjust as needed)
   int timezone_offset = 8;  // UTC+8, adjust this value for your timezone
   int old_hour = hour;
   hour = (hour + timezone_offset) % 24;
   if (old_hour + timezone_offset >= 24) {
     day++;
     // Handle month/year rollover
     int days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
     // Check for leap year
     if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) {
       days_in_month[1] = 29;
     }
     if (day > days_in_month[month - 1]) {
       day = 1;
       month++;
       if (month > 12) {
         month = 1;
         year++;
       }
     }
   }
   
   // Update RTC with GPS time (only if not already synced, or if we want to allow re-sync)
   if (!gpsData.timeSynced) {
     rtc.setDateTime(year, month, day, hour, minute, second);
     gpsData.timeSynced = true;
     Serial.printf("RTC updated from GPS: %04d-%02d-%02d %02d:%02d:%02d\n", year, month, day, hour, minute, second);
     Serial.println("RTC will now continue counting from GPS time");
   }
 }
 
 void sync_rtc_from_ntp() {
   if (gpsData.timeSynced) return;  // Already synced from GPS
   
   // Check if WiFi is connected (not just AP mode)
   if (WiFi.status() != WL_CONNECTED) {
     Serial.println("WiFi not connected, cannot sync from NTP");
     return;
   }
   
   // Configure timezone (UTC+8)
   configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov");
   
   // Wait for time sync (max 10 seconds)
   int retry = 0;
   struct tm timeinfo;
   while (!getLocalTime(&timeinfo) && retry < 20) {
     delay(500);
     retry++;
   }
   
   if (retry < 20) {
     // Set RTC
     int hour = timeinfo.tm_hour;
     int day = timeinfo.tm_mday;
     int month = timeinfo.tm_mon + 1;
     int year = timeinfo.tm_year + 1900;
     
     rtc.setDateTime(year, month, day, hour, timeinfo.tm_min, timeinfo.tm_sec);
     gpsData.timeSynced = true;
     Serial.printf("RTC synced from NTP: %04d-%02d-%02d %02d:%02d:%02d\n",
                   year, month, day, hour, timeinfo.tm_min, timeinfo.tm_sec);
   } else {
     Serial.println("NTP sync failed");
   }
 }
 
 void read_gps_data() {
   if (GPS_SERIAL.available() > 0) {
     String nmeaFrame = GPS_SERIAL.readStringUntil('\n');
     nmeaFrame.trim();
     
     if (nmeaFrame.startsWith("$GNGGA")) {
       parseGNGGA(nmeaFrame);
     }
     else if (nmeaFrame.startsWith("$GPRMC")) {
       parseGPRMC(nmeaFrame);
     }
   }
 }
 
void read_quectel_l76k(char *time_buf, char *lat_buf, char *lon_buf, char *sat_buf, char *alt_buf) {
  read_gps_data();
  
  if (gpsData.utcTime.length() > 0) {
    sprintf(time_buf, "Time: %s", gpsData.utcTime.c_str());
  } else {
    strcpy(time_buf, "Time: --:--:--");
  }
  
  if (gpsData.latitude.length() > 0) {
    // Convert NMEA format to decimal degrees for display
    float latDecimal = nmeaToDecimal(gpsData.latitude);
    sprintf(lat_buf, "Lat: %.6f %s", latDecimal, gpsData.latDir.c_str());
  } else {
    strcpy(lat_buf, "Lat: --");
  }
  
  if (gpsData.longitude.length() > 0) {
    // Convert NMEA format to decimal degrees for display
    float lonDecimal = nmeaToDecimal(gpsData.longitude);
    sprintf(lon_buf, "Lon: %.6f %s", lonDecimal, gpsData.lonDir.c_str());
  } else {
    strcpy(lon_buf, "Lon: --");
  }
  
  sprintf(sat_buf, "Sat: %d", gpsData.satCount);
  
  if (gpsData.fixQuality > 0 && gpsData.altitude != 0.0) {
    sprintf(alt_buf, "Alt: %.2f m", gpsData.altitude);
  } else {
    strcpy(alt_buf, "Alt: -- m");
  }
}
 
 // -------------------------- LoRa SPI Helper Functions --------------------------
 // EXIO pin control via MCP23017
 // EXIO pins 0-7 map to MCP23017 GPA bits 0-7, EXIO pins 8-15 map to GPB bits 0-7

 // Direct I2C access functions for MCP23017 (fallback if library fails)
 // MCP23017 register map (BANK=0 mode, according to ESP-IDF driver):
 //   IODIRA = 0x00, IODIRB = 0x01 (direction: 0=output, 1=input)
 //   GPIOA = 0x12, GPIOB = 0x13 (input ports, read-only)
 //   OLATA = 0x14, OLATB = 0x15 (output latches, write output values)
 static bool mcp23017_direct_write(uint8_t reg, uint8_t value) {
   Wire.beginTransmission(mcp23017_address);
   Wire.write(reg);
   Wire.write(value);
   return (Wire.endTransmission() == 0);
 }

 static uint8_t mcp23017_direct_read(uint8_t reg) {
   Wire.beginTransmission(mcp23017_address);
   Wire.write(reg);
   if (Wire.endTransmission() != 0) {
     return 0xFF;  // Error
   }
   Wire.requestFrom(mcp23017_address, (uint8_t)1);
   if (Wire.available()) {
     return Wire.read();
   }
   return 0xFF;  // Error
 }
 
 static void lora_exio_write(uint8_t exio_pin, uint8_t value) {
   if (mcp23017_address == 0x00) {
     // MCP23017 not found, cannot control EXIO pins
     static bool warned_exio = false;
     if (!warned_exio && (exio_pin == LORA_EXIO_DIO2)) {
       Serial.printf("[WARNING] MCP23017 not found! Cannot control DIO2 (EXIO%d). TXEN/RXEN switching will not work!\n", exio_pin);
       Serial.println("  This will prevent proper RX/TX mode switching. Please check MCP23017 hardware connection.");
       warned_exio = true;
     }
     return;  // Silently fail
   } else {
     // Use MCP23017 library (digitalWrite)
     mcp.digitalWrite(exio_pin, value);
   }
 }
 
 static uint8_t lora_exio_read(uint8_t exio_pin) {
   if (mcp23017_address == 0x00) {
     // MCP23017 not found, return default value (HIGH for BUSY due to pull-up)
     return HIGH;  // Default to HIGH (matches BUSY pin pull-up)
   } else {
     // Use MCP23017 library (digitalRead)
     return mcp.digitalRead(exio_pin);
   }
 }
 
 static void lora_exio_pinMode(uint8_t exio_pin, uint8_t mode) {
   if (mcp23017_address == 0x00) {
     // MCP23017 not found, cannot configure EXIO pins
     return;  // Silently fail
   } else {
     // Use MCP23017 library (pinMode)
     mcp.pinMode(exio_pin, mode);
   }
 }
 
 static void lora_setNSS(uint8_t value) {
   digitalWrite(E22_NSS_PIN, value);
 }
 
 // E22 BUSY check flag (skip BUSY check if SPI works but BUSY logic fails)
 static bool skip_busy_check = false;
 
 // BUSY pin logic flag: true => HIGH means ready, false => LOW means ready
 // Auto-detected on first call
 static bool busy_logic_high_ready = false;
 
 // Wait for E22 BUSY pin to be ready 
 bool waitE22Ready(uint32_t timeout_ms = 800, bool check_busy = true) {
   if (!check_busy || skip_busy_check) {
     // Skip BUSY check, just wait a short time
     delayMicroseconds(10);
     return true;
   }
   
   unsigned long start = millis();
   uint32_t check_count = 0;
   uint8_t initial_busy = lora_exio_read(LORA_EXIO_BUSY);
   
   // Auto-detect logic on first call: if initial state is HIGH, assume HIGH=ready
   // This will be set once and reused for all subsequent calls
   static bool logic_detected = false;
   static bool first_call = true;
   if (!logic_detected) {
     busy_logic_high_ready = (initial_busy == 1);  
     logic_detected = true;
     Serial.printf("BUSY logic auto-detected: %s (initial state: %d)\n", 
                   busy_logic_high_ready ? "HIGH=ready" : "LOW=ready", initial_busy);
   }
   
   // Only print on first call after detection, not every time
   bool should_print = first_call;
   first_call = false;
   
   while (millis() - start < timeout_ms) {
     // Read BUSY pin from MCP23017
     uint8_t busy_state = lora_exio_read(LORA_EXIO_BUSY);
     
     if (should_print && check_count == 0) {
       Serial.printf("Initial BUSY state: %d (logic: %s)\n", busy_state, 
                     busy_logic_high_ready ? "HIGH=ready" : "LOW=ready");
     }
     
     // Check based on detected logic 
     bool is_ready = busy_logic_high_ready ? (busy_state == 1) : (busy_state == 0);
     
     if (is_ready) {
       return true;
     }
     
     check_count++;
     if (check_count % 100 == 0 && should_print) {
       Serial.printf("Waiting for BUSY... (%lu ms, state=%d)\n", millis() - start, busy_state);
     }
     delay(1);
   }
   
   if (should_print) {
     Serial.printf("BUSY timeout! Final state: %d\n", lora_exio_read(LORA_EXIO_BUSY));
   }
   // If BUSY check times out but SPI works, we'll skip BUSY check in future
   return false;  // Timeout
 }
 
 // E22 SPI transaction (wait for ready, select, transfer, deselect) 
 bool e22SPITransaction(uint8_t *tx_data, uint8_t tx_len, uint8_t *rx_data, uint8_t rx_len) {
   // Use global flag to determine if we should check BUSY
   // If SPI communication works but BUSY check fails, skip BUSY check
   if (!skip_busy_check) {
     waitE22Ready(50, true);  // Short timeout
   } else {
     delayMicroseconds(10);  // Small delay instead of BUSY check
   }
 
   // NSS=HIGH
   digitalWrite(E22_NSS_PIN, HIGH);
   delayMicroseconds(10);
 
 
   digitalWrite(E22_NSS_PIN, LOW);
   delayMicroseconds(10);
 
   // Send data and receive response simultaneously
 
   for (uint8_t i = 0; i < tx_len; i++) {
     uint8_t rx_byte = SPI.transfer(tx_data[i]);
     if (rx_data && i < rx_len) {
       rx_data[i] = rx_byte;
     }
  
     if (i == 0) {
       delayMicroseconds(5);
     }
   }
 
   // Read remaining response bytes (if any)
   if (rx_data && rx_len > tx_len) {
     for (uint8_t i = tx_len; i < rx_len; i++) {
       rx_data[i] = SPI.transfer(0x00);
     }
   }
 
   delayMicroseconds(5);
   
   digitalWrite(E22_NSS_PIN, HIGH);
 
   return true;
 }
 
 // Legacy function for compatibility
 static bool lora_waitForBusy(uint32_t timeout_ms = 1000) {
   return waitE22Ready(timeout_ms, true);
 }
 
 static void lora_spi_writeCommand(uint8_t cmd, uint8_t* data = NULL, size_t len = 0) {
   // Try to wait for BUSY, but don't block if it stays HIGH
   // (BUSY pin reading may be unreliable, but chip might still work)
   lora_waitForBusy(50);  // Short timeout
   
   lora_setNSS(LOW);
   delayMicroseconds(1);  // Small delay for NSS setup
   SPI.transfer(cmd);
   if (data && len > 0) {
     for (size_t i = 0; i < len; i++) {
       SPI.transfer(data[i]);
     }
   }
   lora_setNSS(HIGH);
   delayMicroseconds(1);  // Small delay for NSS release
   
   // Wait a bit for command processing (even if BUSY doesn't change)
   delayMicroseconds(100);
   lora_waitForBusy(50);  // Short timeout, don't block
 }
 
 static void lora_spi_readCommand(uint8_t cmd, uint8_t* data, size_t len) {
   // Try to wait for BUSY, but don't block if it stays HIGH
   lora_waitForBusy(50);  // Short timeout
   
   lora_setNSS(LOW);
   delayMicroseconds(1);  // Small delay for NSS setup
   SPI.transfer(cmd);
   SPI.transfer(0x00); // Dummy byte for read
   for (size_t i = 0; i < len; i++) {
     data[i] = SPI.transfer(0x00);
   }
   lora_setNSS(HIGH);
   delayMicroseconds(1);  // Small delay for NSS release
 }
 
 // SX1262/SX1268 register definitions
 #define SX126X_CMD_SET_STANDBY          0x80
 #define SX126X_CMD_SET_SLEEP            0x84
 #define SX126X_CMD_SET_TX               0x83
 #define SX126X_CMD_SET_RX               0x82
 #define SX126X_CMD_SET_PACKET_TYPE      0x8A
#define SX126X_CMD_SET_RF_FREQUENCY     0x86
#define SX126X_CMD_GET_RF_FREQUENCY     0x96
#define SX126X_CMD_SET_MODULATION_PARAMS 0x8B
 #define SX126X_CMD_SET_PACKET_PARAMS    0x8C
 #define SX126X_CMD_SET_TX_PARAMS        0x8E
 #define SX126X_CMD_SET_BUFFER_BASE_ADDRESS 0x8F
 #define SX126X_CMD_WRITE_BUFFER         0x0E
 #define SX126X_CMD_READ_BUFFER          0x1E
 #define SX126X_CMD_GET_STATUS           0xC0
 #define SX126X_CMD_GET_IRQ_STATUS       0x12
 #define SX126X_CMD_CLEAR_IRQ_STATUS     0x02
 #define SX126X_CMD_GET_RX_BUFFER_STATUS 0x13
 #define SX126X_CMD_SET_SYNC_WORD        0x8D
 
 // E22 Command: Get Status
 uint8_t e22GetStatus() {
   // NSS setup time
   digitalWrite(E22_NSS_PIN, HIGH);
   delayMicroseconds(50);
 
   digitalWrite(E22_NSS_PIN, LOW);
   delayMicroseconds(10);  // CS setup time
 
   uint8_t status_byte = SPI.transfer(0xC0);
   uint8_t dummy_response = SPI.transfer(0x00);
 
   delayMicroseconds(10);  // CS hold time
   digitalWrite(E22_NSS_PIN, HIGH);
   delayMicroseconds(50);  // NSS recovery time
 
   uint8_t chip_mode = (status_byte >> 4) & 0x0F;
   if (chip_mode <= 3) {
     return status_byte;
   }
   return 0xFF;
 }
 
 #define SX126X_IRQ_TX_DONE              0x01
 #define SX126X_IRQ_RX_DONE              0x02
 #define SX126X_IRQ_CRC_ERROR            0x40
 #define SX126X_CMD_CFG_DIOIRQ           0x08
 #define SX126X_CMD_GET_PACKET_STATUS    0x14
 #define SX126X_CMD_SET_RFSWITCHMODE     0x9D
 
 // -------------------------- LoRa RX Helper Functions (from 16_lvgl_LoRaTest.ino) --------------------------
 // E22 Command: Read Register 
 uint8_t e22ReadRegister(uint8_t reg) {
   uint8_t tx_data[3] = {0x1D, reg, 0x00};  // Read command + register address + dummy
   uint8_t rx_data[3] = {0};
   
   if (e22SPITransaction(tx_data, 3, rx_data, 3)) {
     return rx_data[2];  
   }
   return 0;
 }
 
 // E22 Command: Write Register
 bool e22WriteRegister(uint8_t reg, uint8_t value) {
   uint8_t tx_data[3] = {0x1C, reg, value};  // Write command (0x1C) + register + value
   return e22SPITransaction(tx_data, 3, NULL, 0);
 }
 uint16_t e22GetIRQStatus() {
   uint8_t tx_data[2] = {0x12, 0x00};  // GET_IRQSTATUS command + dummy
   uint8_t rx_data[4] = {0};
 
   if (e22SPITransaction(tx_data, 2, rx_data, 4)) {
     uint16_t irq_status = ((uint16_t)rx_data[2] << 8) | rx_data[3];
     return irq_status;
   }
   return 0;
 }
 
 // E22 Command: Clear IRQ Status 
 void e22ClearIRQStatus(uint16_t irq_mask) {
   uint8_t tx_data[3] = {0x02, (uint8_t)((irq_mask >> 8) & 0xFF), (uint8_t)(irq_mask & 0xFF)};  // RADIO_CLR_IRQSTATUS
   e22SPITransaction(tx_data, 3, NULL, 0);
 }
 
 // E22 Command: Set Packet Type 
 bool e22SetPacketType(uint8_t packet_type) {
   uint8_t tx_data[2] = {0x8A, packet_type};  // RADIO_SET_PACKETTYPE (0x01 = LoRa)
   return e22SPITransaction(tx_data, 2, NULL, 0);
 }
 
 // E22 Command: Set Sync Word 
 bool e22SetSyncWord(uint8_t sync_word) {
   uint8_t tx_data[3] = {0x09, sync_word, 0x00};  // RADIO_SET_SYNCWORD
   return e22SPITransaction(tx_data, 3, NULL, 0);
 }
 
// E22 Command: Set RF Frequency 
bool e22SetRFFrequency(uint32_t frequency_hz) {
  // Calculate frequency register value: freq = (frequency_hz / 32MHz) * 2^25
  uint32_t freq_raw = (uint32_t)((double)frequency_hz / 32000000.0 * 33554432.0);
  uint8_t tx_data[5] = {0x86, 
                        (uint8_t)((freq_raw >> 24) & 0xFF),
                        (uint8_t)((freq_raw >> 16) & 0xFF),
                        (uint8_t)((freq_raw >> 8) & 0xFF),
                        (uint8_t)(freq_raw & 0xFF)};
  return e22SPITransaction(tx_data, 5, NULL, 0);
}

// E22 Command: Get RF Frequency (read current frequency from module)
uint32_t e22GetRFFrequency() {
  uint8_t tx_data[2] = {0x96, 0x00};  // GET_RF_FREQUENCY command + dummy
  uint8_t rx_data[6] = {0};
  
  if (e22SPITransaction(tx_data, 2, rx_data, 6)) {
    // Frequency is returned as 4 bytes (MSB first)
    uint32_t freq_raw = ((uint32_t)rx_data[2] << 24) |
                         ((uint32_t)rx_data[3] << 16) |
                         ((uint32_t)rx_data[4] << 8) |
                         (uint32_t)rx_data[5];
    
    // Convert back to Hz: frequency_hz = (freq_raw / 2^25) * 32MHz
    uint32_t frequency_hz = (uint32_t)((double)freq_raw / 33554432.0 * 32000000.0);
    return frequency_hz;
  }
  return 0;  // Return 0 if read failed
}
 
 // E22 Command: Set PA Config 
 bool e22SetPAConfig(uint8_t pa_duty_cycle, uint8_t hp_max, uint8_t device_sel, uint8_t pa_lut) {
   uint8_t tx_data[5] = {0x95, pa_duty_cycle, hp_max, device_sel, pa_lut};  // RADIO_SET_PACONFIG
   return e22SPITransaction(tx_data, 5, NULL, 0);
 }
 
 // E22 Command: Set TX Parameters 
 bool e22SetTXParams(uint8_t power, uint8_t ramp_time) {
   uint8_t tx_data[3] = {0x8E, power, ramp_time};  // RADIO_SET_TXPARAMS
   return e22SPITransaction(tx_data, 3, NULL, 0);
 }
 
 // E22 Command: Set Modulation Parameters
 bool e22SetModulationParams(uint8_t spreading_factor, uint8_t bandwidth, 
                             uint8_t coding_rate, uint8_t ldro) {
   uint8_t tx_data[5] = {0x8B, spreading_factor, bandwidth, coding_rate, ldro};  // RADIO_SET_MODULATIONPARAMS
   return e22SPITransaction(tx_data, 5, NULL, 0);
 }
 
 // E22 Command: Set LoRa Symbol Timeout 
 bool e22SetLoRaSymbTimeout(uint8_t timeout_msb, uint8_t timeout_lsb) {
   uint8_t tx_data[3] = {0xA0, timeout_msb, timeout_lsb};  // RADIO_SET_LORASYMBTIMEOUT
   return e22SPITransaction(tx_data, 3, NULL, 0);
 }
 
 // E22 Command: Set Buffer Base Address 
 bool e22SetBufferBaseAddress(uint8_t tx_base_addr, uint8_t rx_base_addr) {
   uint8_t tx_data[3] = {0x8F, tx_base_addr, rx_base_addr};  // RADIO_SET_BUFFERBASEADDRESS
   return e22SPITransaction(tx_data, 3, NULL, 0);
 }
 
 // E22 Command: Configure DIO and IRQ 
 bool e22ConfigDIOIRQ(uint16_t irq_mask, uint16_t dio1_mask, uint16_t dio2_mask, uint16_t dio3_mask) {
   uint8_t tx_data[9] = {0x08,  // RADIO_CFG_DIOIRQ
                         (uint8_t)((irq_mask >> 8) & 0xFF), (uint8_t)(irq_mask & 0xFF),
                         (uint8_t)((dio1_mask >> 8) & 0xFF), (uint8_t)(dio1_mask & 0xFF),
                         (uint8_t)((dio2_mask >> 8) & 0xFF), (uint8_t)(dio2_mask & 0xFF),
                         (uint8_t)((dio3_mask >> 8) & 0xFF), (uint8_t)(dio3_mask & 0xFF)};
   return e22SPITransaction(tx_data, 9, NULL, 0);
 }
 
 // E22 Command: Get RX Buffer Status
 bool e22GetRXBufferStatus(uint8_t *payload_length, uint8_t *rx_start_ptr) {
   uint8_t tx_data[2] = {0x13, 0x00};  // RADIO_GET_RXBUFFERSTATUS command + dummy
   uint8_t rx_data[4] = {0};
   
   if (e22SPITransaction(tx_data, 2, rx_data, 4)) {
     // Response format: [status, dummy, payload_length, rx_start_ptr]
     *payload_length = rx_data[2];
     *rx_start_ptr = rx_data[3];
     return true;
   }
   return false;
 }
 
 // E22 Command: Read Buffer (from specified address) 
 bool e22ReadBuffer(uint8_t offset, uint8_t *data, uint8_t len) {
   uint8_t tx_data[2] = {0x1E, offset};  // RADIO_READ_BUFFER command + offset
   uint8_t rx_data[3 + len];  // status + dummy1 + dummy2 + data
   
   if (e22SPITransaction(tx_data, 2, rx_data, 3 + len)) {
     // [status, dummy1, dummy2, data...]
     for (uint8_t i = 0; i < len; i++) {
       data[i] = rx_data[3 + i];
     }
     return true;
   }
   return false;
 }
 
 // E22 Command: Get Packet Status (for RSSI and SNR) 
 bool e22GetPacketStatus(int16_t *rssi, float *snr) {
   uint8_t tx_data[2] = {0x14, 0x00};  // RADIO_GET_PACKETSTATUS command + dummy
   uint8_t rx_data[5] = {0};
   
   if (e22SPITransaction(tx_data, 2, rx_data, 5)) {
     // Response format: [status, dummy, rssi_pkt, rssi_sync, snr]
     // RSSI calculation: -(rssi_pkt >> 1) dBm (rssi_pkt is in rx_data[2])
     int16_t rssi_pkt = -(int16_t)(rx_data[2] >> 1);
     *rssi = rssi_pkt;
     
     // SNR calculation: snr_raw / 4.0 (SX126x SNR unit is 0.25dB, snr is in rx_data[4])
     int8_t snr_raw = (int8_t)rx_data[4];
     *snr = snr_raw / 4.0f;
     
     return true;
   }
   return false;
 }
 
 // E22 Command: Set to Standby mode 
 bool e22SetStandby() {
   uint8_t current_status = e22GetStatus();
   uint8_t chip_mode = (current_status >> 4) & 0x0F;
   
   if (chip_mode == 0x00) {  // Sleep mode
    
     Serial.println("RX: Sending Standby command via direct SPI (Sleep mode)");
 
     digitalWrite(E22_NSS_PIN, HIGH);
     delayMicroseconds(50); 
 
 
     digitalWrite(E22_NSS_PIN, LOW);
     delayMicroseconds(10);  // CS setup time
 
 
     SPI.transfer(0x80);  // Standby command
     SPI.transfer(0x00);  // STDBY_RC mode parameter
     delayMicroseconds(10);  
 
 
     digitalWrite(E22_NSS_PIN, HIGH);
     delayMicroseconds(50);  // CS hold time
 
     delay(50);  
 
     current_status = e22GetStatus();
     chip_mode = (current_status >> 4) & 0x0F;
     if (chip_mode == 0x01) {
       Serial.println("RX: Module awakened from Sleep to Standby via direct SPI");
       return true;
     } else {
       Serial.printf("RX: WARNING - Standby command sent but module still in mode %d\n", chip_mode);
       return true;  
     }
   } else {
     uint8_t tx_data[1] = {0x80};  // Standby command (0x80 = STDBY_RC)
     bool result = e22SPITransaction(tx_data, 1, NULL, 0);
     delay(10);
     return result;
   }
 }
 
 // E22 Command: Write Buffer (to specified address)
 bool e22WriteBuffer(uint8_t offset, uint8_t *data, uint8_t len) {
   uint8_t tx_buf[2 + len];
   tx_buf[0] = 0x0E;  // RADIO_WRITE_BUFFER command
   tx_buf[1] = offset;  // Buffer offset
   memcpy(&tx_buf[2], data, len);
   
   return e22SPITransaction(tx_buf, 2 + len, NULL, 0);
 }
 
 // E22 Command: Set Packet Parameters
 bool e22SetPacketParams(uint8_t preamble_len_msb, uint8_t preamble_len_lsb, 
                         uint8_t header_type, uint8_t payload_len, 
                         uint8_t crc_type, uint8_t invert_iq) {
   uint8_t tx_data[7] = {0x8C, preamble_len_msb, preamble_len_lsb, header_type, 
                         payload_len, crc_type, invert_iq};  // RADIO_SET_PACKETPARAMS
   return e22SPITransaction(tx_data, 7, NULL, 0);
 }
 
 // E22 Command: Set RF Switch Mode
 bool e22SetRFSwitchMode(uint8_t enable) {
   uint8_t tx_data[2] = {0x9D, enable};  // RADIO_SET_RFSWITCHMODE
   return e22SPITransaction(tx_data, 2, NULL, 0);
 }
 
 bool e22SetRX() {
   uint8_t current_status = e22GetStatus();
   uint8_t chip_mode = (current_status >> 4) & 0x0F;
   Serial.printf("RX: Current mode before Standby: %d (0x0=Sleep, 0x1=Standby, 0x2=TX, 0x3=RX)\n", chip_mode);
   
   if (chip_mode > 3) {
     Serial.println("RX: Invalid status detected, performing hardware reset before RX setup...");
     lora_exio_write(LORA_EXIO_NRST, LOW);
     delay(100);
     lora_exio_write(LORA_EXIO_NRST, HIGH);
     delay(200);  
     
     for (int i = 0; i < 20; i++) {
       delay(10);
       current_status = e22GetStatus();
       chip_mode = (current_status >> 4) & 0x0F;
       if (chip_mode <= 3) {
         Serial.printf("RX: Module ready after reset (mode=%d)\n", chip_mode);
         break;
       }
     }
     
     if (chip_mode > 3 && loraModuleState.initialized) {
       Serial.println("RX: Re-initializing module after reset...");
       bool old_skip_busy = skip_busy_check;
       skip_busy_check = true;
       
       e22SetRFSwitchMode(0x01);
       delay(50);
       e22SetStandby();
       delay(100);
       e22SetPacketType(0x01);
       delay(50);
       e22SetSyncWord(0x12);
       delay(50);
       e22SetRFFrequency(915000000);
       delay(50);
       e22SetPAConfig(0x04, 0x07, 0x00, 0x01);
       delay(50);
       e22SetTXParams(22, 0x04);
       delay(50);
       e22SetModulationParams(0x0C, 0x04, 0x01, 0x01);
       delay(50);
       e22SetLoRaSymbTimeout(0x00, 0x80);
       delay(50);
       e22SetPacketParams(0x00, 0x40, 0x00, 0xFF, 0x01, 0x00);
       delay(50);
       e22SetBufferBaseAddress(0x00, 0x80);
       delay(50);
       
       skip_busy_check = old_skip_busy;
       
       current_status = e22GetStatus();
       chip_mode = (current_status >> 4) & 0x0F;
       Serial.printf("RX: Module re-initialized, status=0x%02X (mode=%d)\n", current_status, chip_mode);
     }
   }
   
 
   int standby_attempts = 0;
   if (chip_mode == 0x00) {  // Sleep mode
     Serial.println("RX: Module in Sleep mode, attempting to wake up...");
     for (int i = 0; i < 3; i++) {
       e22SetStandby();
       delay(50); 
       current_status = e22GetStatus();
       chip_mode = (current_status >> 4) & 0x0F;
       if (chip_mode == 0x01) {  // Standby mode
         Serial.printf("RX: Module awakened to Standby mode (attempt %d)\n", i + 1);
         break;
       }
       standby_attempts++;
     }
     
     current_status = e22GetStatus();
     chip_mode = (current_status >> 4) & 0x0F;
     if (chip_mode == 0x00) {
       Serial.println("RX: Standby command failed, performing hardware reset...");
       lora_exio_write(LORA_EXIO_NRST, LOW);
       delay(50);
       lora_exio_write(LORA_EXIO_NRST, HIGH);
       delay(100); 
       
 
       Serial.println("RX: Waiting for module to be ready after reset...");
       for (int i = 0; i < 20; i++) {
         delay(10);
         current_status = e22GetStatus();
         chip_mode = (current_status >> 4) & 0x0F;
         if (chip_mode != 0x00) {  
           Serial.printf("RX: Module ready after reset (mode=%d)\n", chip_mode);
           break;
         }
       }
       
 
       if (loraModuleState.initialized) {
         Serial.println("RX: Re-configuring module after reset...");
         delay(50);
         
 
         bool old_skip_busy = skip_busy_check;
         skip_busy_check = true;
         
         e22SetRFSwitchMode(0x01);  // DIO2 as RF switch
         delay(20);
         e22SetStandby();
         delay(20);
         
         current_status = e22GetStatus();
         chip_mode = (current_status >> 4) & 0x0F;
         if (chip_mode != 0x01) {
           Serial.printf("RX: WARNING - Module not in Standby after reset config! Status=0x%02X (mode=%d)\n", 
                         current_status, chip_mode);
         }
         
         e22SetPacketType(0x01);  // LoRa
         delay(20);
         e22SetSyncWord(0x12);
         delay(20);
         e22SetRFFrequency(915000000);
         delay(20);
         e22SetPAConfig(0x04, 0x07, 0x00, 0x01);  // PA config
         delay(20);
         e22SetTXParams(22, 0x04);  // TX power
         delay(20);
         e22SetModulationParams(0x0C, 0x04, 0x01, 0x01);  // SF12, BW125, CR4/5
         delay(20);
         e22SetLoRaSymbTimeout(0x00, 0x80);
         delay(20);
         e22SetPacketParams(0x00, 0x40, 0x00, 0xFF, 0x01, 0x00);
         delay(20);
         e22SetBufferBaseAddress(0x00, 0x80);
         delay(20);
         
         skip_busy_check = old_skip_busy;
         
         current_status = e22GetStatus();
         chip_mode = (current_status >> 4) & 0x0F;
         Serial.printf("RX: Module re-configured after reset, final status=0x%02X (mode=%d)\n", 
                       current_status, chip_mode);
       }
     }
   } else {
     e22SetStandby();
     delay(10);
   }
   
   current_status = e22GetStatus();
   chip_mode = (current_status >> 4) & 0x0F;
   
 
   if (chip_mode > 3) {
     Serial.printf("RX: ERROR - Invalid chip_mode %d from status 0x%02X, retrying...\n", chip_mode, current_status);
     delay(50);
     for (int retry = 0; retry < 3; retry++) {
       current_status = e22GetStatus();
       chip_mode = (current_status >> 4) & 0x0F;
       if (chip_mode <= 3) {
         Serial.printf("RX: Status read successful after retry %d: 0x%02X (mode=%d)\n", 
                       retry + 1, current_status, chip_mode);
         break;
       }
       delay(50);
     }
   }
   
   if (chip_mode != 0x01) {
     Serial.printf("RX: WARNING - Module not in Standby mode! Status=0x%02X (mode=%d)\n", current_status, chip_mode);
     if (chip_mode == 0x03) {
       Serial.println("RX: Module already in RX mode, will continue...");
     } else {
       Serial.println("RX: Will attempt to continue anyway...");
       delay(100);
     }
   } else {
     Serial.println("RX: Module confirmed in Standby mode");
   }
   
   // Step 2: Clear IRQ status
   e22ClearIRQStatus(0xFFFF);
   delay(10);
   
   // Step 3: Configure DIO and IRQ for RX mode
   // irqMask: RX_DONE=0x02, PREAMBLE=0x20, SYNC_WORD=0x40 (0x62 total)
   e22ConfigDIOIRQ(0x0062, 0x0062, 0x0000, 0x0000);  // Enable RX-related IRQs
   delay(10);
   
   // Step 4: Set to RX mode (continuous receive, no timeout)
   bool old_skip_busy = skip_busy_check;
   skip_busy_check = true; 
   
   uint8_t tx_data[4] = {0x82, 0x00, 0x00, 0x00};  // RADIO_SET_RX + timeout (0x0000 = continuous)
   bool result = e22SPITransaction(tx_data, 4, NULL, 0);
   
   skip_busy_check = old_skip_busy;
   
   if (result) {
     Serial.println("RX: SetRX command sent successfully");
     
     if (!skip_busy_check) {
       waitE22Ready(500, true); 
     }
     delay(50); 
 
     bool rx_mode_confirmed = false;
     for (int verify_attempt = 0; verify_attempt < 10; verify_attempt++) {  
       delay(30); 
       current_status = e22GetStatus();
       chip_mode = (current_status >> 4) & 0x0F;
       
       if (chip_mode == 0x03) {
         Serial.printf("RX: Module confirmed in RX mode! (attempt %d)\n", verify_attempt + 1);
         rx_mode_confirmed = true;
         break;
       } else if (chip_mode <= 3) {
         if (verify_attempt < 3) {  
           Serial.printf("RX: Module in mode %d (expected 3), waiting... (attempt %d)\n", chip_mode, verify_attempt + 1);
         }
       } else {
         if (verify_attempt < 3) {
           Serial.printf("RX: Invalid status 0x%02X (mode=%d), retrying... (attempt %d)\n", 
                         current_status, chip_mode, verify_attempt + 1);
         }
         delay(50);  
       }
     }
     
     if (!rx_mode_confirmed) {
       Serial.printf("RX: WARNING - Module not confirmed in RX mode! Final status=0x%02X (mode=%d)\n", 
                     current_status, chip_mode);
       Serial.println("RX: Will continue anyway, module may still be transitioning...");
     }
   } else {
     Serial.println("RX: SetRX command failed");
   }
   return result;
 }
 
 
 bool e22SetRXSimple() {
   e22ClearIRQStatus(0xFFFF);
   delay(10);
 
 
   e22ConfigDIOIRQ(0x0002, 0x0002, 0x0000, 0x0000);
   delay(10);
 
 
   uint8_t tx_data[4] = {0x82, 0x00, 0x00, 0x00};
   bool result = e22SPITransaction(tx_data, 4, NULL, 0);
 
   if (result) {
     Serial.println("RX: SetRX command sent successfully");
     delay(20);
 
     // Debug: Check DIO1 pin status after RX mode setup
     uint8_t dio1_status = lora_exio_read(E22_DIO1_EXIO);
     Serial.printf("RX: DIO1 status after RX setup: %d (should be 0 for idle)\n", dio1_status);
   } else {
     Serial.println("RX: SetRX command failed");
   }
 
   return result;
 }
 
 // E22 Command: Read received data (corrected implementation)
 uint8_t e22ReadData(uint8_t *data, uint8_t max_len) {
   // Step 1: Check IRQ status using proper command
   uint16_t irq_status = e22GetIRQStatus();
   
   // Debug: Print IRQ status if there's any activity
   static uint16_t last_irq = 0;
   if (irq_status != last_irq && irq_status != 0) {
     Serial.printf("RX: IRQ changed: 0x%04X (RX_DONE=%d, PREAMBLE=%d, SYNC_WORD=%d)\n",
                   irq_status,
                   (irq_status & 0x02) ? 1 : 0,
                   (irq_status & 0x20) ? 1 : 0,
                   (irq_status & 0x40) ? 1 : 0);
     last_irq = irq_status;
   }
   
   // Check RX_DONE bit (bit 1 = 0x02, not 0x40!)
   if (!(irq_status & 0x02)) {
     // No RX_DONE flag - but check for PREAMBLE or SYNC_WORD to see if signal is being detected
     if (irq_status & 0x20) {
       static unsigned long last_preamble = 0;
       if (millis() - last_preamble > 1000) {
         Serial.println("RX: PREAMBLE detected (signal received but not decoded yet)");
         last_preamble = millis();
       }
     }
     if (irq_status & 0x40) {
       static unsigned long last_sync = 0;
       if (millis() - last_sync > 1000) {
         Serial.println("RX: SYNC_WORD detected (signal received but sync word mismatch?)");
         last_sync = millis();
       }
     }
     return 0;  // No data received
   }
   
   // Step 2: Get packet status (RSSI and SNR)
   int16_t rssi = 0;
   float snr = 0.0f;
   if (e22GetPacketStatus(&rssi, &snr)) {
     Serial.printf("RX: Packet RSSI: %d dBm, SNR: %.2f dB\n", rssi, snr);
     loraData.rssi = rssi;  // Update global RSSI
   }
   
   // Step 3: Get RX buffer status (payload length and start pointer)
   uint8_t payload_length = 0;
   uint8_t rx_start_ptr = 0;
   
   if (!e22GetRXBufferStatus(&payload_length, &rx_start_ptr)) {
     Serial.println("RX: Failed to get RX buffer status");
     return 0;
   }
   
   if (payload_length == 0 || payload_length > max_len) {
     Serial.printf("RX: Invalid payload length: %d (max: %d)\n", payload_length, max_len);
     // Clear IRQ anyway
     e22ClearIRQStatus(0x02);
     return 0;
   }
   
   // Step 4: Read data from buffer
   uint8_t temp_buffer[250] = {0};
   if (!e22ReadBuffer(rx_start_ptr, temp_buffer, payload_length)) {
     Serial.println("RX: Failed to read buffer");
     e22ClearIRQStatus(0x02);
     return 0;
   }
   
   // Copy to output buffer
   for (uint8_t i = 0; i < payload_length && i < max_len; i++) {
     data[i] = temp_buffer[i];
   }
   
   // Ensure null termination
   if (payload_length < max_len) {
     data[payload_length] = '\0';
   } else {
     data[max_len - 1] = '\0';
   }
   
   // Step 5: Clear IRQ status to prevent re-reading
   e22ClearIRQStatus(0x02);  // Clear RX_DONE
 
   // Step 6: Immediately reconfigure RX mode to ensure continuous reception
   // This prevents the module from entering an unstable state after data reception
   Serial.println("RX: Reconfiguring RX mode after data reception...");
   delay(5);  // Small delay before reconfiguring
   e22SetRXSimple();  // Re-enter RX mode immediately
 
  // Debug: Check DIO1 pin status after reconfiguration
  uint8_t dio1_status = lora_exio_read(E22_DIO1_EXIO);
  Serial.printf("RX: DIO1 status after RX reconfiguration: %d\n", dio1_status);
 
   return payload_length;
 }
 
 // -------------------------- LoRa Module Functions (SPI) --------------------------
 bool lora_init() {
   loraData.status = "Initializing";
   loraData.frequency = "915/868MHz";
   loraData.power = "22dBm";
   loraData.mode = "SPI";
   loraData.tx_count = 0;
   loraData.rx_count = 0;
   loraData.rssi = 0;
   loraData.last_message = "None";
   loraData.module_detected = false;
 
   Serial.println("Initializing E22 module via SPI...");
 
   pinMode(E22_NSS_PIN, OUTPUT);
   digitalWrite(E22_NSS_PIN, HIGH);
   lora_setNSS(HIGH);
 
  // FIRST: Detect and initialize MCP23017 - CRITICAL for LoRa control
  Serial.println("Detecting and initializing MCP23017...");

  // Auto-detect MCP23017 address (could be 0x20 or 0x21 depending on A0 pin configuration)
  // According to schematic: A2=0, A1=0, A0 depends on configuration
  // Default: 0x20 (A0=0, all grounded via R50)
  // Can be: 0x21 (A0=1) if A0 is pulled high
  Serial.println("Auto-detecting MCP23017 I2C address...");
   
   // Ensure I2C is ready (it should already be initialized in detect_all_sensors)
   // But verify it's working by checking if we can communicate
   Serial.println("Verifying I2C bus status...");
   Serial.printf("  I2C SDA pin: GPIO%d\n", I2C_SDA_PIN);
   Serial.printf("  I2C SCL pin: GPIO%d\n", I2C_SCL_PIN);
   
   // Ensure I2C is properly initialized (it should be, but verify)
   // Re-initialize I2C to ensure it's in a known state
   Wire.end();
   delay(10);
   Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
   Wire.setClock(100000);  // Use 100kHz for scanning (more reliable than 400kHz)
   Wire.setTimeout(1000000);
   delay(100);  // Give I2C time to stabilize
   
   // First, verify I2C bus is working by checking known devices
   // If other sensors were detected, I2C should be working
   Serial.println("Verifying I2C bus by checking known devices...");
   bool i2c_working = false;
   
   // Check some common I2C addresses that should be on this board
   uint8_t known_addresses[] = {0x51, 0x34, 0x36, 0x44, 0x45, 0x20, 0x21};  // RTC, QMI, SHT41, MCP23017
   for (uint8_t i = 0; i < sizeof(known_addresses); i++) {
     Wire.beginTransmission(known_addresses[i]);
     uint8_t error = Wire.endTransmission();
     if (error == 0) {
       Serial.printf("  ✓ Found I2C device at 0x%02X\n", known_addresses[i]);
       i2c_working = true;
     }
   }
   
   if (!i2c_working) {
     Serial.println("  ⚠ No known I2C devices found, performing full scan...");
   }
   
   // Now do a full scan
   Serial.println("Scanning I2C bus (0x08-0x77)...");
   uint8_t found_devices = 0;
   
   // Try scanning multiple times (sometimes first scan fails)
   for (int scan_attempt = 0; scan_attempt < 3; scan_attempt++) {
     if (scan_attempt > 0) {
       Serial.printf("  Retry scan attempt %d...\n", scan_attempt + 1);
       delay(100);
     }
     
     found_devices = 0;
     for (uint8_t addr = 0x08; addr <= 0x77; addr++) {
       Wire.beginTransmission(addr);
       uint8_t error = Wire.endTransmission();
       if (error == 0) {
         Serial.printf("  ✓ Found I2C device at 0x%02X\n", addr);
         found_devices++;
      } else if (error == 4 && (addr == 0x20 || addr == 0x21)) {
        // Only print errors for MCP23017 addresses
        Serial.printf("  ⚠ I2C error at 0x%02X (unknown error)\n", addr);
      }
     }
     
     if (found_devices > 0) {
       break;  // Found devices, stop retrying
     }
   }
   
   uint8_t detected_addr = 0x00;
   
   if (found_devices == 0) {
     Serial.println("  ✗ ERROR: No I2C devices found on the bus!");
     Serial.println("  Possible causes:");
     Serial.println("    - I2C bus not initialized");
     Serial.println("    - SDA/SCL pins not connected");
     Serial.println("    - I2C pull-up resistors missing");
     Serial.println("    - All I2C devices powered off");
     Serial.println("  Attempting to continue anyway, but LoRa control pins may not work...");
    // Don't return false, allow initialization to continue
    // We'll use a fallback that doesn't require MCP23017
  } else {
    Serial.printf("  Found %d I2C device(s) on the bus\n", found_devices);
    
    // Now specifically check for MCP23017 at possible addresses (0x20-0x21)
    Serial.println("  Checking MCP23017 possible addresses (0x20-0x21)...");
    bool mcp_found = false;
    
    // Try multiple times (sometimes first attempt fails)
    for (int retry = 0; retry < 3 && !mcp_found; retry++) {
      if (retry > 0) {
        Serial.printf("  Retry %d/3...\n", retry + 1);
        delay(50);
      }
      
      // Check both possible addresses (0x20 and 0x21)
      for (uint8_t addr = 0x20; addr <= 0x21; addr++) {
        Wire.beginTransmission(addr);
        uint8_t error = Wire.endTransmission();
        if (error == 0) {
          // Try to read a register to verify it's MCP23017
          // Read GPIOA register (0x12 in BANK=0 mode) - this should always work
          Wire.beginTransmission(addr);
          Wire.write(0x12);  // GPIOA register
          if (Wire.endTransmission() == 0) {
            uint8_t bytes = Wire.requestFrom(addr, (uint8_t)1);
            if (bytes == 1) {
              uint8_t value = Wire.read();
              Serial.printf("  ✓ Found device at 0x%02X (verified: read 0x%02X from GPIOA)\n", addr, value);
              if (!mcp_found) {
                // Use the first found address
                detected_addr = addr;
                mcp23017_address = addr;
                mcp_found = true;
                if (addr == 0x20) {
                  Serial.println("    → Using 0x20 (A0=LOW, default configuration)");
                } else if (addr == 0x21) {
                  Serial.println("    → Using 0x21 (A0=HIGH, A0 pulled high)");
                } else {
                  Serial.printf("    → Using 0x%02X (unexpected address, verify hardware!)\n", addr);
                }
                break;  // Found MCP23017, stop searching
              }
            }
          }
        }
      }
      if (mcp_found) break;  // Found MCP23017, stop retrying
    }
    
    if (!mcp_found) {
      Serial.println("  ✗ WARNING: No device found at 0x20-0x21!");
      Serial.println("  MCP23017 may not be present, not powered, or at unexpected address.");
      Serial.println("  LoRa control pins (NRST, BUSY, DIO1, DIO2) will not work!");
      Serial.println("  Attempting to continue anyway, but LoRa may not function correctly...");
      Serial.println("  ⚠ CRITICAL: Without MCP23017, RX mode will NOT work (RXEN cannot be enabled)!");
      Serial.println("  ⚠ TX mode may work (TXEN has pull-up), but RX requires DIO2 control.");
      mcp23017_address = 0x00;  // Mark MCP23017 as not found
      detected_addr = 0x00;
    } else {
      Serial.printf("  ✓ MCP23017 found and configured at address 0x%02X\n", mcp23017_address);
    }
  }
  
  // Re-initialize MCP23017 (I2C may have been re-initialized in detect_all_sensors)
  bool mcp_init_ok = false;
  
  if (detected_addr == 0x00) {
    Serial.println("  ⚠ WARNING: MCP23017 not found! LoRa control pins will not work.");
    Serial.println("  LoRa module may not initialize correctly without MCP23017.");
    Serial.println("  Continuing anyway...");
    // Skip MCP23017 configuration, but allow initialization to continue
  } else {
    Serial.println("Re-initializing MCP23017...");
    
    // Initialize MCP23017 using Adafruit library
    // Note: mcp may already be initialized in setup() for LCD reset, but re-initializing is safe
    // Use the detected address (could be 0x20 or 0x21)
    mcp_init_ok = mcp.begin_I2C(detected_addr);
    if (mcp_init_ok) {
      Serial.printf("  ✓ MCP23017 (0x%02X) initialized successfully\n", detected_addr);
      
      // Configure MCP23017 pins for E22 control
      Serial.println("Configuring MCP23017 pins for E22...");
      Serial.printf("  Setting pin %d (NRST) as OUTPUT\n", E22_NRST_EXIO);
      Serial.printf("  Setting pin %d (BUSY) as INPUT\n", E22_BUSY_EXIO);
      Serial.printf("  Setting pin %d (DIO1) as INPUT\n", E22_DIO1_EXIO);

      mcp.pinMode(E22_NRST_EXIO, OUTPUT);  // NRST as output
      mcp.pinMode(E22_BUSY_EXIO, INPUT);    // BUSY as input (with pull-up if needed)
      mcp.pinMode(E22_DIO1_EXIO, INPUT);     // DIO1 as input

      // Verify configuration
      delay(10);
      uint8_t busy_after_config = mcp.digitalRead(E22_BUSY_EXIO);
      Serial.printf("BUSY state after config: %d\n", busy_after_config);
    } else {
      Serial.printf("  ✗ MCP23017 (0x%02X) begin() failed, but device detected!\n", detected_addr);
      Serial.println("  Will use direct I2C access as fallback.");
      mcp_init_ok = true;  // Mark as OK for direct I2C access (if needed in future)
      // Note: Cannot use mcp library functions if begin() failed
      // The lora_exio_* functions will check mcp23017_address instead
    }
    
    // Test MCP23017 communication
    Serial.println("Testing MCP23017 communication...");
    if (mcp_init_ok && mcp23017_address != 0x00) {
      Serial.printf("  ✓ MCP23017 (0x%02X) is connected and ready\n", mcp23017_address);
    } else {
      Serial.println("  ⚠ WARNING: MCP23017 communication test failed!");
      Serial.println("  Will attempt to continue with direct I2C access...");
    }
  }

  // Configure EXIO pins via MCP23017 (only if MCP23017 is available)
  if (detected_addr != 0x00) {
    Serial.println("MCP23017 configured successfully - LoRa control pins ready");
  } else {
    Serial.println("MCP23017 not available - using direct SPI mode only");
  }
   
   // Initialize SPI for E22 (Mode 0, MSB first, 1MHz - slower for reliability)
   // Note: SPI.begin() can be called multiple times with different pins
   SPI.begin(E22_SCK_PIN, E22_MISO_PIN, E22_MOSI_PIN, E22_NSS_PIN);
   SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0));  
   Serial.println("SPI initialized for E22 (1MHz clock)");
 
   Serial.println("Testing basic SPI bus connectivity...");
   pinMode(E22_NSS_PIN, OUTPUT);
   digitalWrite(E22_NSS_PIN, HIGH);
 
 
   Serial.println("SPI pin status check:");
   Serial.printf("  NSS (GPIO%d): %s\n", E22_NSS_PIN, digitalRead(E22_NSS_PIN) ? "HIGH" : "LOW");
   Serial.printf("  SCK (GPIO%d): %s\n", E22_SCK_PIN, digitalRead(E22_SCK_PIN) ? "HIGH" : "LOW");
   Serial.printf("  MOSI (GPIO%d): %s\n", E22_MOSI_PIN, digitalRead(E22_MOSI_PIN) ? "HIGH" : "LOW");
   Serial.printf("  MISO (GPIO%d): %s\n", E22_MISO_PIN, digitalRead(E22_MISO_PIN) ? "HIGH" : "LOW");
 
 
   Serial.println("Sending test SPI transactions...");
   for (int i = 0; i < 3; i++) {
     digitalWrite(E22_NSS_PIN, HIGH);
     delayMicroseconds(50);
     digitalWrite(E22_NSS_PIN, LOW);
     delayMicroseconds(10);
 
     uint8_t test_byte = SPI.transfer(0x55 + i);  
     delayMicroseconds(10);
     digitalWrite(E22_NSS_PIN, HIGH);
     delayMicroseconds(50);
 
     Serial.printf("  Test %d: sent 0x%02X, received 0x%02X\n", i+1, 0x55 + i, test_byte);
   }
 
   Serial.println("SPI bus test summary:");
   Serial.println("  - If all responses are 0xFF: LoRa module not responding");
   Serial.println("  - If some responses vary: SPI bus working, LoRa module may be in wrong state");
   Serial.println("  - If responses are consistent non-FF: SPI communication working");
 
   Serial.println("Checking LoRa control pins...");
   uint8_t nss_state = digitalRead(E22_NSS_PIN);
   Serial.printf("NSS pin (GPIO%d): %s\n", E22_NSS_PIN, nss_state == HIGH ? "HIGH" : "LOW");
 
   if (detected_addr != 0x00) {
     uint8_t busy_state = lora_exio_read(LORA_EXIO_BUSY);
     uint8_t dio1_state = lora_exio_read(LORA_EXIO_DIO1);
     Serial.printf("BUSY pin (EXIO%d): %s\n", LORA_EXIO_BUSY, busy_state == HIGH ? "HIGH" : "LOW");
     Serial.printf("DIO1 pin (EXIO%d): %s\n", LORA_EXIO_DIO1, dio1_state == HIGH ? "HIGH" : "LOW");
   } else {
     Serial.println("MCP23017 not available - cannot read BUSY/DIO1 pins");
   }
 
   Serial.println("SPI bus test result: 0xFF received (SPI OK, LoRa not responding)");
 
   Serial.println("Testing LoRa SPI response...");
 
 
   digitalWrite(E22_NSS_PIN, LOW);
   delayMicroseconds(10);
   uint8_t status_response = SPI.transfer(0xC0);  // GET_STATUS command
   uint8_t dummy_response = SPI.transfer(0x00);   // dummy byte
   digitalWrite(E22_NSS_PIN, HIGH);
 
   Serial.printf("Direct SPI test: status=0x%02X, dummy=0x%02X\n", status_response, dummy_response);
 
   if (status_response != 0x00 && status_response != 0xFF) {
     uint8_t chip_mode = (status_response >> 4) & 0x0F;
     Serial.printf("✓ LoRa module responded! Status=0x%02X, mode=%d\n", status_response, chip_mode);
   } else {
     Serial.printf("✗ LoRa module not responding: 0x%02X (expected valid status byte)\n", status_response);
     Serial.println("CRITICAL: Check LoRa module power and SPI connections");
   }
   
   Serial.println("Testing LoRa module SPI communication...");
   delay(10);
 
   Serial.println("Testing LoRa status register reads:");
   for (int attempt = 0; attempt < 5; attempt++) {
     uint8_t status = e22GetStatus();
     uint8_t chip_mode = (status >> 4) & 0x0F;
     Serial.printf("  Attempt %d: status=0x%02X, chip_mode=%d\n", attempt + 1, status, chip_mode);
 
     if (status != 0xFF && chip_mode <= 3) {
       Serial.println("  ✓ Got valid LoRa module response!");
       break;
     }
     delay(100);
   }
   
   // Reset module
   Serial.println("Resetting LoRa module...");
   Serial.println("Step 1: Pulling NRST LOW...");
   lora_exio_write(LORA_EXIO_NRST, LOW);
   delay(50); 
   
 
   uint8_t busy_before = lora_exio_read(LORA_EXIO_BUSY);
   Serial.printf("  BUSY before reset release: %s\n", busy_before == HIGH ? "HIGH" : "LOW");
   
   Serial.println("Step 2: Releasing NRST HIGH...");
   lora_exio_write(LORA_EXIO_NRST, HIGH);
   delay(50); 
   
   uint8_t busy_after = lora_exio_read(LORA_EXIO_BUSY);
   Serial.printf("  BUSY after reset release: %s\n", busy_after == HIGH ? "HIGH" : "LOW");
   
   Serial.println("Step 3: Waiting for BUSY to go LOW...");
   unsigned long busy_wait_start = millis();
   bool busy_ok = false;
   while (millis() - busy_wait_start < 500) {  
     uint8_t busy_level = lora_exio_read(LORA_EXIO_BUSY);
     if (busy_level == LOW) {
       busy_ok = true;
       Serial.printf("  ✓ BUSY went LOW after %lu ms\n", millis() - busy_wait_start);
       break;
     }
     if ((millis() - busy_wait_start) % 100 == 0) {
       Serial.printf("  [%lu ms] BUSY still HIGH...\n", millis() - busy_wait_start);
     }
     delay(10);
   }
   
   // Wait for module to be ready (with longer timeout) 
   Serial.println("Waiting for E22 to be ready...");
   bool busy_ready = waitE22Ready(200);  // Short timeout for initial check
   
   if (!busy_ready) {
     Serial.println("WARNING: E22 module BUSY check failed, but will attempt SPI communication...");
     Serial.println("Note: Will skip BUSY check if SPI communication succeeds");
   } else {
     Serial.println("E22 module BUSY check passed!");
   }
   
   Serial.println("Testing LoRa SPI communication...");
   uint8_t version = e22ReadRegister(0x01);
   Serial.printf("Version register: 0x%02X\n", version);
 
   if (version != 0 && version != 0xFF) {
     loraData.module_detected = true;
     loraData.status = "Connected";
     Serial.printf("E22 module detected! Version: 0x%02X\n", version);
     Serial.println("SPI communication successful!");
     
     // If BUSY check failed but SPI works, skip BUSY check for future operations
     if (!busy_ready) {
       skip_busy_check = true;
       Serial.println("Note: BUSY pin check will be skipped (SPI works without it)");
     }
     
     // Initialize E22 module using proper SX126x commands 
     Serial.println("Configuring E22 module parameters...");
   
   // Step 1: Enable DIO2 as RF switch control
   e22SetRFSwitchMode(0x01);
   delay(10);
   Serial.println("DIO2 configured as RF switch control");
   
   // Step 2: Set to Standby mode
   e22SetStandby();
   delay(10);
   Serial.println("Set to Standby mode");
   
   // Step 3: Set packet type to LoRa
   e22SetPacketType(0x01);  // 0x01 = LoRa
   delay(10);
   Serial.println("Packet type set to LoRa");
   
   // Step 4: Set Sync Word (0x12 for LoRaWAN, 0x34 for point-to-point)
   e22SetSyncWord(0x12);
   delay(10);
   Serial.println("Sync Word set to 0x12");
   
   // Step 5: Set RF frequency (915 MHz)
   e22SetRFFrequency(915000000);
   delay(10);
   Serial.println("Frequency set to 915 MHz");
   // Set frequency display to fixed value
   loraData.frequency = "915/868MHz";
   
   // Step 6: Set PA configuration
   e22SetPAConfig(0x04, 0x07, 0x00, 0x01);  // paDutyCycle=0x04, hpMax=0x07, deviceSel=0x00, paLut=0x01
   delay(10);
   Serial.println("PA config set");
   
   // Step 7: Set TX power
   e22SetTXParams(22, 0x04);  // power=+22dBm, rampTime=40us
   delay(10);
   Serial.println("TX power set to +22dBm");
   
   // Step 8: Set modulation parameters (SF12, BW125kHz, CR4/5, LDRO on)
   e22SetModulationParams(0x0C, 0x04, 0x01, 0x01);  // SF12, BW125kHz, CR4/5, LDRO on
   delay(10);
   Serial.println("Modulation params set (SF12, BW125, CR4/5)");
   
   // Step 9: Set LoRa symbol timeout
   e22SetLoRaSymbTimeout(0x00, 0x80);
   delay(10);
   Serial.println("Symbol timeout set");
   
   // Step 10: Set packet parameters
   e22SetPacketParams(0x00, 0x40, 0x00, 0xFF, 0x01, 0x00);  // preamble=16, explicit header, max payload=255, CRC on
   delay(10);
   Serial.println("Packet params set");
   
   // Step 11: Set buffer base addresses
   e22SetBufferBaseAddress(0x00, 0x80);  // txBaseAddr=0x00, rxBaseAddr=0x80
   delay(10);
   Serial.println("Buffer base addresses set");
   
   // Step 12: Configure DIO and IRQ (for TX mode initially)
   // Match working ESP-IDF version: only enable TX_DONE initially
   e22ConfigDIOIRQ(0x0001, 0x0001, 0x0000, 0x0000);  // irqMask=0x01 (TX_DONE only), dio1Mask same
   delay(10);
   Serial.println("DIO/IRQ configured");
   
   // Step 13: Set module to Standby mode initially (will switch to TX/RX when user presses button)
   e22SetStandby();
   delay(10);
   Serial.println("Module set to Standby mode (waiting for user input)");
   
     Serial.println("E22 module configuration complete!");
 
     // Quick test: Check if module responds after configuration
     Serial.println("Testing LoRa module response after configuration...");
     uint8_t test_status = e22GetStatus();
     if (test_status != 0x00 && test_status != 0xFF) {
       uint8_t chip_mode = (test_status >> 4) & 0x0F;
       Serial.printf("✓ Module responded: status=0x%02X, mode=%d\n", test_status, chip_mode);
       if (chip_mode <= 3) {
         Serial.println("✓ LoRa module is working correctly!");
       } else {
         Serial.println("⚠ Module responded but status invalid - possible communication issue");
       }
     } else {
       Serial.printf("✗ Module not responding: status=0x%02X\n", test_status);
       Serial.println("CRITICAL: Check LoRa module connections and power supply");
       Serial.println("Running detailed hardware diagnostics...");
 
       // Run hardware diagnostics
       hardwareDiagnostics();
     }
 
     delay(50);
   } else {
     loraData.module_detected = false;
     loraData.status = "No Response";
     Serial.println("E22 module not responding!");
     Serial.println("Will use default values for display");
   }
   
   // Update module state
   loraModuleState.initialized = true;
   
   // Initialize default values (always set, even if module not detected) - Reference 16_lvgl_LoRaTest.ino
   if (loraData.frequency.length() == 0) {
     loraData.frequency = "915/868MHz";
   }
   if (loraData.power.length() == 0) {
     loraData.power = "22dBm";
   }
   if (loraData.mode.length() == 0) {
     loraData.mode = "SPI";
   }
   if (loraData.status.length() == 0) {
     loraData.status = "Unknown";
   }
   if (loraData.last_message.length() == 0) {
     loraData.last_message = "None";
   }
   
   Serial.printf("Display values initialized: Freq=%s, Power=%s, Mode=%s\n", 
                 loraData.frequency.c_str(), loraData.power.c_str(), loraData.mode.c_str());
   
   return true;
 }
 
 // Get LoRa IRQ status (for debugging)
 uint16_t lora_get_irq_status() {
   uint8_t irq_status[2];
   lora_spi_readCommand(SX126X_CMD_GET_IRQ_STATUS, irq_status, 2);
   return (irq_status[0] << 8) | irq_status[1];
 }
 
// Send test message via SPI 
void sendTestMessage() {
  if (!loraData.module_detected) return;
  // Check if still in TX mode - exit immediately if user pressed button
  if (!loraData.tx_mode) return;
  
  // Sending："111"-"999"
  uint8_t digit = (loraData.tx_count % 9) + 1;  // 1~9
  char ch = '0' + digit;
  String message;
  message.reserve(4);
  message += ch;
  message += ch;
  message += ch;
  uint8_t msg_len = message.length();
  if (msg_len > 240) msg_len = 240;
  
  // Convert String to uint8_t array
  uint8_t msg_data[240];
  memcpy(msg_data, message.c_str(), msg_len);
  
  // Send data (e22SendData already handles TX mode setup and IRQ configuration)
  if (e22SendData(msg_data, msg_len)) {
    // Check if still in TX mode before updating count (user may have pressed button)
    if (loraData.tx_mode) {
      loraData.tx_count++;
      // Save sent message to last_message for display in TX mode
      loraData.last_message = message;
      Serial.printf("Sent: %s\n", message.c_str());

      // Update Data label in TX screen immediately
      if (label_lora_tx_count && loraData.tx_mode) {
        lv_label_set_text_fmt(label_lora_tx_count, "Data: %s", message.c_str());
      }

      if (label_lora_message && loraData.tx_mode) {
        char msg_buf[64];
        snprintf(msg_buf, sizeof(msg_buf), "TX: %s", message.c_str());
        lv_label_set_text(label_lora_message, msg_buf);
      }
      
      // Force UI refresh immediately after sending data
      lv_timer_handler();  // Force LVGL timer handler to update UI immediately

      // Wait for TX done (check IRQ status) 
      delay(100);
      if (!loraData.tx_mode) return;  // Check if cancelled during delay
      uint8_t irq_status = e22ReadRegister(0x12);
      if (irq_status & 0x08) {  // TX_DONE
        e22WriteRegister(0x12, 0xFF);  // Clear IRQ
        e22SetStandby();  // Return to standby
      }
    } else {
      Serial.println("TX: Send completed but TX mode was cancelled");
    }
  } else {
    // Only show error if still in TX mode
    if (loraData.tx_mode) {
      Serial.println("Send failed!");
      if (label_lora_message) {
        lv_label_set_text(label_lora_message, "TX: Failed!");
      }
    }
  }
 }
 
// E22 Command: Send data (full TX flow with TX_DONE check) 
bool e22SendData(uint8_t *data, uint8_t len) {
  if (len > 240) len = 240;  // Max payload size
  
  // Check if still in TX mode - exit immediately if user pressed button
  if (!loraData.tx_mode) {
    Serial.println("TX: TX mode cancelled before sending");
    return false;
  }

  // Step 1: Set to Standby mode
  e22SetStandby();
  delay(10);
  if (!loraData.tx_mode) return false;  // Check again after delay

  // Step 2: Clear IRQ status
  e22ClearIRQStatus(0xFFFF);
  
  // Step 4: Re-configure DIO and IRQ for TX mode (TX_DONE only)
  e22ConfigDIOIRQ(0x0001, 0x0001, 0x0000, 0x0000);  // TX_DONE only
  delay(10);
  if (!loraData.tx_mode) return false;  // Check again after delay
   
   // Step 5: Write data to buffer (offset 0x00 for TX)
   if (!e22WriteBuffer(0x00, data, len)) {
     return false;
   }
   
   // Step 6: Update packet parameters with actual payload length
   // preambleLength = 16 (0x00, 0x40), headerType = explicit (0x00), 
   // payloadLength = len, crcType = on (0x01), invertIQ = standard (0x00)
   if (!e22SetPacketParams(0x00, 0x40, 0x00, len, 0x01, 0x00)) {
     return false;
   }
   
   // Step 7: Set to TX mode (3 bytes timeout: no timeout = 0x00, 0x00, 0x00)
   Serial.println("TX: Sending SET_TX command...");
   uint8_t tx_cmd[4] = {0x83, 0x00, 0x00, 0x00};  // RADIO_SET_TX + timeout (no timeout)
   bool tx_result = e22SPITransaction(tx_cmd, 4, NULL, 0);
   if (!tx_result) {
     Serial.println("TX: SET_TX command failed");
     return false;
   }
  Serial.println("TX: SET_TX command sent successfully");

  delay(10);
  if (!loraData.tx_mode) return false;  // Check again after delay
  uint8_t status_after_tx = e22GetStatus();
   uint8_t chip_mode_after_tx = (status_after_tx >> 4) & 0x0F;
   Serial.printf("TX: After SET_TX, module status: 0x%02X (chip_mode=%d, expected=3 for TX)\n",
                 status_after_tx, chip_mode_after_tx);
 
   if (chip_mode_after_tx != 0x03) {
     Serial.println("TX: DEBUG - Checking module state before retry:");
     Serial.printf("  DIO1 pin state: %d\n", lora_exio_read(LORA_EXIO_DIO1));
     Serial.printf("  BUSY pin state: %d\n", lora_exio_read(LORA_EXIO_BUSY));
 
     uint16_t current_irq = e22GetIRQStatus();
     Serial.printf("  Current IRQ status: 0x%04X\n", current_irq);
   }
 
   if (chip_mode_after_tx != 0x03) {
     Serial.println("TX: WARNING - Module may not be in TX mode (chip_mode != 3)");
     if (chip_mode_after_tx == 0x00) {
      Serial.println("TX: Module in Sleep mode, attempting to wake up...");
      e22SetStandby();
      delay(50);
      if (!loraData.tx_mode) return false;  // Check after delay

      Serial.println("TX: Re-sending SET_TX command...");
      if (!e22SPITransaction(tx_cmd, 4, NULL, 0)) {
        Serial.println("TX: Re-send SET_TX command failed");
        return false;
      }

      delay(10);
      if (!loraData.tx_mode) return false;  // Check after delay
      status_after_tx = e22GetStatus();
       chip_mode_after_tx = (status_after_tx >> 4) & 0x0F;
       Serial.printf("TX: After retry, module status: 0x%02X (chip_mode=%d)\n",
                     status_after_tx, chip_mode_after_tx);
     }
   }
   
  // Step 8: Wait for TX_DONE IRQ (Waiting 5s)
  Serial.println("TX: Waiting for TX_DONE IRQ...");
  unsigned long start = millis();
  bool tx_completed = false;

  // Button state tracking for detection during wait (reset at start of each wait)
  static bool wait_button_pressed = false;
  static unsigned long wait_button_press_time = 0;
  const unsigned long SHORT_PRESS_MAX_TIME = 800;  // 800ms maximum for short press (same as long press threshold)
  
  // Reset button state at start of wait loop
  wait_button_pressed = false;
  
  while (millis() - start < 5000) {  // 5 second timeout
    // Check if still in TX mode - exit immediately if user pressed button to exit TX mode
    if (!loraData.tx_mode) {
      Serial.println("TX: TX mode cancelled by user, exiting wait loop");
      e22SetStandby();
      wait_button_pressed = false;  // Reset button state
      return false;
    }
    
    // Keep UI responsive by calling lv_timer_handler
    lv_timer_handler();
    
    // Check button state during wait loop
    int button_state = digitalRead(BUTTON_PIN);
    if (button_state == LOW && !wait_button_pressed) {
      // Button just pressed
      wait_button_pressed = true;
      wait_button_press_time = millis();
    } else if (button_state == HIGH && wait_button_pressed) {
      // Button released - check if it's a short press
      unsigned long press_duration = millis() - wait_button_press_time;
      wait_button_pressed = false;
      
      if (press_duration >= 10 && press_duration < SHORT_PRESS_MAX_TIME) {
        // Short press detected - exit TX mode immediately
        Serial.println("TX: Short press detected during TX wait, cancelling TX mode");
        loraData.tx_mode = false;
        e22SetStandby();
        return false;
      }
    }
    
    uint16_t irq_status = e22GetIRQStatus();
    uint8_t module_status = e22GetStatus();
    uint8_t chip_mode = (module_status >> 4) & 0x0F;

    if ((millis() - start) % 100 == 0) {
      Serial.printf("TX: Waiting for TX_DONE, IRQ status: 0x%04X (TX_DONE=%d, RX_DONE=%d)\n",
                    irq_status, (irq_status & 0x01) ? 1 : 0, (irq_status & 0x02) ? 1 : 0);
      Serial.printf("TX: Module status changed: chip_mode=%d (0=Sleep, 1=Standby, 2=RX, 3=TX)\n", chip_mode);
    }

    if (irq_status & 0x01) {  // TX_DONE bit (bit 0 = 0x01)
      e22ClearIRQStatus(0x01);  // Clear TX_DONE
      e22SetStandby();  // Return to standby
      Serial.println("TX: TX_DONE confirmed!");
      tx_completed = true;
      wait_button_pressed = false;  // Reset button state
      break;
    }

    if (irq_status & 0x02) {  // TIMEOUT bit
      Serial.println("TX: TX timeout detected");
      e22ClearIRQStatus(0x02);
      e22SetStandby();
      wait_button_pressed = false;  // Reset button state
      return false;
    }

    // Use smaller delay and check tx_mode more frequently
    delay(5);
    // Check again after delay
    if (!loraData.tx_mode) {
      Serial.println("TX: TX mode cancelled during wait, exiting");
      e22SetStandby();
      wait_button_pressed = false;  // Reset button state
      return false;
    }
  }
  
  // Reset button state when exiting wait loop
  wait_button_pressed = false;
   
   if (!tx_completed) {
     e22SetStandby();
     return true; 
   }
   
   return true;
 }
 
 // Check for received LoRa message via SPI
 void lora_check_received() {
   if (!loraData.module_detected) {
     static unsigned long last_warn = 0;
     if (millis() - last_warn > 5000) {
       Serial.println("RX: Module not detected, cannot receive");
       last_warn = millis();
     }
     return;
   }
   
   // Ensure module is in RX mode (only set once, or if not set yet) 
   // Use static variable like in 16_lvgl_LoRaTest.ino
   static bool rx_mode_set = false;
   
   // If force reset flag is set (from mode switch), reset the static variable and re-enter RX mode
   // This ensures module is properly configured for RX after mode switch
   if (force_rx_reset) {
     Serial.println("RX: Force reset flag set, re-entering RX mode...");
     rx_mode_set = false;  // Reset flag to force re-entry into RX mode
     force_rx_reset = false;
     e22SetRXSimple();  // Re-enter RX mode to ensure proper configuration
     delay(20);  // Give module time to enter RX mode
     rx_mode_set = true;
     Serial.println("RX: Re-entered RX mode after force reset");
   }
   
   if (!rx_mode_set) {
     e22SetRXSimple(); 
     rx_mode_set = true;
   }
   
   // Check for received data
   uint16_t irq_status = e22GetIRQStatus();
 
   if (irq_status & 0x02) {
 
     Serial.printf("RX: RX_DONE IRQ detected! IRQ=0x%04X\n", irq_status);
     
 
     uint8_t payload_length = 0;
     uint8_t rx_start_ptr = 0;
     
     if (e22GetRXBufferStatus(&payload_length, &rx_start_ptr)) {
       if (payload_length > 0 && payload_length < 250) {
         uint8_t rx_buffer[250] = {0};
         uint8_t received_len = e22ReadData(rx_buffer, sizeof(rx_buffer));
         
         if (received_len > 0) {
           Serial.printf("RX: Received %d bytes: %s\n", received_len, rx_buffer);
 
           // Process received data (convert to string, validate, update UI)
           // Ensure null termination
           if (received_len < sizeof(rx_buffer)) {
             rx_buffer[received_len] = '\0';
           } else {
             rx_buffer[sizeof(rx_buffer) - 1] = '\0';
           }
 
           // Convert to String - handle both text and binary data
           String received = "";
           bool is_text = true;
           for (uint8_t i = 0; i < received_len && i < sizeof(rx_buffer); i++) {
             if (rx_buffer[i] >= 32 && rx_buffer[i] < 127) {
               // Printable ASCII character
               received += (char)rx_buffer[i];
             } else if (rx_buffer[i] == 0) {
               // Null terminator found, stop here
               break;
             } else {
               // Non-printable character
               is_text = false;
               break;
             }
           }
 
           // If not text, create hex representation
           if (!is_text || received.length() == 0) {
             received = "0x";
             for (uint8_t i = 0; i < received_len && i < 10; i++) {  // Show first 10 bytes
               char hex[3];
               sprintf(hex, "%02X", rx_buffer[i]);
               received += hex;
             }
             if (received_len > 10) received += "...";
           }
 
           received.trim();
 
           if (received.length() > 0) {
             // Application-level filter:
             // Only accept payloads that look like "111"..."999" (3 same digits from '1' to '9').
             bool valid = false;
             if (received.length() == 3 &&
                 received[0] == received[1] &&
                 received[1] == received[2] &&
                 received[0] >= '1' && received[0] <= '9') {
               valid = true;
             }
 
             if (valid) {
               // Avoid updating UI and counters with the exact same payload repeatedly.
               static String last_displayed_msg = "";
               if (received != last_displayed_msg) {
                 loraData.last_message = received;
                 loraData.rx_count++;
                 last_displayed_msg = received;

                 // RSSI is already read in e22ReadData() via e22GetPacketStatus()
                 Serial.printf("RX: Received (%d bytes): %s, RSSI: %d dBm\n",
                               received_len, received.c_str(), loraData.rssi);

                 // Debug: Confirm data was stored
                 Serial.printf("RX: Updated loraData.last_message to: '%s'\n", loraData.last_message.c_str());
                 Serial.printf("RX: loraData.rx_mode = %s\n", loraData.rx_mode ? "true" : "false");

                 // Update Data label in RX screen immediately
                 if (label_lora_rx_count && loraData.rx_mode) {
                   lv_label_set_text_fmt(label_lora_rx_count, "Data: %s", received.c_str());
                 }

                 // Force UI refresh after receiving data
                 lv_timer_handler();  // Force LVGL timer handler to update UI immediately
 
                 // Additional debug: Check UI update conditions
                 Serial.printf("RX: UI update check - rx_mode: %s, last_message: '%s' (len=%d)\n",
                               loraData.rx_mode ? "true" : "false",
                               loraData.last_message.c_str(),
                               loraData.last_message.length());
               }
             } else {
               // Invalid format, but still count it
               loraData.rx_count++;
               Serial.printf("RX: Received invalid format: '%s' (len=%d)\n", received.c_str(), received.length());
             }
           }
         }
       }
     }
     
     e22ClearIRQStatus(0x02);
   }
   
 
   static unsigned long last_irq_check = 0;
   if (millis() - last_irq_check > 2000) {  // Check IRQ status every 2 seconds
     uint8_t module_status = e22GetStatus();
     uint8_t chip_mode = (module_status >> 4) & 0x0F;
     Serial.printf("RX: IRQ=0x%04X, ModuleStatus=0x%02X (%s)\n",
                   irq_status,
                   module_status,
                   (chip_mode <= 3) ? "Valid" : "Unknown");
     
     if (chip_mode > 3) {
       static unsigned long last_warn = 0;
       if (millis() - last_warn > 5000) {  
         Serial.printf("RX: WARNING - Status read invalid (0x%02X, mode=%d), but IRQ status may still work\n",
                       module_status, chip_mode);
         last_warn = millis();
       }
     }
     
     last_irq_check = millis();
   }
   
   if (irq_status & 0x20) {
     static unsigned long last_preamble = 0;
     if (millis() - last_preamble > 2000) {
       Serial.println("RX: PREAMBLE detected (signal received but not decoded yet)");
       last_preamble = millis();
     }
     e22ClearIRQStatus(0x20);  
   }
   
   if (irq_status & 0x40) {
     static unsigned long last_sync = 0;
     if (millis() - last_sync > 2000) {
       Serial.println("RX: SYNC_WORD detected (signal received but sync word mismatch?)");
       last_sync = millis();
     }
     e22ClearIRQStatus(0x40);  
   }
 
   uint8_t rx_buffer[240];
   // Clear buffer before each receive to avoid any leftover bytes
   memset(rx_buffer, 0, sizeof(rx_buffer));
   uint8_t rx_len = e22ReadData(rx_buffer, 240);
   
   if (rx_len > 0) {
     // Ensure null termination
     if (rx_len < 240) {
       rx_buffer[rx_len] = '\0';
     } else {
       rx_buffer[239] = '\0';  // Safety: ensure null termination
     }
     
     // Convert to String - handle both text and binary data
     String received = "";
     bool is_text = true;
     for (uint8_t i = 0; i < rx_len && i < 240; i++) {
       if (rx_buffer[i] >= 32 && rx_buffer[i] < 127) {
         // Printable ASCII character
         received += (char)rx_buffer[i];
       } else if (rx_buffer[i] == 0) {
         // Null terminator found, stop here
         break;
       } else {
         // Non-printable character
         is_text = false;
         break;
       }
     }
     
     // If not text, create hex representation
     if (!is_text || received.length() == 0) {
       received = "0x";
       for (uint8_t i = 0; i < rx_len && i < 10; i++) {  // Show first 10 bytes
         char hex[3];
         sprintf(hex, "%02X", rx_buffer[i]);
         received += hex;
       }
       if (rx_len > 10) received += "...";
     }
     
     received.trim();
     
     if (received.length() > 0) {
       // Application-level filter:
       // Only accept payloads that look like "111"..."999" (3 same digits from '1' to '9').
       bool valid = false;
       if (received.length() == 3 &&
           received[0] == received[1] &&
           received[1] == received[2] &&
           received[0] >= '1' && received[0] <= '9') {
         valid = true;
       }
 
       if (valid) {
         // Avoid updating UI and counters with the exact same payload repeatedly.
         static String last_displayed_msg = "";
         if (received != last_displayed_msg) {
           loraData.last_message = received;
           loraData.rx_count++;
           last_displayed_msg = received;

           // RSSI is already read in e22ReadData() via e22GetPacketStatus()
           Serial.printf("RX: Received (%d bytes): %s, RSSI: %d dBm\n",
                         rx_len, received.c_str(), loraData.rssi);

           // Debug: Confirm data was stored
           Serial.printf("RX: Updated loraData.last_message to: '%s'\n", loraData.last_message.c_str());
           Serial.printf("RX: loraData.rx_mode = %s\n", loraData.rx_mode ? "true" : "false");

           // Update Data label in RX screen immediately
           if (label_lora_rx_count && loraData.rx_mode) {
             lv_label_set_text_fmt(label_lora_rx_count, "Data: %s", received.c_str());
           }

           // Force UI refresh after receiving data
           lv_timer_handler();  // Force LVGL timer handler to update UI immediately
 
           // Additional debug: Check UI update conditions
           Serial.printf("RX: UI update check - rx_mode: %s, last_message: '%s' (len=%d)\n",
                         loraData.rx_mode ? "true" : "false",
                         loraData.last_message.c_str(),
                         loraData.last_message.length());
         }
       } else {
         // Corrupted or unexpected payload, ignore for UI and counters
         Serial.printf("RX: Corrupted (%d bytes): %s (ignored)\n",
                       rx_len, received.c_str());
       }
 
       // Force RX reconfiguration for next reception
       // Note: RX mode is already reconfigured in e22ReadData(), so we don't need to reset here
       // rx_mode_set = false;
     } else {
       Serial.printf("RX: Received %d bytes but could not parse as text\n", rx_len);
     }
   }
 }
 
 // -------------------------- Flash Detection Functions --------------------------
 uint32_t flash_get_size() {
   // Use ESP.getFlashChipSize() which is reliable for ESP32-S3
   uint32_t flash_size = ESP.getFlashChipSize();
   return flash_size;
 }
 
 // -------------------------- WIFI AP Functions --------------------------
 void gpio_init_wifi() {
   gpio_config_t gpio_conf = {};
   gpio_conf.intr_type = GPIO_INTR_DISABLE;
   gpio_conf.mode = GPIO_MODE_OUTPUT;
   gpio_conf.pin_bit_mask = 0x1ULL << GPIO_NUM_8;
   gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
   gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
   gpio_config(&gpio_conf);
   gpio_set_level(GPIO_NUM_8, true);
 }
 
 // -------------------------- Sensor Detection Functions --------------------------
 
 bool wifi_ap_init() {
   Serial.println("Initializing WiFi AP...");
   gpio_init_wifi();
   
   const char *ssid = "W10";
   const char *password = "12345678";
   
   bool result = WiFi.softAP(ssid, password);
   if (result) {
     Serial.print("WiFi AP started. IP: ");
     Serial.println(WiFi.softAPIP());
     return true;
   } else {
     Serial.println("WiFi AP start failed!");
     return false;
   }
 }
 
 // -------------------------- QMI8658 (IMU) Functions --------------------------
 bool qmi8658_init() {
   // Ensure I2C is at 400kHz for QMI8658 (it needs higher speed)
   Wire.setClock(400000);
   delay(100);
 
   if (!qmi.begin(Wire, QMI8658_L_SLAVE_ADDRESS, I2C_SDA_PIN, I2C_SCL_PIN)) {
     Serial.println("QMI8658 not found!");
     return false;
   }
 
   qmi.configAccelerometer(SensorQMI8658::ACC_RANGE_4G, SensorQMI8658::ACC_ODR_125Hz, SensorQMI8658::LPF_MODE_0);
   qmi.configGyroscope(SensorQMI8658::GYR_RANGE_64DPS, SensorQMI8658::GYR_ODR_448_4Hz, SensorQMI8658::LPF_MODE_3);
   qmi.enableAccelerometer();
   qmi.enableGyroscope();
   Serial.println("QMI8658 initialized");
   return true;
 }
 
 void read_qmi8658(char *accel_buf, char *gyro_buf, char *temp_buf) {
   bool accel_ok = qmi.getAccelerometer(qmiData.ax, qmiData.ay, qmiData.az);
   bool gyro_ok = qmi.getGyroscope(qmiData.gx, qmiData.gy, qmiData.gz);
   qmiData.temp = qmi.getTemperature_C();
   bool temp_ok = !isnan(qmiData.temp) && isfinite(qmiData.temp);
   qmiData.isValid = (accel_ok && gyro_ok) && !(qmiData.ax == 0 && qmiData.ay == 0 && qmiData.az == 0 && qmiData.gx == 0 && qmiData.gy == 0 && qmiData.gz == 0);
 
   if (qmiData.isValid) {
     sprintf(accel_buf, "Accel: X:%.4f Y:%.4f Z:%.4f", qmiData.ax, qmiData.ay, qmiData.az);
     sprintf(gyro_buf, "Gyro:  X:%.4f Y:%.4f Z:%.4f", qmiData.gx, qmiData.gy, qmiData.gz);
   } else {
     strcpy(accel_buf, "Accel: Read failed");
     strcpy(gyro_buf, "Gyro:  Read failed");
   }
   
   if (temp_ok) {
     sprintf(temp_buf, "Temp: %.3f °C", qmiData.temp);
   } else {
     strcpy(temp_buf, "Temp: --.- °C");
   }
 }
 
 // -------------------------- SHT41 (Temp/Hum) Functions --------------------------
 uint8_t crc8(const uint8_t* data, uint8_t len) {
   const uint8_t polynomial = 0x31;
   uint8_t crc = 0xFF;
   for (uint8_t i = 0; i < len; i++) {
     crc ^= data[i];
     for (uint8_t j = 0; j < 8; j++) {
       crc = (crc & 0x80) ? (crc << 1) ^ polynomial : (crc << 1);
     }
   }
   return crc;
 }
 
 bool sht41_init() {
   sht41Data.detectedAddr = 0;
   Wire.beginTransmission(SHT41_ADDR_44);
   if (Wire.endTransmission() == 0) {
     sht41Data.detectedAddr = SHT41_ADDR_44;
   } else {
     Wire.beginTransmission(SHT41_ADDR_45);
     if (Wire.endTransmission() == 0) {
       sht41Data.detectedAddr = SHT41_ADDR_45;
     }
   }
 
   if (sht41Data.detectedAddr != 0) {
     Wire.beginTransmission(sht41Data.detectedAddr);
     Wire.write(SHT41_CMD_SOFT_RESET);
     Wire.endTransmission();
     delay(500);
     Serial.printf("SHT41 initialized at 0x%02X\n", sht41Data.detectedAddr);
     return true;
   } else {
     Serial.println("SHT41 not found!");
     return false;
   }
 }
 
 void read_sht41(char *temp_buf, char *hum_buf) {
   if (sht41Data.detectedAddr == 0) {
     strcpy(temp_buf, "Temp: No Data");
     strcpy(hum_buf, "Hum:  No Data");
     return;
   }
 
   uint8_t dataBuffer[6];
   bool cmdSent = false;
   for (int retry = 0; retry < COMM_RETRY_COUNT; retry++) {
     Wire.beginTransmission(sht41Data.detectedAddr);
     Wire.write(SHT41_CMD_MEASURE_HIGH);
     if (Wire.endTransmission() == 0) {
       cmdSent = true;
       break;
     }
     delay(100);
   }
   if (!cmdSent) {
     strcpy(temp_buf, "Temp: Cmd Fail");
     strcpy(hum_buf, "Hum:  Cmd Fail");
     return;
   }
 
   delay(MEASURE_DELAY);
   if (Wire.requestFrom(sht41Data.detectedAddr, 6) != 6) {
     strcpy(temp_buf, "Temp: Read Fail");
     strcpy(hum_buf, "Hum:  Read Fail");
     return;
   }
 
   for (int i = 0; i < 6; i++) dataBuffer[i] = Wire.read();
   if (crc8(dataBuffer, 2) != dataBuffer[2] || crc8(dataBuffer + 3, 2) != dataBuffer[5]) {
     strcpy(temp_buf, "Temp: CRC Fail");
     strcpy(hum_buf, "Hum:  CRC Fail");
     return;
   }
 
   uint16_t tempRaw = (dataBuffer[0] << 8) | dataBuffer[1];
   uint16_t humRaw = (dataBuffer[3] << 8) | dataBuffer[4];
   sht41Data.temperature = -45.0 + 175.0 * (float)tempRaw / 65535.0;
   sht41Data.humidity = 0.0 + 100.0 * (float)humRaw / 65535.0;
 
   sprintf(temp_buf, "Temp: %.3f °C", sht41Data.temperature);
   sprintf(hum_buf, "Hum:  %.3f %%RH", sht41Data.humidity);
 }
 
 // -------------------------- LVGL Display Flush --------------------------
 void my_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
   uint32_t w = area->x2 - area->x1 + 1;
   uint32_t h = area->y2 - area->y1 + 1;
 #if (LV_COLOR_16_SWAP != 0)
   gfx->draw16bitBeRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
 #else
   gfx->draw16bitRGBBitmap(area->x1, area->y1, (uint16_t *)&color_p->full, w, h);
 #endif
   lv_disp_flush_ready(disp_drv);
 }
 
 // -------------------------- Detection Screen UI Initialization --------------------------
 void lvgl_detect_ui_init() {
   // Create Detection Screen
   screen_detect = lv_obj_create(NULL);
   lv_obj_set_style_bg_color(screen_detect, lv_color_black(), 0);
   lv_obj_set_style_bg_opa(screen_detect, LV_OPA_COVER, 0);
   lv_scr_load(screen_detect);
 
   // Title Style
   static lv_style_t detect_title_style;
   lv_style_init(&detect_title_style);
   lv_style_set_text_color(&detect_title_style, lv_color_white());
   lv_style_set_text_font(&detect_title_style, &lv_font_montserrat_16);
 
   // Label Style (initialize global static style)
   lv_style_init(&detect_label_style);
   lv_style_set_text_color(&detect_label_style, lv_color_white());
   lv_style_set_text_font(&detect_label_style, &lv_font_montserrat_14);
   
   // Red Warning Style for button prompt
   static lv_style_t detect_warning_style;
   lv_style_init(&detect_warning_style);
   lv_style_set_text_color(&detect_warning_style, lv_color_hex(0xFF0000));
   lv_style_set_text_font(&detect_warning_style, &lv_font_montserrat_14);
 
   // Title
   label_detect_title = lv_label_create(screen_detect);
   lv_obj_add_style(label_detect_title, &detect_title_style, 0);
   lv_label_set_text(label_detect_title, "Sensor Detection");
   lv_obj_align(label_detect_title, LV_ALIGN_TOP_MID, 0, 10);
 }
 
 
 // -------------------------- LVGL UI Initialization --------------------------
 void lvgl_ui_init() {
   // Create Main Screen
   screen_main = lv_obj_create(NULL);
   lv_obj_set_style_bg_color(screen_main, lv_color_black(), 0);
   lv_obj_set_style_bg_opa(screen_main, LV_OPA_COVER, 0);
   // Use main screen
   lv_obj_t *scr = screen_main;
 
   // Common Styles
   static lv_style_t title_style_blue;    // PCF85063
   static lv_style_t title_style_green;   // Quectel L76K GPS
   static lv_style_t title_style_yellow;  // QMI8658
   static lv_style_t title_style_red;     // SHT41
   static lv_style_t data_style;          // Data Labels
 
   // Title Style: Bold + Colored + 14px
   lv_style_init(&title_style_blue);
   lv_style_set_text_color(&title_style_blue, lv_color_hex(0x0000FF));
   lv_style_set_text_font(&title_style_blue, &lv_font_montserrat_16);
   lv_style_set_text_align(&title_style_blue, LV_TEXT_ALIGN_LEFT);
 
   lv_style_init(&title_style_green);
   lv_style_set_text_color(&title_style_green, lv_color_hex(0x00FF00));
   lv_style_set_text_font(&title_style_green, &lv_font_montserrat_16);
   lv_style_set_text_align(&title_style_green, LV_TEXT_ALIGN_RIGHT);
 
   lv_style_init(&title_style_yellow);
   lv_style_set_text_color(&title_style_yellow, lv_color_hex(0xFF9900));
   lv_style_set_text_font(&title_style_yellow, &lv_font_montserrat_16);
   lv_style_set_text_align(&title_style_yellow, LV_TEXT_ALIGN_LEFT);
 
   lv_style_init(&title_style_red);
   lv_style_set_text_color(&title_style_red, lv_color_hex(0xFF0000));
   lv_style_set_text_font(&title_style_red, &lv_font_montserrat_16);
   lv_style_set_text_align(&title_style_red, LV_TEXT_ALIGN_RIGHT);
 
   // Data Style: White + 12px + Left/Right Align
   lv_style_init(&data_style);
   lv_style_set_text_color(&data_style, lv_color_white());
   lv_style_set_text_font(&data_style, &lv_font_montserrat_12);
 
   // Border Style for sections
   static lv_style_t border_style;
   lv_style_init(&border_style);
   lv_style_set_border_color(&border_style, lv_color_white());
   lv_style_set_border_width(&border_style, 1);
   lv_style_set_bg_opa(&border_style, LV_OPA_TRANSP);
   lv_style_set_pad_all(&border_style, 0);
 
  // Main Screen Title and MAC Address (Same Line)
  static lv_style_t main_title_style;
  lv_style_init(&main_title_style);
  lv_style_set_text_color(&main_title_style, lv_color_white());
  lv_style_set_text_font(&main_title_style, &lv_font_montserrat_16);
  lv_obj_t *label_main_title = lv_label_create(scr);
  lv_obj_add_style(label_main_title, &main_title_style, 0);
  lv_label_set_text(label_main_title, "Main Screen");
  lv_obj_align(label_main_title, LV_ALIGN_TOP_LEFT, 5, 5);  // Left side

  // Get MAC address
  uint8_t mac[6];
  esp_err_t mac_err = esp_efuse_mac_get_default(mac);
  char mac_str[25];
  if (mac_err == ESP_OK) {
    snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X", 
             mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  } else {
    strcpy(mac_str, "N/A");
  }

  // MAC address style (Cyan color, smaller font)
  static lv_style_t mac_style;
  lv_style_init(&mac_style);
  lv_style_set_text_color(&mac_style, lv_color_hex(0x00FFFF));
  lv_style_set_text_font(&mac_style, &lv_font_montserrat_12);
  lv_style_set_text_align(&mac_style, LV_TEXT_ALIGN_RIGHT);

  label_main_mac_address = lv_label_create(scr);
  lv_obj_add_style(label_main_mac_address, &mac_style, 0);
  lv_label_set_text(label_main_mac_address, mac_str);
  lv_obj_align(label_main_mac_address, LV_ALIGN_TOP_RIGHT, -5, 5);  // Right side, same Y as title
  
  Serial.printf("MAC Address displayed: %s\n", mac_str);

  // -------------------------- QMI8658 (Top) --------------------------
   // Create container for IMU section with border
   lv_obj_t *imu_container = lv_obj_create(scr);
   lv_obj_add_style(imu_container, &border_style, 0);
   lv_obj_set_size(imu_container, 240, 70);  
   lv_obj_align(imu_container, LV_ALIGN_TOP_LEFT, 0, 30);  // Original position  
   lv_obj_clear_flag(imu_container, LV_OBJ_FLAG_SCROLLABLE);
 
   label_qmi_title = lv_label_create(imu_container);
   lv_obj_add_style(label_qmi_title, &title_style_yellow, 0);
   lv_label_set_text(label_qmi_title, "IMU");
   lv_obj_align(label_qmi_title, LV_ALIGN_TOP_MID, 0, 5); 
 
   label_qmi_accel = lv_label_create(imu_container);
   lv_obj_add_style(label_qmi_accel, &data_style, 0);
   lv_label_set_text(label_qmi_accel, "Accel: X--.- Y--.- Z--.-");
   lv_obj_align(label_qmi_accel, LV_ALIGN_TOP_LEFT, 5, 25);
 
   label_qmi_gyro = lv_label_create(imu_container);
   lv_obj_add_style(label_qmi_gyro, &data_style, 0);
   lv_label_set_text(label_qmi_gyro, "Gyro:  X--.- Y--.- Z--.-");
   lv_obj_align(label_qmi_gyro, LV_ALIGN_TOP_LEFT, 5, 45);
 
   label_qmi_temp = lv_label_create(imu_container);
   lv_obj_add_style(label_qmi_temp, &data_style, 0);
   lv_label_set_text(label_qmi_temp, "Temp: --.- °C");
   lv_obj_align(label_qmi_temp, LV_ALIGN_TOP_LEFT, 5, 65);
 
   // -------------------------- PCF85063 (Middle-Left) --------------------------
   // Create container for RTC section with border
   lv_obj_t *rtc_container = lv_obj_create(scr);
   lv_obj_add_style(rtc_container, &border_style, 0);
   lv_obj_set_size(rtc_container, 130, 50); 
   lv_obj_align(rtc_container, LV_ALIGN_TOP_LEFT, 0, 100);  // Original position 
   lv_obj_clear_flag(rtc_container, LV_OBJ_FLAG_SCROLLABLE);
 
   label_rtc_title = lv_label_create(rtc_container);
   lv_obj_add_style(label_rtc_title, &title_style_blue, 0);
   lv_label_set_text(label_rtc_title, "RTC");
   lv_obj_align(label_rtc_title, LV_ALIGN_TOP_MID, 0, 0); 
 
   label_rtc_datetime = lv_label_create(rtc_container);
   lv_obj_add_style(label_rtc_datetime, &data_style, 0);
   lv_label_set_text(label_rtc_datetime, "2025-01-01 12:00:00");
   lv_obj_align(label_rtc_datetime, LV_ALIGN_TOP_LEFT, 5, 18);
 
   label_rtc_weekday = lv_label_create(rtc_container);
   lv_obj_add_style(label_rtc_weekday, &data_style, 0);
   lv_label_set_text(label_rtc_weekday, "Wednesday");
   lv_obj_align(label_rtc_weekday, LV_ALIGN_TOP_LEFT, 5, 33);
 
  // -------------------------- Quectel L76K GPS (Middle-Right) --------------------------
  // Create container for GPS section with border
  lv_obj_t *gps_container = lv_obj_create(scr);
  lv_obj_add_style(gps_container, &border_style, 0);
  lv_obj_set_size(gps_container, 110, 100);
  lv_obj_align(gps_container, LV_ALIGN_TOP_RIGHT, 0, 100);  // Original position
   lv_obj_clear_flag(gps_container, LV_OBJ_FLAG_SCROLLABLE);
 
   label_gps_title = lv_label_create(gps_container);
   lv_obj_add_style(label_gps_title, &title_style_green, 0);
   lv_label_set_text(label_gps_title, "GPS");
   lv_obj_align(label_gps_title, LV_ALIGN_TOP_RIGHT, -35, 0);
 
   label_gps_time = lv_label_create(gps_container);
   lv_obj_add_style(label_gps_time, &data_style, 0);
   lv_label_set_text(label_gps_time, "Time: --:--:--");
   lv_obj_set_style_text_align(label_gps_time, LV_TEXT_ALIGN_RIGHT, 0);
   lv_obj_align(label_gps_time, LV_ALIGN_TOP_RIGHT, -5, 17);
 
   label_gps_lat = lv_label_create(gps_container);
   lv_obj_add_style(label_gps_lat, &data_style, 0);
   lv_label_set_text(label_gps_lat, "Lat: --");
   lv_obj_set_style_text_align(label_gps_lat, LV_TEXT_ALIGN_RIGHT, 0);
   lv_obj_align(label_gps_lat, LV_ALIGN_TOP_RIGHT, -5, 32);
 
   label_gps_lon = lv_label_create(gps_container);
   lv_obj_add_style(label_gps_lon, &data_style, 0);
   lv_label_set_text(label_gps_lon, "Lon: --");
   lv_obj_set_style_text_align(label_gps_lon, LV_TEXT_ALIGN_RIGHT, 0);
   lv_obj_align(label_gps_lon, LV_ALIGN_TOP_RIGHT, -5, 47);
 
   label_gps_sat = lv_label_create(gps_container);
   lv_obj_add_style(label_gps_sat, &data_style, 0);
   lv_label_set_text(label_gps_sat, "Sat: 0");
   lv_obj_set_style_text_align(label_gps_sat, LV_TEXT_ALIGN_RIGHT, 0);
   lv_obj_align(label_gps_sat, LV_ALIGN_TOP_RIGHT, -5, 62);
 
   label_gps_alt = lv_label_create(gps_container);
   lv_obj_add_style(label_gps_alt, &data_style, 0);
   lv_label_set_text(label_gps_alt, "Alt: -- m");
   lv_obj_set_style_text_align(label_gps_alt, LV_TEXT_ALIGN_RIGHT, 0);
   lv_obj_align(label_gps_alt, LV_ALIGN_TOP_RIGHT, -5, 77);
 
   // -------------------------- SHT41 (Bottom-Left) --------------------------
   // Create container for SHT41 section with border
   lv_obj_t *sht_container = lv_obj_create(scr);
   lv_obj_add_style(sht_container, &border_style, 0);
   lv_obj_set_size(sht_container, 130, 50); 
   lv_obj_align(sht_container, LV_ALIGN_TOP_LEFT, 0, 150);  // Original position 
   lv_obj_clear_flag(sht_container, LV_OBJ_FLAG_SCROLLABLE);
 
   label_sht_title = lv_label_create(sht_container);
   lv_obj_add_style(label_sht_title, &title_style_red, 0);
   lv_label_set_text(label_sht_title, "Temp/Hum");
   lv_obj_align(label_sht_title, LV_ALIGN_TOP_MID, 0, 0);
 
   label_sht_temp = lv_label_create(sht_container);
   lv_obj_add_style(label_sht_temp, &data_style, 0);
   lv_label_set_text(label_sht_temp, "Temp: --.- °C");
   lv_obj_align(label_sht_temp, LV_ALIGN_TOP_LEFT, 5, 18);
 
  label_sht_hum = lv_label_create(sht_container);
  lv_obj_add_style(label_sht_hum, &data_style, 0);
  lv_label_set_text(label_sht_hum, "Hum:  --.- %RH");
  lv_obj_align(label_sht_hum, LV_ALIGN_TOP_LEFT, 5, 33);

  // -------------------------- Main Screen Bottom Prompt --------------------------
   // Create container for bottom prompt with border
   lv_obj_t *prompt_container = lv_obj_create(scr);
   lv_obj_add_style(prompt_container, &border_style, 0);
   lv_obj_set_size(prompt_container, 240, 40); 
   lv_obj_align(prompt_container, LV_ALIGN_BOTTOM_LEFT, 0, 0);
   lv_obj_clear_flag(prompt_container, LV_OBJ_FLAG_SCROLLABLE);
 
   static lv_style_t lora_prompt_style;
   lv_style_init(&lora_prompt_style);
   lv_style_set_text_color(&lora_prompt_style, lv_color_hex(0xFFFFFF));  // White color
   lv_style_set_text_font(&lora_prompt_style, &lv_font_montserrat_12);
   lv_style_set_text_align(&lora_prompt_style, LV_TEXT_ALIGN_CENTER);  // Center align text
   
   label_main_lora_prompt = lv_label_create(prompt_container);
   lv_obj_add_style(label_main_lora_prompt, &lora_prompt_style, 0);
   lv_label_set_long_mode(label_main_lora_prompt, LV_LABEL_LONG_WRAP);  // Enable multi-line text
   lv_obj_set_width(label_main_lora_prompt, 230);  // Set width to allow text wrapping
   lv_label_set_text(label_main_lora_prompt, "Boot button short press: LoRa TX\nBoot button long press: LoRa RX");
   lv_obj_align(label_main_lora_prompt, LV_ALIGN_CENTER, 0, 0); 
 }
 
 // Callback function for LoRa timer 
static void lora_update_callback(lv_timer_t *timer) {
  // Check current screen - only update UI if we're on the corresponding screen
  lv_obj_t *current_screen = lv_scr_act();
  bool is_tx_screen = (current_screen == screen_lora_tx);
  bool is_rx_screen = (current_screen == screen_lora_rx);
  
  // If not on TX or RX screen, don't update UI
  if (!is_tx_screen && !is_rx_screen) {
    return;
  }
  
  String status_text = loraData.status;
  if (loraData.tx_mode) {
    status_text += " [TX]";
  } else if (loraData.rx_mode) {
    status_text += " [RX]";
  }
  if (label_lora_status) lv_label_set_text(label_lora_status, status_text.c_str());
   
   // Update frequency, power, and mode (use fixed frequency value)
   // Frequency is set to 915/868MHz - display the configured value
   String freq_display = (loraData.frequency.length() > 0) ? loraData.frequency : "915/868MHz";
   String power_display = (loraData.power.length() > 0) ? loraData.power : "Unknown";
   String mode_display = (loraData.mode.length() > 0) ? loraData.mode : "Unknown";
   
   if (label_lora_freq) lv_label_set_text_fmt(label_lora_freq, "Freq: %s", freq_display.c_str());
   if (label_lora_power) lv_label_set_text_fmt(label_lora_power, "Power: %s", power_display.c_str());
   if (label_lora_mode) lv_label_set_text_fmt(label_lora_mode, "Mode: %s", mode_display.c_str());
   
 
  // Display data instead of counts
  if (loraData.tx_mode) {
    if (label_lora_tx_count) {
      if (loraData.last_message.length() > 0 && loraData.last_message != "None") {
        lv_label_set_text_fmt(label_lora_tx_count, "Data: %s", loraData.last_message.c_str());
      } else {
        lv_label_set_text(label_lora_tx_count, "Data: --");
      }
    }
    if (label_lora_rx_count) lv_label_set_text(label_lora_rx_count, "");  
  } else if (loraData.rx_mode) {
    // RX mode: only show one Data label with received data
    if (label_lora_rx_count) {
      if (loraData.last_message.length() > 0 && loraData.last_message != "None") {
        lv_label_set_text_fmt(label_lora_rx_count, "Data: %s", loraData.last_message.c_str());
      } else {
        lv_label_set_text(label_lora_rx_count, "Data: --");
      }
    }
  } else {
    if (label_lora_tx_count) {
      if (loraData.last_message.length() > 0 && loraData.last_message != "None") {
        lv_label_set_text_fmt(label_lora_tx_count, "Data: %s", loraData.last_message.c_str());
      } else {
        lv_label_set_text(label_lora_tx_count, "Data: --");
      }
    }
    if (label_lora_rx_count) {
      if (loraData.last_message.length() > 0 && loraData.last_message != "None") {
        lv_label_set_text_fmt(label_lora_rx_count, "Data: %s", loraData.last_message.c_str());
      } else {
        lv_label_set_text(label_lora_rx_count, "Data: --");
      }
    }
  }
   
   if (label_lora_rssi) {
     if (loraData.rssi != 0) {
       lv_label_set_text_fmt(label_lora_rssi, "RSSI: %d dBm", loraData.rssi);
     } else {
       lv_label_set_text(label_lora_rssi, "RSSI: --");
     }
   }
   
   static unsigned long last_ui_update = 0;
   if (millis() - last_ui_update > 1000) {  
     Serial.printf("UI: rx_mode=%s, tx_mode=%s, last_message='%s' (len=%d)\n",
                   loraData.rx_mode ? "true" : "false",
                   loraData.tx_mode ? "true" : "false",
                   loraData.last_message.c_str(),
                   loraData.last_message.length());
     last_ui_update = millis();
   }
 
  // Update message display based on current screen and mode
  // RX mode: Data is already displayed in label_lora_rx_count, no need for separate message label
  if (is_tx_screen && loraData.tx_mode) {
    if (label_lora_message) {
      lv_label_set_text(label_lora_message, "TX: Sending...");
    }
  } else if (is_tx_screen || is_rx_screen) {
    // Only update "Last: ..." message if we're on TX or RX screen but not in active mode
    String display_msg = loraData.last_message;
    if (display_msg.length() > 15) {
      display_msg = display_msg.substring(0, 12) + "...";
    }

    if (label_lora_message) {
      char msg_buf[64];
      if (display_msg.length() > 0 && display_msg != "None") {
        snprintf(msg_buf, sizeof(msg_buf), "Last: %s", display_msg.c_str());
      } else {
        snprintf(msg_buf, sizeof(msg_buf), "Last: None");
      }
      lv_label_set_text(label_lora_message, msg_buf);
    } else {
      Serial.println("UI: ERROR - label_lora_message is NULL!");
    }
  }
 }
 
 void lvgl_lora_tx_ui_init() {
   // Create LoRa TX screen (only if not already created)
   if (screen_lora_tx == NULL) {
     screen_lora_tx = lv_obj_create(NULL);
     lv_obj_set_style_bg_color(screen_lora_tx, lv_color_black(), 0); 
     lv_obj_set_style_bg_opa(screen_lora_tx, LV_OPA_COVER, 0);
   }
   
   // Create styles
   static lv_style_t title_style;
   lv_style_init(&title_style);
   lv_style_set_text_color(&title_style, lv_color_white());
   lv_style_set_text_font(&title_style, &lv_font_montserrat_20);
   lv_style_set_text_align(&title_style, LV_TEXT_ALIGN_CENTER);
   
   static lv_style_t status_style;
   lv_style_init(&status_style);
   lv_style_set_text_color(&status_style, lv_color_make(100, 255, 100));  // Light green
   lv_style_set_text_font(&status_style, &lv_font_montserrat_18);
   lv_style_set_text_align(&status_style, LV_TEXT_ALIGN_LEFT);
   
   static lv_style_t info_style;
   lv_style_init(&info_style);
   lv_style_set_text_color(&info_style, lv_color_make(150, 200, 255));  // Light blue
   lv_style_set_text_font(&info_style, &lv_font_montserrat_16);
   lv_style_set_text_align(&info_style, LV_TEXT_ALIGN_LEFT);
   
   static lv_style_t count_style;
   lv_style_init(&count_style);
   lv_style_set_text_color(&count_style, lv_color_make(255, 200, 100));  // Light yellow
   lv_style_set_text_font(&count_style, &lv_font_montserrat_16);
   lv_style_set_text_align(&count_style, LV_TEXT_ALIGN_LEFT);
   
   static lv_style_t msg_style;
   lv_style_init(&msg_style);
   lv_style_set_text_color(&msg_style, lv_color_make(255, 150, 150));  // Light red
   lv_style_set_text_font(&msg_style, &lv_font_montserrat_16);
   lv_style_set_text_align(&msg_style, LV_TEXT_ALIGN_LEFT);
   
   // Create title
   label_lora_title = lv_label_create(screen_lora_tx);
   lv_obj_add_style(label_lora_title, &title_style, 0);
   lv_label_set_text(label_lora_title, "LoRa Test [TX]");
   lv_obj_align(label_lora_title, LV_ALIGN_TOP_MID, 0, 5);
   lv_obj_set_width(label_lora_title, LCD_HOR_RES - 10);
   
   // Create status label
   label_lora_status = lv_label_create(screen_lora_tx);
   lv_obj_add_style(label_lora_status, &status_style, 0);
   lv_label_set_text(label_lora_status, "Initializing...");
   lv_obj_align(label_lora_status, LV_ALIGN_TOP_LEFT, 5, 30);
   lv_obj_set_width(label_lora_status, LCD_HOR_RES - 10);
   
   // Create frequency label 
   label_lora_freq = lv_label_create(screen_lora_tx);
   lv_obj_add_style(label_lora_freq, &info_style, 0);
   lv_label_set_text(label_lora_freq, "Freq: --");
   lv_obj_align(label_lora_freq, LV_ALIGN_TOP_LEFT, 5, 50);
   lv_obj_set_width(label_lora_freq, LCD_HOR_RES - 10);
   
   // Create power label
   label_lora_power = lv_label_create(screen_lora_tx);
   lv_obj_add_style(label_lora_power, &info_style, 0);
   lv_label_set_text(label_lora_power, "Power: --");
   lv_obj_align(label_lora_power, LV_ALIGN_TOP_LEFT, 5, 70);
   lv_obj_set_width(label_lora_power, LCD_HOR_RES - 10);
   
   // Create mode label
   label_lora_mode = lv_label_create(screen_lora_tx);
   lv_obj_add_style(label_lora_mode, &info_style, 0);
   lv_label_set_text(label_lora_mode, "Mode: --");
   lv_obj_align(label_lora_mode, LV_ALIGN_TOP_LEFT, 5, 90);
   lv_obj_set_width(label_lora_mode, LCD_HOR_RES - 10);
   
  // Create TX data label
  label_lora_tx_count = lv_label_create(screen_lora_tx);
  lv_obj_add_style(label_lora_tx_count, &count_style, 0);
  lv_label_set_text(label_lora_tx_count, "Data: --");
  lv_obj_align(label_lora_tx_count, LV_ALIGN_TOP_LEFT, 5, 110);
  lv_obj_set_width(label_lora_tx_count, 100);
  
  // Create RX data label 
  label_lora_rx_count = lv_label_create(screen_lora_tx);
  lv_obj_add_style(label_lora_rx_count, &count_style, 0);
  lv_label_set_text(label_lora_rx_count, "Data: --");
  lv_obj_align(label_lora_rx_count, LV_ALIGN_TOP_LEFT, 120, 110);
  lv_obj_set_width(label_lora_rx_count, 100);
   
   // Create RSSI label
   label_lora_rssi = lv_label_create(screen_lora_tx);
   lv_obj_add_style(label_lora_rssi, &info_style, 0);
   lv_label_set_text(label_lora_rssi, "RSSI: --");
   lv_obj_align(label_lora_rssi, LV_ALIGN_TOP_LEFT, 5, 130);
   lv_obj_set_width(label_lora_rssi, LCD_HOR_RES - 10);
   
   // Create last message label
   label_lora_message = lv_label_create(screen_lora_tx);
   lv_obj_add_style(label_lora_message, &msg_style, 0);
   lv_label_set_text(label_lora_message, "Last: --");
   lv_obj_align(label_lora_message, LV_ALIGN_TOP_LEFT, 5, 150);
   lv_obj_set_width(label_lora_message, LCD_HOR_RES - 10);
   
   // Create bottom info label 
   lv_obj_t *info_label = lv_label_create(screen_lora_tx);
   static lv_style_t bottom_style;
   lv_style_init(&bottom_style);
   lv_style_set_text_color(&bottom_style, lv_color_make(100, 100, 100));
   lv_style_set_text_font(&bottom_style, &lv_font_montserrat_14);
   lv_style_set_text_align(&bottom_style, LV_TEXT_ALIGN_CENTER);
   lv_obj_add_style(info_label, &bottom_style, 0);
   lv_label_set_text(info_label, "E22-900MM22S | 850-930MHz");
   lv_obj_align(info_label, LV_ALIGN_BOTTOM_MID, 0, -5);
   lv_obj_set_width(info_label, LCD_HOR_RES - 10);
   
   // Create timer to update display (500ms interval)
   if (lora_timer == NULL) {
     lora_timer = lv_timer_create(lora_update_callback, 500, NULL);
     lv_timer_ready(lora_timer);
   }
 }
 
void lvgl_lora_rx_ui_init() {
  // Create LoRa RX screen (only if not already created)
  if (screen_lora_rx == NULL) {
    screen_lora_rx = lv_obj_create(NULL);
    lv_obj_set_style_bg_color(screen_lora_rx, lv_color_black(), 0); 
    lv_obj_set_style_bg_opa(screen_lora_rx, LV_OPA_COVER, 0);
  }
  
  // Clear previous received data when initializing RX screen
  loraData.last_message = "None";
   
   // Create styles 
   static lv_style_t title_style;
   lv_style_init(&title_style);
   lv_style_set_text_color(&title_style, lv_color_white());
   lv_style_set_text_font(&title_style, &lv_font_montserrat_20);
   lv_style_set_text_align(&title_style, LV_TEXT_ALIGN_CENTER);
   
   static lv_style_t status_style;
   lv_style_init(&status_style);
   lv_style_set_text_color(&status_style, lv_color_make(100, 255, 100));  // Light green
   lv_style_set_text_font(&status_style, &lv_font_montserrat_18);
   lv_style_set_text_align(&status_style, LV_TEXT_ALIGN_LEFT);
   
   static lv_style_t info_style;
   lv_style_init(&info_style);
   lv_style_set_text_color(&info_style, lv_color_make(150, 200, 255));  // Light blue
   lv_style_set_text_font(&info_style, &lv_font_montserrat_16);
   lv_style_set_text_align(&info_style, LV_TEXT_ALIGN_LEFT);
   
   static lv_style_t count_style;
   lv_style_init(&count_style);
   lv_style_set_text_color(&count_style, lv_color_make(255, 200, 100));  // Light yellow
   lv_style_set_text_font(&count_style, &lv_font_montserrat_16);
   lv_style_set_text_align(&count_style, LV_TEXT_ALIGN_LEFT);
   
   static lv_style_t msg_style;
   lv_style_init(&msg_style);
   lv_style_set_text_color(&msg_style, lv_color_make(255, 150, 150));  // Light red
   lv_style_set_text_font(&msg_style, &lv_font_montserrat_16);
   lv_style_set_text_align(&msg_style, LV_TEXT_ALIGN_LEFT);
   
   // Create title
   label_lora_title = lv_label_create(screen_lora_rx);
   lv_obj_add_style(label_lora_title, &title_style, 0);
   lv_label_set_text(label_lora_title, "LoRa Test [RX]");
   lv_obj_align(label_lora_title, LV_ALIGN_TOP_MID, 0, 5);
   lv_obj_set_width(label_lora_title, LCD_HOR_RES - 10);
   
   // Create status label
   label_lora_status = lv_label_create(screen_lora_rx);
   lv_obj_add_style(label_lora_status, &status_style, 0);
   lv_label_set_text(label_lora_status, "Initializing...");
   lv_obj_align(label_lora_status, LV_ALIGN_TOP_LEFT, 5, 30);
   lv_obj_set_width(label_lora_status, LCD_HOR_RES - 10);
   
   // Create frequency label
   label_lora_freq = lv_label_create(screen_lora_rx);
   lv_obj_add_style(label_lora_freq, &info_style, 0);
   lv_label_set_text(label_lora_freq, "Freq: --");
   lv_obj_align(label_lora_freq, LV_ALIGN_TOP_LEFT, 5, 50);
   lv_obj_set_width(label_lora_freq, LCD_HOR_RES - 10);
   
   // Create power label
   label_lora_power = lv_label_create(screen_lora_rx);
   lv_obj_add_style(label_lora_power, &info_style, 0);
   lv_label_set_text(label_lora_power, "Power: --");
   lv_obj_align(label_lora_power, LV_ALIGN_TOP_LEFT, 5, 70);
   lv_obj_set_width(label_lora_power, LCD_HOR_RES - 10);
   
  // Create mode label
  label_lora_mode = lv_label_create(screen_lora_rx);
  lv_obj_add_style(label_lora_mode, &info_style, 0);
  lv_label_set_text(label_lora_mode, "Mode: --");
  lv_obj_align(label_lora_mode, LV_ALIGN_TOP_LEFT, 5, 90);
  lv_obj_set_width(label_lora_mode, LCD_HOR_RES - 10);
  
 // Create Data label (only one for RX mode, shows received data)
 label_lora_rx_count = lv_label_create(screen_lora_rx);
 lv_obj_add_style(label_lora_rx_count, &count_style, 0);
 lv_label_set_text(label_lora_rx_count, "Data: --");
 lv_obj_align(label_lora_rx_count, LV_ALIGN_TOP_LEFT, 5, 110);
 lv_obj_set_width(label_lora_rx_count, LCD_HOR_RES - 10);
   
   // Create RSSI label
   label_lora_rssi = lv_label_create(screen_lora_rx);
   lv_obj_add_style(label_lora_rssi, &info_style, 0);
   lv_label_set_text(label_lora_rssi, "RSSI: --");
   lv_obj_align(label_lora_rssi, LV_ALIGN_TOP_LEFT, 5, 130);
   lv_obj_set_width(label_lora_rssi, LCD_HOR_RES - 10);
   
   // Create bottom info label
   lv_obj_t *info_label = lv_label_create(screen_lora_rx);
   static lv_style_t bottom_style;
   lv_style_init(&bottom_style);
   lv_style_set_text_color(&bottom_style, lv_color_make(100, 100, 100));
   lv_style_set_text_font(&bottom_style, &lv_font_montserrat_14);
   lv_style_set_text_align(&bottom_style, LV_TEXT_ALIGN_CENTER);
   lv_obj_add_style(info_label, &bottom_style, 0);
   lv_label_set_text(info_label, "E22-900MM22S | 850-930MHz");
   lv_obj_align(info_label, LV_ALIGN_BOTTOM_MID, 0, -5);
   lv_obj_set_width(info_label, LCD_HOR_RES - 10);
   
   // Create timer to update display (500ms interval) 
   if (lora_timer == NULL) {
     lora_timer = lv_timer_create(lora_update_callback, 500, NULL);
     lv_timer_ready(lora_timer);
   }
 }
 
// -------------------------- Sensor Detection Function --------------------------
void detect_all_sensors() {
  // I2C Init
  pinMode(I2C_SDA_PIN, INPUT_PULLUP);
  pinMode(I2C_SCL_PIN, INPUT_PULLUP);
  Wire.end();
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  Wire.setClock(100000);
  Wire.setTimeout(1000000);
  delay(300);
  Serial.println("I2C initialized (100kHz)");

  // Update LVGL to show detection screen
  lv_timer_handler();
  delay(100);

  // Feed watchdog before detection
  esp_task_wdt_reset();

  // Speaker test - Play three beeps before detection starts
  int detect_index = 0;
  lv_obj_t *label_detect_speaker = lv_label_create(screen_detect);
  lv_obj_add_style(label_detect_speaker, &detect_label_style, 0);
  lv_label_set_text(label_detect_speaker, "Speaker: Testing...");
  lv_obj_align(label_detect_speaker, LV_ALIGN_TOP_LEFT, 10, DETECT_LABEL_START_Y + detect_index * DETECT_LABEL_SPACING);
  lv_timer_handler();
  delay(200);
  
  // Execute buzzer test (three beeps)
  beep_speaker(3);
  
  lv_label_set_text(label_detect_speaker, "Speaker: ok");
  lv_timer_handler();
  delay(500);
  detect_index++;

  lv_obj_t *label_detect_first = NULL;  // Save first label for potential reuse
 
   // Detect PCF85063
   lv_obj_t *label_detect_rtc = lv_label_create(screen_detect);
   label_detect_first = label_detect_rtc;  // Save first label
   lv_obj_add_style(label_detect_rtc, &detect_label_style, 0);
   lv_label_set_text(label_detect_rtc, "PCF85063 (RTC): Detecting...");
   lv_obj_align(label_detect_rtc, LV_ALIGN_TOP_LEFT, 10, DETECT_LABEL_START_Y + detect_index * DETECT_LABEL_SPACING);
   lv_timer_handler();
   delay(200);
   bool rtc_ok = pcf85063_init();
   lv_label_set_text(label_detect_rtc, rtc_ok ? "PCF85063 (RTC): ok" : "PCF85063 (RTC): Failed");
   lv_timer_handler();
   delay(500);
 
   // Detect AXP2101
   detect_index++;
   lv_obj_t *label_detect_axp = lv_label_create(screen_detect);
   lv_obj_add_style(label_detect_axp, &detect_label_style, 0);
   lv_label_set_text(label_detect_axp, "AXP2101 (Power): Detecting...");
   lv_obj_align(label_detect_axp, LV_ALIGN_TOP_LEFT, 10, DETECT_LABEL_START_Y + detect_index * DETECT_LABEL_SPACING);
   lv_timer_handler();
   delay(200);
   bool axp_ok = axp2101_init();
   lv_label_set_text(label_detect_axp, axp_ok ? "AXP2101 (Power): ok" : "AXP2101 (Power): Failed");
   lv_timer_handler();
   delay(500);
 
   // Detect QMI8658
   detect_index++;
   lv_obj_t *label_detect_qmi = lv_label_create(screen_detect);
   lv_obj_add_style(label_detect_qmi, &detect_label_style, 0);
   lv_label_set_text(label_detect_qmi, "QMI8658 (IMU): Detecting...");
   lv_obj_align(label_detect_qmi, LV_ALIGN_TOP_LEFT, 10, DETECT_LABEL_START_Y + detect_index * DETECT_LABEL_SPACING);
   lv_timer_handler();
   delay(200);
   bool qmi_ok = qmi8658_init();
   lv_label_set_text(label_detect_qmi, qmi_ok ? "QMI8658 (IMU): ok" : "QMI8658 (IMU): Failed");
   lv_timer_handler();
   delay(500);
 
   // Detect SHT41
   detect_index++;
   lv_obj_t *label_detect_sht = lv_label_create(screen_detect);
   lv_obj_add_style(label_detect_sht, &detect_label_style, 0);
   lv_label_set_text(label_detect_sht, "SHT41 (Temp/Hum): Detecting...");
   lv_obj_align(label_detect_sht, LV_ALIGN_TOP_LEFT, 10, DETECT_LABEL_START_Y + detect_index * DETECT_LABEL_SPACING);
   lv_timer_handler();
   delay(200);
   bool sht_ok = sht41_init();
   lv_label_set_text(label_detect_sht, sht_ok ? "SHT41 (Temp/Hum): ok" : "SHT41 (Temp/Hum): Failed");
   lv_timer_handler();
   delay(500);
 
   // Detect Quectel L76K
   detect_index++;
   lv_obj_t *label_detect_gps = lv_label_create(screen_detect);
   lv_obj_add_style(label_detect_gps, &detect_label_style, 0);
   lv_label_set_text(label_detect_gps, "Quectel L76K(GPS):Detecting...");
   lv_obj_align(label_detect_gps, LV_ALIGN_TOP_LEFT, 10, DETECT_LABEL_START_Y + detect_index * DETECT_LABEL_SPACING);
   lv_timer_handler();
   delay(200);
   bool gps_ok = quectel_l76k_init();
   lv_label_set_text(label_detect_gps, gps_ok ? "Quectel L76K (GPS): ok" : "Quectel L76K (GPS): Failed");
   lv_timer_handler();
   delay(500);
 
   // Detect WiFi AP
   detect_index++;
   lv_obj_t *label_detect_wifi = lv_label_create(screen_detect);
   lv_obj_add_style(label_detect_wifi, &detect_label_style, 0);
   lv_label_set_text(label_detect_wifi, "WiFi AP: Detecting...");
   lv_obj_align(label_detect_wifi, LV_ALIGN_TOP_LEFT, 10, DETECT_LABEL_START_Y + detect_index * DETECT_LABEL_SPACING);
   lv_timer_handler();
   delay(200);
   bool wifi_ok = wifi_ap_init();
   lv_label_set_text(label_detect_wifi, wifi_ok ? "WiFi AP: ok" : "WiFi AP: Failed");
   lv_timer_handler();
   delay(500);
 
   // Detect LoRa Module
   detect_index++;
   lv_obj_t *label_detect_lora = lv_label_create(screen_detect);
   lv_obj_add_style(label_detect_lora, &detect_label_style, 0);
   lv_label_set_text(label_detect_lora, "LoRa (E22): Detecting...");
   lv_obj_align(label_detect_lora, LV_ALIGN_TOP_LEFT, 10, DETECT_LABEL_START_Y + detect_index * DETECT_LABEL_SPACING);
   lv_timer_handler();
   delay(200);
   bool lora_ok = lora_init();
   lv_label_set_text(label_detect_lora, lora_ok ? "LoRa (E22): ok" : "LoRa (E22): Failed");
   lv_timer_handler();
   delay(500);
 
   // Detect Flash
   detect_index++;
   lv_obj_t *label_detect_flash = lv_label_create(screen_detect);
   lv_obj_add_style(label_detect_flash, &detect_label_style, 0);
   lv_label_set_text(label_detect_flash, "Flash: Detecting...");
   lv_obj_align(label_detect_flash, LV_ALIGN_TOP_LEFT, 10, DETECT_LABEL_START_Y + detect_index * DETECT_LABEL_SPACING);
   lv_timer_handler();
   delay(200);
   
   uint32_t flash_size = flash_get_size();
   if (flash_size > 0) {
     // Convert bytes to MB
     float flash_size_mb = flash_size / (1024.0 * 1024.0);
     char flash_text[64];
     sprintf(flash_text, "Flash: ok (%dMB)", (int)flash_size_mb);
     lv_label_set_text(label_detect_flash, flash_text);
     Serial.printf("Flash detected: %d bytes (%.2f MB)\n", flash_size, flash_size_mb);
   } else {
     lv_label_set_text(label_detect_flash, "Flash: Failed");
     Serial.println("Flash detection failed!");
   }
   lv_timer_handler();
   delay(500);
 
   // Re-initialize I2C for all sensors (QMI8658 needs 400kHz, others can work with it)
   Wire.end();
   Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
   Wire.setClock(400000);
   Wire.setTimeout(1000000);
   delay(100);
   Serial.println("I2C re-initialized (400kHz) for all sensors");
 
   // Detect ES8311 - Initialize after I2C reconfiguration
   detect_index++;
   lv_obj_t *label_detect_es8311 = lv_label_create(screen_detect);
   lv_obj_add_style(label_detect_es8311, &detect_label_style, 0);
   lv_label_set_text(label_detect_es8311, "ES8311: Detecting...");
   lv_obj_align(label_detect_es8311, LV_ALIGN_TOP_LEFT, 10, DETECT_LABEL_START_Y + detect_index * DETECT_LABEL_SPACING);
   lv_timer_handler();
   delay(200);
   
   bool es8311_ok = es8311_init();
   if (es8311_ok) {
     lv_label_set_text(label_detect_es8311, "ES8311: ok");
     Serial.println("ES8311 initialized successfully");
   } else {
     lv_label_set_text(label_detect_es8311, "ES8311: Failed");
     Serial.println("[Audio] ES8311 initialization failed!");
   }
   lv_timer_handler();
   delay(500);
 
   // Detect Audio MIC (I2S) - Only test if ES8311 is OK
   detect_index++;
   lv_obj_t *label_detect_mic = lv_label_create(screen_detect);
   lv_obj_add_style(label_detect_mic, &detect_label_style, 0);
   lv_label_set_long_mode(label_detect_mic, LV_LABEL_LONG_WRAP);  // Enable multi-line text
   lv_obj_set_width(label_detect_mic, 220);  // Set width to allow text wrapping
   lv_label_set_text(label_detect_mic, "Audio MIC: Detecting...");
   lv_obj_align(label_detect_mic, LV_ALIGN_TOP_LEFT, 10, DETECT_LABEL_START_Y + detect_index * DETECT_LABEL_SPACING);
   lv_timer_handler();
   delay(200);
   
   bool i2s_ok = false;
   if (es8311_ok) {
     i2s_ok = setupI2S();
     if (i2s_ok) {
       // Read audio data from microphone to verify it's working
       Serial.println("Reading audio data from microphone...");
       delay(200);  // Wait a bit for I2S to start receiving data
       
       const size_t sample_count = 512;  // Read 512 stereo samples (about 11.6ms at 44.1kHz)
       const size_t buffer_size = sample_count * sizeof(int16_t) * 2;  // Stereo buffer
       uint8_t *audio_buffer = (uint8_t *)malloc(buffer_size);
       
       if (audio_buffer != NULL) {
         // Clear buffer first
         memset(audio_buffer, 0, buffer_size);
         
         // Try to read audio data byte by byte
         size_t bytes_read = 0;
         unsigned long start_time = millis();
         while (bytes_read < buffer_size && (millis() - start_time) < 500) {  // Wait up to 500ms for data
           if (i2s.available() > 0) {
             int byte_val = i2s.read();
             if (byte_val >= 0) {
               audio_buffer[bytes_read++] = (uint8_t)byte_val;
             }
           } else {
             delay(5);
             lv_timer_handler();  // Keep UI responsive
           }
         }
         
         if (bytes_read >= sizeof(int16_t) * 2) {  // At least one stereo sample
           // Calculate statistics from left channel (every other sample)
           int32_t sum = 0;
           int16_t max_val = 0;
           int16_t min_val = 32767;
           int samples_processed = bytes_read / (sizeof(int16_t) * 2);  // Number of stereo samples
           
           if (samples_processed > 0) {
             int16_t *samples = (int16_t *)audio_buffer;
             for (int i = 0; i < samples_processed; i++) {
               int16_t sample = samples[i * 2];  // Left channel
               int abs_sample = abs(sample);
               sum += abs_sample;
               if (abs_sample > max_val) max_val = abs_sample;
               if (abs_sample < min_val) min_val = abs_sample;
             }
             
             int avg = sum / samples_processed;
             
             // Format result string (two lines)
             char mic_text[80];
             sprintf(mic_text, "Audio MIC: ok\nMax:%d Avg:%d", max_val, avg);
             lv_label_set_text(label_detect_mic, mic_text);
             Serial.printf("Audio MIC: Max=%d, Min=%d, Avg=%d, Samples=%d\n", max_val, min_val, avg, samples_processed);
           } else {
             lv_label_set_text(label_detect_mic, "Audio MIC: ok (No samples)");
             Serial.println("Audio MIC: Data read but no samples processed");
           }
         } else {
           lv_label_set_text(label_detect_mic, "Audio MIC: ok (No data)");
           Serial.printf("Audio MIC: I2S initialized but insufficient data read (%d bytes)\n", bytes_read);
         }
         
         free(audio_buffer);
       } else {
         lv_label_set_text(label_detect_mic, "Audio MIC: ok");
         Serial.println("Audio MIC (I2S) initialized successfully (memory allocation failed for test)");
       }
     } else {
       lv_label_set_text(label_detect_mic, "Audio MIC: Failed");
       Serial.println("[Audio] I2S initialization failed!");
     }
   } else {
     lv_label_set_text(label_detect_mic, "Audio MIC: Failed");
     Serial.println("[Audio] Audio MIC test skipped (ES8311 not initialized)");
   }
   lv_timer_handler();
   delay(500);
 
   // Re-initialize RTC to ensure it works after I2C reconfiguration
   if (rtc_ok) {
     rtc.begin(Wire);
     Serial.println("RTC re-initialized");
   }
 
   // Try to sync RTC time from GPS or NTP
   if (rtc_ok) {
     Serial.println("RTC initialized with default time (will continue counting)");
     Serial.println("Attempting to sync RTC time from GPS...");
     // Wait a bit for GPS to get valid data (non-blocking, will continue in loop)
     for (int i = 0; i < 10 && !gpsData.timeSynced; i++) {
       read_gps_data();
       sync_rtc_from_gps();
       delay(500);
     }
     
     if (gpsData.timeSynced) {
       Serial.println("RTC successfully synced from GPS during initialization");
     } else {
       Serial.println("GPS time not available yet, RTC continues with initial time");
       Serial.println("Will keep trying to sync from GPS in background");
       // If GPS sync failed, try NTP
       if (wifi_ok) {
         Serial.println("Trying NTP as backup...");
         sync_rtc_from_ntp();
       }
     }
   }
 
   // Don't switch to main screen yet - will do LoRa test first
   // The startup test will switch to main screen after completion
   Serial.println("Sensor detection complete. Starting LoRa startup test...");
 }
 
 // -------------------------- Sensor Data Update Callback --------------------------
 static void sensor_data_callback(lv_timer_t *timer) {
   // Buffer for Data
   char datetime_buf[32], weekday_buf[16];
   char accel_buf[64], gyro_buf[64], qmi_temp_buf[32];
   char temp_buf[32], hum_buf[32];
   char gps_time_buf[32], gps_lat_buf[32], gps_lon_buf[32], gps_sat_buf[16];
   char gps_alt_buf[32];
 
   // Read All Sensors
   read_pcf85063(datetime_buf, weekday_buf);
   read_qmi8658(accel_buf, gyro_buf, qmi_temp_buf);
   read_quectel_l76k(gps_time_buf, gps_lat_buf, gps_lon_buf, gps_sat_buf, gps_alt_buf);
   read_sht41(temp_buf, hum_buf);
 
   // Update UI
   lv_label_set_text(label_rtc_datetime, datetime_buf);
   lv_label_set_text(label_rtc_weekday, weekday_buf);
   lv_label_set_text(label_qmi_accel, accel_buf);
   lv_label_set_text(label_qmi_gyro, gyro_buf);
   lv_label_set_text(label_qmi_temp, qmi_temp_buf);
   lv_label_set_text(label_gps_time, gps_time_buf);
   lv_label_set_text(label_gps_lat, gps_lat_buf);
   lv_label_set_text(label_gps_lon, gps_lon_buf);
   lv_label_set_text(label_gps_sat, gps_sat_buf);
   lv_label_set_text(label_gps_alt, gps_alt_buf);
   lv_label_set_text(label_sht_temp, temp_buf);
   lv_label_set_text(label_sht_hum, hum_buf);
 }
 
 
 // -------------------------- Setup & Loop --------------------------
void setup() {
  // Initialize serial port first - CRITICAL: Must be first operation
  Serial.begin(115200);
  
  // For ESP32, we need to wait longer for serial port to be ready
  // Also wait for serial monitor to connect (optional, but helps with debugging)
  unsigned long serial_start = millis();
  while (!Serial && (millis() - serial_start < 3000)) {
    delay(10);
  }
  
  // Additional delay to ensure serial port is fully initialized
  delay(1000);
  
  // Print startup message immediately - this helps verify serial is working
  Serial.println("\n\n\n");
  Serial.println("========================================");
  Serial.println("=== Multi-Sensor LVGL Display ===");
  Serial.println("========================================");
  Serial.println("Serial port initialized successfully!");
  Serial.printf("Boot time: %lu ms\n", millis());
  Serial.flush();
  
  Serial.println("Starting initialization...");
  Serial.flush();  // Ensure message is sent
 
   // Configure watchdog (to prevent reboot during long operations)
   // Use safer configuration - disable panic and set longer timeout
   esp_task_wdt_config_t wdt_config = {
     .timeout_ms = 60000,  // 60 second timeout
     .idle_core_mask = 0,
     .trigger_panic = false  // Disable panic to prevent reboot
   };
   
  Serial.println("Configuring watchdog...");
  Serial.flush();
  esp_err_t wdt_ret = esp_task_wdt_init(&wdt_config);
  if (wdt_ret == ESP_OK) {
    esp_task_wdt_add(NULL);  // Add current task to watchdog
    Serial.println("Watchdog configured (60s timeout, no panic)");
    Serial.flush();
  } else if (wdt_ret == ESP_ERR_INVALID_STATE) {
    // Watchdog already initialized, try to reconfigure
    esp_task_wdt_reconfigure(&wdt_config);
    esp_task_wdt_add(NULL);
    Serial.println("Watchdog reconfigured");
    Serial.flush();
  } else {
    Serial.printf("Watchdog init failed: %s, continuing without watchdog\n", esp_err_to_name(wdt_ret));
    Serial.flush();
  }
   
   // Feed watchdog immediately after configuration
   if (wdt_ret == ESP_OK || wdt_ret == ESP_ERR_INVALID_STATE) {
     esp_task_wdt_reset();
   }
   
   // Feed watchdog immediately after configuration
   esp_task_wdt_reset();
 
   // LCD Init
   // Turn off backlight first to prevent showing old content during reset
#ifdef GFX_BL
   pinMode(GFX_BL, OUTPUT);
   digitalWrite(GFX_BL, LOW);
#endif
   
   mcp.begin_I2C(0x20);
   mcp.pinMode(1, OUTPUT);
   lcd_reset();
   if (!gfx->begin()) {
     Serial.println("Display init failed!");
     while (1);
   }
   // Immediately clear screen before turning on backlight to prevent showing old content
   gfx->fillScreen(RGB565_BLACK);
   // No delay - clear immediately and turn on backlight right away
   gfx->fillScreen(RGB565_BLACK);  // Fill with black (RGB565) to clear any residual content
   gfx->fillScreen(RGB565_BLACK);  // Fill with black again

   // Backlight ON (after screen is cleared)
#ifdef GFX_BL
   digitalWrite(GFX_BL, HIGH);
#endif
 
   // LVGL Init
   lv_init();
   screenWidth = LCD_HOR_RES;
   screenHeight = LCD_VER_RES;
   bufSize = screenWidth * 20;
   disp_draw_buf1 = (lv_color_t *)malloc(bufSize * sizeof(lv_color_t));
   disp_draw_buf2 = (lv_color_t *)malloc(bufSize * sizeof(lv_color_t));
   if (!disp_draw_buf1 || !disp_draw_buf2) {
     Serial.println("LVGL buffer failed!");
     while (1);
   }
   lv_disp_draw_buf_init(&draw_buf, disp_draw_buf1, disp_draw_buf2, bufSize);
   lv_disp_drv_init(&disp_drv);
   disp_drv.hor_res = screenWidth;
   disp_drv.ver_res = screenHeight;
   disp_drv.flush_cb = my_disp_flush;
   disp_drv.draw_buf = &draw_buf;
   lv_disp_drv_register(&disp_drv);
 
   // Immediately clear entire screen using GFX directly (bypasses LVGL buffer)
   // This ensures no old content from previous boot is visible
   gfx->fillScreen(0x0000);  // Fill with black (RGB565)
   // No delay - clear immediately
 
   // Initialize Detection Screen (white background will be drawn by LVGL)
   lvgl_detect_ui_init();
   lv_timer_handler();
   delay(20);  // Ensure detection screen is fully displayed
   
   // Initialize Main Screen (but don't show it yet)
   lvgl_ui_init();
   
   // LoRa TX/RX screens will be initialized when needed (on button press)
 
   // Detect Sensors and Show Results
   detect_all_sensors();
 
   // Data Update Timer (500ms Interval)
   sensor_timer = lv_timer_create(sensor_data_callback, 500, NULL);
 
   // Initialize button pin with interrupt
   pinMode(BUTTON_PIN, INPUT_PULLUP);
   // Attach interrupt for button (CHANGE mode to catch both press and release)
   attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, CHANGE);
   Serial.println("Button interrupt configured on GPIO0 (CHANGE mode)");

   // Start LoRa startup test if module is detected
   if (loraData.module_detected) {
     Serial.println("LoRa module detected - starting startup test (TX -> RX)");
     startup_test.state = STARTUP_TEST_WAIT_LORA_INIT;
     startup_test.start_time = millis();
     startup_test.last_action_time = millis();
     startup_test.lora_initialized = false;
     startup_test.tx_completed = false;
   } else {
     // No LoRa module, skip test and go directly to main screen
     Serial.println("LoRa module not detected - skipping startup test");
     startup_test.state = STARTUP_TEST_COMPLETE;
     lv_scr_load(screen_main);
     sensor_data_callback(NULL);
   }

   Serial.println("Setup complete");
 }
 
void loop() {
  // Debug output to verify loop is running (only print once at startup and then every 10 seconds)
  static bool first_loop = true;
  static unsigned long last_debug_time = 0;
  if (first_loop) {
    Serial.println("Loop() function started successfully!");
    Serial.flush();
    first_loop = false;
    last_debug_time = millis();
  } else if (millis() - last_debug_time > 10000) {
    Serial.printf("Loop running... (uptime: %lu ms)\n", millis());
    Serial.flush();
    last_debug_time = millis();
  }
  
  // LVGL timer handler (must be called frequently)
  lv_timer_handler();
  
  // Process startup test (non-blocking) - must be before button events
  processStartupTest();
  
  // Process button events (non-blocking) - only if startup test is complete
  if (startup_test.state == STARTUP_TEST_COMPLETE || 
      startup_test.state == STARTUP_TEST_IDLE) {
    processButtonEvents();
  }
  
  // Process LoRa initialization (non-blocking, step by step)
  processLoRaInit();
  
  // Check if TX mode was cancelled but still on TX screen
  lv_obj_t *current_screen = lv_scr_act();
  if (!loraData.tx_mode && current_screen == screen_lora_tx) {
    // TX mode was cancelled but still on TX screen - return to main screen
    Serial.println("TX mode cancelled, returning to main screen");
    // Stop LoRa timer if running
    if (lora_timer != NULL) {
      lv_timer_del(lora_timer);
      lora_timer = NULL;
    }
    // Set module to standby mode (non-blocking)
    if (loraData.module_detected) {
      e22SetStandby();
    }
    // Switch to main screen immediately
    lv_scr_load(screen_main);
    // Trigger sensor data update to refresh main screen
    sensor_data_callback(NULL);
  }
  
  // TX mode: Send test message every 5 seconds 
  if (loraData.tx_mode) {
    static unsigned long last_tx_time = 0;
    if (millis() - last_tx_time > 5000) {
      // Double check tx_mode before sending (user may have pressed button)
      if (loraData.tx_mode) {
        sendTestMessage();
      }
      last_tx_time = millis();
    }
  } else if (loraData.rx_mode) {
     // RX mode: Ensure module is in RX mode and check for data
     static unsigned long last_rx_check = 0;
     if (millis() - last_rx_check > 100) {  // Check every 100ms
       lora_check_received();
       last_rx_check = millis();
     }
   }
   
   // If in TX or RX mode, don't update main screen data
   if (!loraData.tx_mode && !loraData.rx_mode) {
     // Read GPS data continuously (only when on main screen)
     read_gps_data();
     
     // Periodically try to sync RTC from GPS if not synced yet
     static unsigned long lastSyncAttempt = 0;
     if (!gpsData.timeSynced && (millis() - lastSyncAttempt > 5000)) {
       sync_rtc_from_gps();
       lastSyncAttempt = millis();
     }
   }
   
   // Feed watchdog periodically
   esp_task_wdt_reset();
   
   // Clear AXP2101 IRQs (non-blocking)
   power.clearIrqStatus();
   
   // Small yield to allow other tasks to run (non-blocking alternative to delay)
   yield();
}

// Hardware diagnostics function (non-blocking version to avoid watchdog timeout)
void hardwareDiagnostics() {
  Serial.println("=== LoRa Hardware Diagnostics (Quick Check) ===");

  // Check SPI pins quickly
  Serial.println("1. SPI bus test result: sent 0x55, received 0xFF (SPI bus OK, but LoRa not responding)");

  // Check LoRa control pins via MCP23017
  Serial.println("2. LoRa control pins:");
  Serial.printf("   NRST (EXIO%d): %s\n", LORA_EXIO_NRST,
                lora_exio_read(LORA_EXIO_NRST) == HIGH ? "HIGH" : "LOW");
  Serial.printf("   BUSY (EXIO%d): %s\n", LORA_EXIO_BUSY,
                lora_exio_read(LORA_EXIO_BUSY) == HIGH ? "HIGH" : "LOW");
  Serial.printf("   DIO1 (EXIO%d): %s\n", LORA_EXIO_DIO1,
                lora_exio_read(LORA_EXIO_DIO1) == HIGH ? "HIGH" : "LOW");

  // Quick MCP23017 test
  Serial.println("3. MCP23017 connectivity:");
  uint8_t original_value = lora_exio_read(LORA_EXIO_NRST);
  lora_exio_write(LORA_EXIO_NRST, !original_value);
  delay(1);  // Very short delay
  uint8_t read_value = lora_exio_read(LORA_EXIO_NRST);
  lora_exio_write(LORA_EXIO_NRST, original_value);  // Restore original value

  if (read_value == (uint8_t)!original_value) {
    Serial.println("   MCP23017: OK");
  } else {
    Serial.println("   MCP23017: FAIL");
  }

  // Quick power check
  Serial.println("4. Power status:");
  Serial.printf("   System voltage: %.1fV\n", power.getSystemVoltage() / 1000.0);

  Serial.println("=== Quick Diagnostics Complete ===");
  Serial.println("SPI bus works but LoRa module doesn't respond.");
  Serial.println("Most likely causes:");
  Serial.println("1. LoRa module not powered (check power connections)");
  Serial.println("2. SPI cables not connected to LoRa module");
  Serial.println("3. NSS signal routing issue (check CS pin)");
  Serial.println("4. LoRa module needs different power supply voltage");
  Serial.println();
}

// Quick hardware check function (very fast to avoid watchdog timeout)
void quickHardwareCheck() {
   Serial.println("=== LoRa Hardware Troubleshooting ===");
 
   // Test LoRa module power by checking if it responds to basic SPI command
   Serial.println("Testing LoRa module SPI response...");
 
   // Try to read status multiple times with different approaches
   bool module_responding = false;
   uint8_t test_results[5];
 
   for (int i = 0; i < 5; i++) {
     // Method 1: Direct SPI transfer
     digitalWrite(E22_NSS_PIN, HIGH);
     delayMicroseconds(10);
     digitalWrite(E22_NSS_PIN, LOW);
     delayMicroseconds(10);
     test_results[i] = SPI.transfer(0xC0);  // GET_STATUS command
     digitalWrite(E22_NSS_PIN, HIGH);
     delayMicroseconds(10);
 
     if (test_results[i] != 0x00 && test_results[i] != 0xFF) {
       module_responding = true;
       break;
     }
     delay(5);
   }
 
   Serial.printf("LoRa SPI responses: 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X\n",
                 test_results[0], test_results[1], test_results[2], test_results[3], test_results[4]);
 
   if (module_responding) {
     Serial.println("✓ LoRa module is responding to SPI commands");
   } else {
     Serial.println("✗ LoRa module is NOT responding to SPI commands");
     Serial.println();
     Serial.println("=== HARDWARE TROUBLESHOOTING STEPS ===");
     Serial.println("1. POWER SUPPLY:");
     Serial.println("   - Check if LoRa module (E22-900MM22S) has 3.3V power");
     Serial.println("   - Use multimeter to measure VCC and GND pins on module");
     Serial.println("   - Module requires stable 3.3V supply");
     Serial.println();
     Serial.println("2. SPI CONNECTIONS (check each wire):");
     Serial.println("   ESP32 → LoRa Module");
     Serial.println("   GPIO12 (SCK)  → SCK pin on LoRa module");
     Serial.println("   GPIO13 (MOSI) → MOSI/SDI pin on LoRa module");
     Serial.println("   GPIO11 (MISO) → MISO/SDO pin on LoRa module");
     Serial.println("   GPIO14 (NSS)  → NSS/CS pin on LoRa module");
     Serial.println("   GND           → GND pin on LoRa module");
     Serial.println();
     Serial.println("3. CONTROL PINS:");
    Serial.println("   - RST pin on LoRa module → EXIO3 (MCP23017)");
    Serial.println("   - BUSY pin on LoRa module → EXIO10 (MCP23017)");
    Serial.println("   - DIO1 pin on LoRa module → EXIO9 (MCP23017)");
     Serial.println();
     Serial.println("4. MODULE IDENTIFICATION:");
     Serial.println("   - Confirm you have E22-900MM22S (SX1262/SX1268 based)");
     Serial.println("   - Check module markings and pinout diagram");
     Serial.println();
     Serial.println("5. ALTERNATIVE TEST:");
     Serial.println("   - Try connecting LoRa module directly to ESP32 pins");
     Serial.println("   - Bypass MCP23017 for initial testing");
     Serial.println();
   }
 
   // Check control pins
   Serial.println("Current control pin status:");
   Serial.printf("  NRST: %s, BUSY: %s, DIO1: %s\n",
                 lora_exio_read(LORA_EXIO_NRST) ? "HIGH" : "LOW",
                 lora_exio_read(LORA_EXIO_BUSY) ? "HIGH" : "LOW",
                 lora_exio_read(LORA_EXIO_DIO1) ? "HIGH" : "LOW");
 
   Serial.println("=== Hardware Check Complete ===");
   Serial.println();
 }
 