/*Using LVGL with Arduino requires some extra steps:
 *Be sure to read the docs here: https://docs.lvgl.io/master/get-started/platforms/arduino.html  */

#include <lvgl.h>
#include <Arduino_GFX_Library.h>
#include <Adafruit_MCP23X17.h>
#include <Wire.h>
#include <SPI.h>

#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 8
#define I2C_SCL 7

// 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, pin 3)
//   EXIO9  = GPB1 (GPB port, bit 1, pin 9)
//   EXIO10 = GPB2 (GPB port, bit 2, pin 10)
// Note: pin numbering uses 0-15 (GPA0-7 = 0-7, GPB0-7 = 8-15)
#define E22_NRST_EXIO 3   // E22_NRST -> EXIO3 (MCP23017 GPA3, pin 3)
#define E22_BUSY_EXIO 10  // E22_BUSY -> EXIO10 (MCP23017 GPB2, pin 10)
#define E22_DIO1_EXIO 9   // E22_DIO1 -> EXIO9 (MCP23017 GPB1, pin 9)

// ============================================================================
// LoRa Device Mode Configuration - switch TX/RX by Boot button
// ============================================================================
// At boot, read Boot button:
// - Button released (HIGH): TX mode (transmit)
// - Button pressed (LOW):   RX mode (receive)
// ============================================================================
#define LORA_MODE_TX 1
#define LORA_MODE_RX 2

// Boot button pin
// On ESP32-S3 boards, BOOT button is usually connected to GPIO0
// Pressed = LOW, released = HIGH (with internal pull-up)
#define BOOT_BUTTON_PIN 0

// Runtime mode variable (set in setup() based on Boot button state)
uint8_t lora_mode = LORA_MODE_TX;

// E22 BUSY check flag (skip BUSY check if SPI works but BUSY logic fails)
bool skip_busy_check = false;

// BUSY pin logic flag: true => HIGH means ready, false => LOW means ready
// Auto-detected on first call
bool busy_logic_high_ready = false;

Adafruit_MCP23X17 mcp;

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);

// SPI for LoRa module (shared with LCD but different CS)
// Note: SPI is initialized in setup()

uint32_t screenWidth;
uint32_t screenHeight;
uint32_t 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;

// LVGL UI elements
lv_obj_t *label_title;
lv_obj_t *label_status;
lv_obj_t *label_freq;
lv_obj_t *label_power;
lv_obj_t *label_mode;
lv_obj_t *label_tx_count;
lv_obj_t *label_rx_count;
lv_obj_t *label_rssi;
lv_obj_t *label_last_msg;

lv_timer_t *lora_timer = NULL;

// LoRa test data
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;
} loraData = {
  .status = "Initializing",
  .frequency = "915MHz",
  .power = "22dBm",
  .mode = "SPI",
  .tx_count = 0,
  .rx_count = 0,
  .rssi = 0,
  .last_message = "None",
  .module_detected = false
};

// Display flushing
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);
}

// LCD reset function
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
}

// ============================================================================
// E22 LoRa Module SPI Functions
// ============================================================================

// Wait for E22 BUSY pin to be ready
// Automatically detects BUSY logic based on initial state
bool waitE22Ready(uint32_t timeout_ms = 1000, 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 = mcp.digitalRead(E22_BUSY_EXIO);
  
  // 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 (EXIO10 = GPB2, pin 10)
    uint8_t busy_state = mcp.digitalRead(E22_BUSY_EXIO);
    
    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", mcp.digitalRead(E22_BUSY_EXIO));
  }
  // If BUSY check times out but SPI works, we'll skip BUSY check in future
  return false;  // Timeout
}

// Note: SPI read/write are now handled directly in e22SPITransaction

// 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
  }
  
  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;
    }
  }
  
  // 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(10);
  digitalWrite(E22_NSS_PIN, HIGH);
  
  return true;
}

