XIAO ESP32S3 Deep Sleep

I was wondering if there is a way to disable certain pins on the esp32 such as my i2c and uart pins as during deep sleep, they are still pulling current which is not ideal. I have tried using rtc_gpio_isolate and. disabling the pullup and pulldown pins and I am just getting really confused. Any advice will be appreciated, I have attached my code below:

#include <Arduino.h>
#include <U8x8lib.h>
#include <Wire.h>
#include <string.h>
#include <cstring>

#include "GPSManager.h"
#include "SDManager.h"
#include "SensorReader.h"
#include "SensorStruct.h"

// Minimum GPS accuracy required for data recording
const int GPS_ACCURACY = 6;

// Set to true to enable GPSECHO for debugging
#define GPSECHO false

// Pin definitions
const uint8_t BUTTON_PIN = 2;
const int SD_CARD_PIN = 3;
const int SD_CARD_CS_PIN = D2;
const int TEMP_WIRE_BUS = 1;

// Constants for LoRaWAN configuration
static char recv_buf[512];
static bool is_exist = false;
static bool is_join = false;

// Initialize manager objects
GPSManager gpsManager;
SDManager sdManager(SD_CARD_PIN, SD_CARD_CS_PIN);
SensorData sensorData;
SensorReader sensorReader(TEMP_WIRE_BUS);

// RTC-backed variables for persistent storage
RTC_DATA_ATTR unsigned int bootCount = 0;
RTC_DATA_ATTR char dataBuffer[1000];
RTC_DATA_ATTR unsigned int bufferIndex = 0;
RTC_DATA_ATTR unsigned int bufferWriteCount = 0;

// Conversion factors for sleep timing, GPS Timeout and Buffer for SD Card
#define CONVERT_S_TO_US 1000000ULL
#define CONVERT_M_TO_S  60
#define CONVERT_H_TO_S  3600
#define CONVERT_D_TO_S  86400
#define GPS_TIME_LOCK 120000
#define BUFFER_THRESHOLD 0.7
#define TIME_TO_SLEEP  30

RTC_DATA_ATTR unsigned long long sleepDuration = TIME_TO_SLEEP * CONVERT_S_TO_US;

// Function to send AT commands to the LoRaWAN module and check the response
static int at_send_check_response(const char* expected_ack, int timeout_ms, const char* command_format, ...)
{
  memset(recv_buf, 0, sizeof(recv_buf));

  // Print the command to both Serial1 and Serial
  va_list args;
  va_start(args, command_format);
  Serial1.printf(command_format, args);
  Serial.printf(command_format, args);
  va_end(args);

  // Wait a short delay for the command to be sent
  delay(200);

  // If no expected_ack, return immediately
  if (expected_ack == nullptr)
  {
      return 0;
  }

  // Read the response from Serial1 and check for the expected_ack
  unsigned long start_time = millis();
  while (millis() - start_time < timeout_ms)
  {
      while (Serial1.available() > 0)
      {
          recv_buf[strlen(recv_buf)] = static_cast<char>(Serial1.read());
          // Serial.print(recv_buf[strlen(recv_buf) - 1]);
          delay(2);
      }
      if (strstr(recv_buf, expected_ack) != nullptr)
      {
          return 1;
      }
  }

  // Timeout reached without finding the expected_ack
  return 0;
}

// Function to parse the received data from the LoRaWAN module
static void recv_parse(char *message, SensorData& sensorData)
{
  if (message == NULL)
  {
    return;
  }

  int port = 0;
  unsigned long timing = 0;
  int rssi = 0; 
  int snr = 0;
  
  // Parse the port number from the received message
  char *start = strstr(message, "PORT:");
  if (start && (1 == sscanf(start, "PORT: %d;", &port)))
  {
    // Parse the timing value from the received message
    start = strstr(message, "RX:");
    if (start && (1 == sscanf(start, "RX: \"%lx\"", &timing)))
    {
      // Go through the different ports which indicate a different timing slot
      switch (port)
      {
        case 1:
          // Convert to minutes
          sleepDuration = timing * CONVERT_M_TO_S * CONVERT_S_TO_US;
          break;
        case 2:
          // Convert to hours
          sleepDuration = timing * CONVERT_H_TO_S * CONVERT_S_TO_US;
          break;
        case 3:
          sleepDuration = timing * CONVERT_D_TO_S * CONVERT_S_TO_US;
          break;
      }
    }
  }
  
  // Parse the RSSI value from the received message
  start = strstr(message, "RSSI");
  if (start && (1 == sscanf(start, "RSSI %d,", &rssi)))
  {
    sensorData.rssi = rssi;
  }

  // Parse the SNR value from the received message
  start = strstr(message, "SNR");
  if (start && (1 == sscanf(start, "SNR %d", &snr)))
  {
    sensorData.snr = snr;
  }
}