// E22 Command: Read Register
uint8_t e22ReadRegister(uint8_t reg) {
  uint8_t tx_data[2] = {0x1D, reg};  // Read command (0x1D) + register address
  uint8_t rx_data[3] = {0};
  
  if (e22SPITransaction(tx_data, 2, rx_data, 3)) {
    return rx_data[2];  // Third byte is the register value
  }
  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);
}

// E22 Command: Set to Sleep mode
bool e22SetSleep() {
  uint8_t tx_data[1] = {0x84};  // Sleep command
  return e22SPITransaction(tx_data, 1, NULL, 0);
}

// E22 Command: Set to Standby mode
bool e22SetStandby() {
  uint8_t tx_data[1] = {0x80};  // Standby command
  return e22SPITransaction(tx_data, 1, NULL, 0);
}

// E22 Command: Set to TX mode
bool e22SetTX() {
  uint8_t tx_data[4] = {0x83, 0x00, 0x00, 0x00};  // TX command + timeout
  return e22SPITransaction(tx_data, 4, NULL, 0);
}

// E22 Command: Set to RX mode
bool e22SetRX() {
  // Step 1: Set to Standby mode first
  e22SetStandby();
  delay(10);
  
  // Step 2: Clear IRQ status
  e22ClearIRQStatus(0xFFFF);
  
  // 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)
  uint8_t tx_data[4] = {0x82, 0x00, 0x00, 0x00};  // RADIO_SET_RX + timeout (0x0000 = continuous)
  bool result = e22SPITransaction(tx_data, 4, NULL, 0);
  if (result) {
    Serial.println("RX: SetRX command sent successfully");
    delay(10);  // Give module time to enter RX mode
  } else {
    Serial.println("RX: SetRX command failed");
  }
  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);
}

// 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: 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: 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

  // Step 1: Set to Standby mode
  e22SetStandby();
  delay(10);
  
  // Step 2: Clear IRQ status
  e22ClearIRQStatus(0xFFFF);
  
  // Step 3: Re-configure DIO and IRQ for TX mode (TX_DONE only)
  e22ConfigDIOIRQ(0x0001, 0x0001, 0x0000, 0x0000);  // TX_DONE only
  delay(10);
  
  // Step 4: Write data to buffer (offset 0x00 for TX)
  if (!e22WriteBuffer(0x00, data, len)) {
    return false;
  }
  
  // Step 5: 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 6: Set to TX mode (3 bytes timeout: no timeout = 0x00, 0x00, 0x00)
  uint8_t tx_cmd[4] = {0x83, 0x00, 0x00, 0x00};  // RADIO_SET_TX + timeout (no timeout)
  if (!e22SPITransaction(tx_cmd, 4, NULL, 0)) {
    return false;
  }
  
  // Step 7: Wait for TX_DONE IRQ (最多等待 5 秒)
  unsigned long start = millis();
  bool tx_completed = false;
  
  while (millis() - start < 5000) {  // 5 second timeout
    uint16_t irq_status = e22GetIRQStatus();
    
    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;
      break;
    }
    
    if (irq_status & 0x02) {  // TIMEOUT bit
      Serial.println("TX: TX timeout detected");
      e22ClearIRQStatus(0x02);
      e22SetStandby();
      return false;
    }
    
    delay(10);
  }
  
  if (!tx_completed) {
    e22SetStandby();
    // 即使没有检测到 TX_DONE，也返回 true，因为可能已经发送了
    return true;  // 假设发送成功
  }
  
  return true;
}