// Method to initialize LoRaWAN module and configure parameters
bool initaliseLoRaWAN()
{ 
  // Check if E5 Module exists
  if(at_send_check_response("+AT: OK", 100, "AT\r\n"))
  {
    // Configure LoRaWAN parameters
    const int TIMEOUT_SHORT = 1000;
    const int TIMEOUT_LONG = 12000;

    // Configure parameters
    at_send_check_response("+ID:", TIMEOUT_SHORT, "AT+ID\r\n");           // Get device ID
    at_send_check_response("+MODE: LWOTAA", TIMEOUT_SHORT, "AT+MODE=LWOTAA\r\n");   // Select LoRaWAN mode
    at_send_check_response("+DR: EU868", TIMEOUT_SHORT, "AT+DR=EU868\r\n"); // Select frequency band
    at_send_check_response("+CH: NUM", TIMEOUT_SHORT, "AT+CH=NUM,0-2\r\n"); // Select sub band
    at_send_check_response("+ID: DevEui", TIMEOUT_SHORT, "AT+ID=DevEui,\"70B3D57ED00655FB\"\r\n");  // Write device EUI
    at_send_check_response("+ID: AppEui", TIMEOUT_SHORT, "AT+ID=AppEui,\"0128944F83E7AF55\"\r\n");  // Write application EUI
    at_send_check_response("+KEY: APPKEY", TIMEOUT_SHORT, "AT+KEY=APPKEY,\"7A48DD0E7F43A8FC53A5F018087C9508\"\r\n");  // Write application key
    at_send_check_response("+CLASS: C", TIMEOUT_SHORT, "AT+CLASS=A\r\n"); // Write device class
    at_send_check_response("+PORT: 8", TIMEOUT_SHORT, "AT+PORT=8\r\n");   // Set port number
    at_send_check_response("+ADR: ON", TIMEOUT_SHORT, "AT+ADR=ON\r\n");   // Enable Adaptive Data Rate

    // Delay for stability
    delay(200);

    // Attempt to join the LoRaWAN network
    return at_send_check_response("+JOIN: Network joined", TIMEOUT_LONG, "AT+JOIN\r\n");
  }
  
  // If the LoRa Module cannot be seen, return false
  return false;
}

bool sendDataOverLoRaWAN(SensorData& sensorData)
{
  // Convert sensor values to integers for transmission
  int temp_int = static_cast<uint32_t>(sensorData.temp * 10);
  int ph_int = static_cast<uint8_t>(sensorData.ph * 10);
  int tds_int = static_cast<uint16_t>(sensorData.tds * 100);
  int turbidity_int = static_cast<uint16_t>(sensorData.turbidity * 100);
  int ir_int = static_cast<uint16_t>(sensorData.ir);
  int visible_int = static_cast<uint16_t>(sensorData.visible);
  int latitude_int = static_cast<uint64_t>(sensorData.latitude * 1000000);
  int longitude_int = static_cast<uint64_t>(sensorData.longitude * 1000000);
  int speed_int = static_cast<uint16_t>(sensorData.speed * 100);
  int angle_int = static_cast<uint16_t>(sensorData.angle * 100);
  int altitude_int = static_cast<uint16_t>(sensorData.altitude * 100);
  int satellites_int = sensorData.satellites;
  int fix_int = sensorData.fix;
  int quality_int = sensorData.quality;

  // Construct the uplink message in hexadecimal format
  char uplink[128];
  sprintf(uplink, "AT+CMSGHEX=\"%08X%02X%04X%04X%04X%04X%016X%016X%04X%04X%04X%02X%02X%02X\"\r\n",
          temp_int, ph_int, tds_int, turbidity_int, ir_int, visible_int,
          latitude_int, longitude_int, speed_int, angle_int, altitude_int, satellites_int, fix_int, quality_int);

  // Check if the uplink has gone through. Will always come back with a fail
  at_send_check_response("Done", 5000, uplink);

  // Send a zero-length payload to allow the downlink to be read
  char downlink[128];
  sprintf(downlink, "AT+CMSGHEX");

  if (at_send_check_response("Done", 12000, downlink))
  {
    recv_parse(recv_buf, sensorData);
    return true;    // If the Data has been sent return true
  }

  return false;   // If the Data has not been sent return false
}

// Function to add data to the buffer and write to the SD card if necessary
void addToBufferAndWriteToFile(const String& dataString)
{
  size_t availableSpace = sizeof(dataBuffer) - bufferIndex - 1;

  // If buffer is full or dataString cannot fit in the remaining space
  if (bufferWriteCount >= BUFFER_THRESHOLD * 100 || dataString.length() > availableSpace)
  {
    resetBuffer(); // Write buffer to SD card and reset
  }

  // Determine how much of dataString can fit in the remaining space
  size_t copyLength = min(dataString.length(), availableSpace);

  // Copy dataString to buffer
  strncpy(dataBuffer + bufferIndex, dataString.c_str(), copyLength);
  bufferIndex += copyLength;
  dataBuffer[bufferIndex++] = '\n'; // Add newline
  dataBuffer[bufferIndex] = '\0'; // Null terminate the string

  // Increment buffer write count
  bufferWriteCount++;
}

// Function to write the buffer to the SD card and reset the buffer
void resetBuffer()
{
  const char* DATA_FILE_PATH = "/SensorData.txt";
  sdManager.initialise();
  bool written = sdManager.writeToFile(DATA_FILE_PATH, dataBuffer);

  if (written)
  {
    memset(dataBuffer, 0, sizeof(dataBuffer));
    bufferIndex = 0;
    bufferWriteCount = 0;
  } else
  {
    size_t copyLength = bufferIndex; // Keep track of current data length

    // Check if there's enough space to accommodate both existing and new data
    if (copyLength + bufferIndex < sizeof(dataBuffer))
    {
      // Shift existing data to make room for new data
      memmove(dataBuffer, dataBuffer + copyLength, sizeof(dataBuffer) - copyLength);
      
      // Reset buffer index and write count
      bufferIndex = copyLength;
      bufferWriteCount++;
    } else
    {
      // Handle case where buffer is not large enough to accommodate both data
      // For simplicity the buffer is cleared
      bufferIndex = 0;
      bufferWriteCount = 0;
    }
  }
}

void setup() {
  // Take some time to open up the Serial Monitor
  Serial.begin(115200);
  delay(1000);

  // Begin serial communication to allow for LoRa communication
  Serial1.begin(9600, SERIAL_8N1, 44, 43);

  //Initialise the sensors
  Wire.begin();
  sensorReader.initialise();
  gpsManager.setup();
  
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));
  
  unsigned long startTime = millis(); // Get the start time
  
  // Wait for GPS to acquire satellites
  while (sensorData.satellites == 0.0)
  {
    gpsManager.update(sensorData);
    
    // Check if 2 minutes have elapsed
    if (millis() - startTime >= GPS_TIME_LOCK)
    {
      //Satellite search timed out
      break; // Exit the loop if 2 minutes have elapsed
    }
  }
  
  // Read sensor values
  sensorReader.readSensorValues(sensorData);

  // Attempt to initialize LoRaWAN module and send data if initialization succeeds
  sensorData.loraSent = initaliseLoRaWAN() && sendDataOverLoRaWAN(sensorData);

  String dataString = sensorReader.getSensorValuesAsString(sensorData);
  addToBufferAndWriteToFile(dataString);
  Serial.println(dataBuffer);

  // Disable the I2C, UART, and other RTC peripherals before going to sleep
  esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
  
  // Enable deep sleep mode
  esp_sleep_enable_timer_wakeup(sleepDuration);
  Serial.println("Going to sleep for " + String(sleepDuration / CONVERT_S_TO_US) + " seconds");
  Serial.flush();
  esp_deep_sleep_start();
}

void loop()
{
  // This loop is empty as the device will be in deep sleep mode
}