// E22 Command: Get IRQ Status (using proper command)
uint16_t e22GetIRQStatus() {
  uint8_t tx_data[2] = {0x12, 0x00};  // RADIO_GET_IRQSTATUS command + dummy
  uint8_t rx_data[4] = {0};
  
  if (e22SPITransaction(tx_data, 2, rx_data, 4)) {
    // Response format: [status, dummy, irq_high, irq_low]
    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: 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)
// According to esp_lora_port.cpp, actual payload starts from spi_rx_buf[3]
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)) {
    // 根据 esp_lora_port.cpp 的实现，响应格式是：
    // [status, dummy1, dummy2, data...]
    // 实际数据从 rx_data[3] 开始
    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: 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();
  
  // Check RX_DONE bit (bit 1 = 0x02, not 0x40!)
  if (!(irq_status & 0x02)) {
    // No RX_DONE flag
    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 using proper command
  // First read into a temporary 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
  
  // Return payload length to caller; logging is handled in checkReceivedMessage()
  return payload_length;
}

// Detect Boot button and set LoRa mode
// Boot button pressed  => RX mode
// Boot button released => TX mode
void detectLoRaMode() {
  pinMode(BOOT_BUTTON_PIN, INPUT_PULLUP);
  delay(50);  // Wait for pin to stabilize
  
  // Read Boot button state (LOW = pressed, HIGH = not pressed)
  bool boot_pressed = (digitalRead(BOOT_BUTTON_PIN) == LOW);
  
  if (boot_pressed) {
    lora_mode = LORA_MODE_RX;
    Serial.println("Boot button PRESSED -> RX Mode (receive)");
  } else {
    lora_mode = LORA_MODE_TX;
    Serial.println("Boot button NOT pressed -> TX Mode (transmit)");
  }
  
  Serial.printf("LoRa Mode set to: %s\n", (lora_mode == LORA_MODE_TX) ? "TX" : "RX");
}

// Initialize LoRa module via SPI
void initLoRa() {
  // Initialize default values first (before any module detection)
  loraData.status = "Initializing";
  loraData.frequency = "915MHz";
  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...");
  
  // First, verify MCP23017 is working
  Serial.println("Checking MCP23017...");
  
  // Initialize MCP23017 if not already initialized
  if (!mcp.begin_I2C(0x20)) {
    Serial.println("ERROR: MCP23017 initialization failed!");
    Serial.println("LoRa control pins will not work!");
  } else {
    Serial.println("MCP23017 initialized at address 0x20");
  }
  
  // Try reading pins to diagnose
  Serial.println("Reading MCP23017 pins...");
  for (int i = 0; i < 8; i++) {
    uint8_t val0 = mcp.digitalRead(i);
    uint8_t val1 = mcp.digitalRead(i + 8);
    Serial.printf("  Pin%02d=%d  Pin%02d=%d\n", i, val0, i+8, val1);
  }
  
  uint8_t test_read = mcp.digitalRead(E22_BUSY_EXIO);
  Serial.printf("MCP23017 read test (BUSY pin %d): %d\n", E22_BUSY_EXIO, test_read);
  
  // Configure MCP23017 pins for E22 control
  Serial.println("Configuring MCP23017 pins...");
  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);
  
  // Configure NSS pin
  pinMode(E22_NSS_PIN, OUTPUT);
  digitalWrite(E22_NSS_PIN, HIGH);
  Serial.println("NSS pin configured");
  
  // Reset E22 module (NRST: LOW -> HIGH)
  Serial.println("Resetting E22 module...");
  mcp.digitalWrite(E22_NRST_EXIO, LOW);
  delay(50);  // Hold reset longer
  mcp.digitalWrite(E22_NRST_EXIO, HIGH);
  Serial.println("Reset released, waiting for module...");
  delay(200);  // Give module more time to initialize
  
  // Check BUSY state before SPI init
  uint8_t busy_before = mcp.digitalRead(E22_BUSY_EXIO);
  Serial.printf("BUSY state before SPI init: %d\n", busy_before);
  
  // Initialize SPI for E22 (Mode 0, MSB first, 8MHz)
  // Note: SPI.begin() can be called multiple times with different pins
  // The LCD uses SPI but with different CS pin, so we can initialize again
  SPI.begin(E22_SCK_PIN, E22_MISO_PIN, E22_MOSI_PIN, E22_NSS_PIN);
  SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
  Serial.println("SPI initialized");
  
  // 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!");
  }
  
  // Try to read version register to verify communication
  // Even if BUSY check failed, we can try SPI communication
  delay(50);
  Serial.println("Attempting to read E22 version register...");
  
  // Try reading version (skip BUSY check for this first attempt)
  uint8_t tx_data[2] = {0x1D, 0x01};  // Read command + RegVersion
  uint8_t rx_data[3] = {0};
  
  digitalWrite(E22_NSS_PIN, LOW);
  delayMicroseconds(10);
  for (int i = 0; i < 2; i++) {
    rx_data[i] = SPI.transfer(tx_data[i]);
  }
  rx_data[2] = SPI.transfer(0x00);  // Read response
  delayMicroseconds(10);
  digitalWrite(E22_NSS_PIN, HIGH);
  
  uint8_t version = rx_data[2];
  Serial.printf("Version register read: 0x%02X\n", version);
  
  if (version != 0 && version != 0xFF && version != 0x00) {
    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");
    
    // 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)
    // irqMask: TX_DONE=0x01, RX_DONE=0x02, PREAMBLE=0x20, SYNC_WORD=0x40
    // For now, enable all common IRQs
    e22ConfigDIOIRQ(0x0063, 0x0063, 0x0000, 0x0000);  // irqMask=0x63 (TX_DONE|RX_DONE|PREAMBLE|SYNC_WORD), dio1Mask same
    delay(10);
    Serial.println("DIO/IRQ configured");
    
    Serial.println("E22 module configuration complete!");
    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");
  }
  
  // Initialize default values (always set, even if module not detected)
  // This ensures UI displays values even when module initialization fails
  if (loraData.frequency.length() == 0) {
    loraData.frequency = "915MHz";
  }
  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";
  }
  
  // Ensure counts are initialized
  if (loraData.tx_count == 0 && loraData.rx_count == 0) {
    // This is fine, counts start at 0
  }
  
  Serial.printf("Display values initialized: Freq=%s, Power=%s, Mode=%s\n", 
                loraData.frequency.c_str(), loraData.power.c_str(), loraData.mode.c_str());
}

// Send test message via SPI
void sendTestMessage() {
  if (!loraData.module_detected) return;
  

  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)) {
    loraData.tx_count++;
    Serial.printf("Sent: %s\n", message.c_str());
    
    // Wait for TX done (check IRQ status)
    delay(100);
    uint8_t irq_status = e22ReadRegister(0x12);
    if (irq_status & 0x08) {  // TX_DONE
      e22WriteRegister(0x12, 0xFF);  // Clear IRQ
      e22SetStandby();  // Return to standby
    }
  } else {
    Serial.println("Send failed!");
  }
}

// Check for received messages via SPI
void checkReceivedMessage() {
  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)
  static bool rx_mode_set = false;
  if (!rx_mode_set) {
    e22SetRX();
    rx_mode_set = true;
  }
  
  // Check for received data
  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);
        }
      } 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
      rx_mode_set = false;
    } else {
      Serial.printf("RX: Received %d bytes but could not parse as text\n", rx_len);
    }
  }
}

// Callback function for LoRa timer
static void lora_callback(lv_timer_t *timer) {
  // Check for received messages (only in RX mode)
  if (lora_mode == LORA_MODE_RX) {
    checkReceivedMessage();
  }
  
  // Update display
  String status_text = loraData.status;
  if (lora_mode == LORA_MODE_TX) {
    status_text += " [TX]";
  } else if (lora_mode == LORA_MODE_RX) {
    status_text += " [RX]";
  }
  lv_label_set_text(label_status, status_text.c_str());
  // Update frequency, power, and mode (ensure strings are not empty)
  String freq_display = (loraData.frequency.length() > 0) ? loraData.frequency : "Unknown";
  String power_display = (loraData.power.length() > 0) ? loraData.power : "Unknown";
  String mode_display = (loraData.mode.length() > 0) ? loraData.mode : "Unknown";
  
  lv_label_set_text_fmt(label_freq, "Freq: %s", freq_display.c_str());
  lv_label_set_text_fmt(label_power, "Power: %s", power_display.c_str());
  lv_label_set_text_fmt(label_mode, "Mode: %s", mode_display.c_str());
  lv_label_set_text_fmt(label_tx_count, "TX: %lu", loraData.tx_count);
  lv_label_set_text_fmt(label_rx_count, "RX: %lu", loraData.rx_count);
  
  if (loraData.rssi != 0) {
    lv_label_set_text_fmt(label_rssi, "RSSI: %d dBm", loraData.rssi);
  } else {
    lv_label_set_text(label_rssi, "RSSI: --");
  }
  
  // Limit last message display length
  String display_msg = loraData.last_message;
  if (display_msg.length() > 15) {
    display_msg = display_msg.substring(0, 12) + "...";
  }
  lv_label_set_text_fmt(label_last_msg, "Last: %s", display_msg.c_str());
}

// Initialize LVGL UI for LoRa test display
void lvgl_lora_ui_init(lv_obj_t *parent) {
  // Set screen background to black
  lv_obj_set_style_bg_color(parent, lv_color_black(), 0);
  lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0);
  
  // Create title style
  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);
  
  // Create status style (green for connected, red for disconnected)
  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);
  
  // Create info style
  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);
  
  // Create count style
  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);
  
  // Create message style
  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
  label_title = lv_label_create(parent);
  lv_obj_add_style(label_title, &title_style, 0);
  // Title will be updated in setup() after mode detection
  lv_label_set_text(label_title, "LoRa Test");
  lv_obj_align(label_title, LV_ALIGN_TOP_MID, 0, 5);
  lv_obj_set_width(label_title, LCD_HOR_RES - 10);
  
  // Create status label
  label_status = lv_label_create(parent);
  lv_obj_add_style(label_status, &status_style, 0);
  lv_label_set_text(label_status, "Initializing...");
  lv_obj_align(label_status, LV_ALIGN_TOP_LEFT, 5, 30);
  lv_obj_set_width(label_status, LCD_HOR_RES - 10);
  
  // Create frequency label
  label_freq = lv_label_create(parent);
  lv_obj_add_style(label_freq, &info_style, 0);
  lv_label_set_text(label_freq, "Freq: --");
  lv_obj_align(label_freq, LV_ALIGN_TOP_LEFT, 5, 50);
  lv_obj_set_width(label_freq, LCD_HOR_RES - 10);
  
  // Create power label
  label_power = lv_label_create(parent);
  lv_obj_add_style(label_power, &info_style, 0);
  lv_label_set_text(label_power, "Power: --");
  lv_obj_align(label_power, LV_ALIGN_TOP_LEFT, 5, 70);
  lv_obj_set_width(label_power, LCD_HOR_RES - 10);
  
  // Create mode label
  label_mode = lv_label_create(parent);
  lv_obj_add_style(label_mode, &info_style, 0);
  lv_label_set_text(label_mode, "Mode: --");
  lv_obj_align(label_mode, LV_ALIGN_TOP_LEFT, 5, 90);
  lv_obj_set_width(label_mode, LCD_HOR_RES - 10);
  
  // Create TX count label
  label_tx_count = lv_label_create(parent);
  lv_obj_add_style(label_tx_count, &count_style, 0);
  lv_label_set_text(label_tx_count, "TX: 0");
  lv_obj_align(label_tx_count, LV_ALIGN_TOP_LEFT, 5, 110);
  lv_obj_set_width(label_tx_count, 100);
  
  // Create RX count label
  label_rx_count = lv_label_create(parent);
  lv_obj_add_style(label_rx_count, &count_style, 0);
  lv_label_set_text(label_rx_count, "RX: 0");
  lv_obj_align(label_rx_count, LV_ALIGN_TOP_LEFT, 120, 110);
  lv_obj_set_width(label_rx_count, 100);
  
  // Create RSSI label
  label_rssi = lv_label_create(parent);
  lv_obj_add_style(label_rssi, &info_style, 0);
  lv_label_set_text(label_rssi, "RSSI: --");
  lv_obj_align(label_rssi, LV_ALIGN_TOP_LEFT, 5, 130);
  lv_obj_set_width(label_rssi, LCD_HOR_RES - 10);
  
  // Create last message label
  label_last_msg = lv_label_create(parent);
  lv_obj_add_style(label_last_msg, &msg_style, 0);
  lv_label_set_text(label_last_msg, "Last: --");
  lv_obj_align(label_last_msg, LV_ALIGN_TOP_LEFT, 5, 150);
  lv_obj_set_width(label_last_msg, LCD_HOR_RES - 10);
  
  // Create bottom info label
  lv_obj_t *info_label = lv_label_create(parent);
  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)
  lora_timer = lv_timer_create(lora_callback, 500, NULL);
  lv_timer_ready(lora_timer);
}

void setup() {
  Serial.begin(115200);
  delay(500);  // Wait for serial to initialize
  
  // Detect LoRa mode from Boot button (must be done early)
  Serial.println("========================================");
  Serial.println("Checking Boot button state...");
  detectLoRaMode();
  Serial.println("========================================");
  
  Wire.begin(I2C_SDA, I2C_SCL);
  
  // Initialize MCP23017 (for LCD reset and LoRa control)
  if (!mcp.begin_I2C(0x20)) {
    Serial.println("ERROR: MCP23017 initialization failed!");
  } else {
    Serial.println("MCP23017 initialized at address 0x20");
    // Configure LCD reset pin as output
    mcp.pinMode(1, OUTPUT);
  }
  
  lcd_reset();
  Serial.println("ST7789 LoRa Test Display initialized");
  
  // Initialize display
  if (!gfx->begin()) {
    Serial.println("gfx->begin() failed!");
  }
  
  gfx->fillScreen(RGB565_BLACK);
  
#ifdef GFX_BL
  pinMode(GFX_BL, OUTPUT);
  digitalWrite(GFX_BL, HIGH);
#endif
  
  lv_init();
  
  screenWidth = gfx->width();
  screenHeight = gfx->height();
  bufSize = screenWidth * 40;
  
  // Allocate display buffers
  disp_draw_buf1 = (lv_color_t *)heap_caps_malloc(bufSize * 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT);
  disp_draw_buf2 = (lv_color_t *)heap_caps_malloc(bufSize * 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT);
  
  lv_disp_draw_buf_init(&draw_buf, disp_draw_buf1, disp_draw_buf2, bufSize);
  
  // Initialize display driver
  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);
  
  // Initialize LoRa module
  Serial.println("Initializing LoRa module...");
  initLoRa();
  
  // Set initial mode based on boot button
  if (loraData.module_detected) {
    if (lora_mode == LORA_MODE_TX) {
      e22SetStandby();
      Serial.println("E22 set to Standby (TX mode)");
    } else if (lora_mode == LORA_MODE_RX) {
      e22SetRX();
      Serial.println("E22 set to RX mode");
      Serial.println("RX mode initialized, ready to receive");
    }
  } else {
    Serial.println("WARNING: Module not detected, RX/TX functions will not work");
    if (lora_mode == LORA_MODE_RX) {
      Serial.println("RX mode requested but module not available");
    }
  }
  
  // Initialize UI
  lvgl_lora_ui_init(lv_scr_act());
  
  // Update title based on detected mode
  if (lora_mode == LORA_MODE_TX) {
    lv_label_set_text(label_title, "LoRa Test [TX]");
  } else if (lora_mode == LORA_MODE_RX) {
    lv_label_set_text(label_title, "LoRa Test [RX]");
  }
  
  Serial.println("Setup done");
  Serial.printf("Screen resolution: %dx%d\n", screenWidth, screenHeight);
  Serial.println("LoRa test display ready");
}

void loop() {
  lv_timer_handler();  // Handle LVGL tasks
  
  if (lora_mode == LORA_MODE_TX) {
    // TX mode: Send test message every 5 seconds
    static unsigned long last_tx_time = 0;
    if (millis() - last_tx_time > 5000) {
      sendTestMessage();
      last_tx_time = millis();
    }
  } else if (lora_mode == LORA_MODE_RX) {
    // 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
      checkReceivedMessage();
      last_rx_check = millis();
    }
  }
  
  delay(5);
}

