High deepsleep consumption SDI-12 RAK13010

Hi @carlrowan, thanks for your reply.
Here is the complete version of the sketch (I’ve used the deep sleep example from the RAK4631 to implement this one):

#include <Arduino.h>
#include <SPI.h>

#include <LoRaWan-RAK4630.h>

#include "RAK13010_SDI12.h"

#define TX_PIN WB_IO6 // The pin of the SDI-12 data bus.
#define RX_PIN WB_IO5 // The pin of the SDI-12 data bus.
#define OE WB_IO4     // Output enable pin, active low.

#define SENSOR_ADDRESS 'a'

RAK_SDI12 mySDI12(RX_PIN, TX_PIN, OE);

String sdiResponse = "";
int sdiCommandIndex = 1;
String sdiCommand = "M1!";

float waterContent, ecPore, temperature, batteryVoltage, ecBulk, permittivity;

#define PIN_VBAT WB_A0
uint32_t vbat_pin = PIN_VBAT;
#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12 - bit ADC resolution = 3000mV / 4096
#define VBAT_DIVIDER_COMP (1.73)      // Compensation factor for the VBAT divider, depend on the board
#define PIN_LORA_DIO_1 47             // DIO1 GPIO pin for RAK4631
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
#define LORAWAN_APP_DATA_BUFF_SIZE 64                                         // Max size of the data to be transmitted.
#define JOINREQ_NBTRIALS 8                                                    // Number of trials for the join request.
static uint8_t m_lora_app_data_buffer[LORAWAN_APP_DATA_BUFF_SIZE];            // Lora application data buffer.
static lmh_app_data_t m_lora_app_data = {m_lora_app_data_buffer, 0, 0, 0, 0}; // Lora application data structure.
// LoRaWan event handlers
static void lorawan_has_joined_handler(void);
static void lorawan_join_failed_handler(void);
static void lorawan_rx_handler(lmh_app_data_t *app_data);
static void lorawan_confirm_class_handler(DeviceClass_t Class);
bool sendLoRaFrame(void);

/**
 * @brief Structure containing LoRaWan parameters, needed for lmh_init()
   Set structure members to
   LORAWAN_ADR_ON or LORAWAN_ADR_OFF to enable or disable adaptive data rate
   LORAWAN_DEFAULT_DATARATE OR DR_0 ... DR_5 for default data rate or specific data rate selection
   LORAWAN_PUBLIC_NETWORK or LORAWAN_PRIVATE_NETWORK to select the use of a public or private network
   JOINREQ_NBTRIALS or a specific number to set the number of trials to join the network
   LORAWAN_DEFAULT_TX_POWER or a specific number to set the TX power used
   LORAWAN_DUTYCYCLE_ON or LORAWAN_DUTYCYCLE_OFF to enable or disable duty cycles
                     Please note that ETSI mandates duty cycled transmissions.
*/
static lmh_param_t lora_param_init = {LORAWAN_ADR_ON, DR_3, LORAWAN_PUBLIC_NETWORK, JOINREQ_NBTRIALS, LORAWAN_DEFAULT_TX_POWER, LORAWAN_DUTYCYCLE_ON};
/** Structure containing LoRaWan callback functions, needed for lmh_init() */
static lmh_callback_t lora_callbacks = {BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed, lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler, lorawan_join_failed_handler};

// Comment the next line if you want DEBUG output. But the power savings are not as good then!!!!!!!
#define MAX_SAVE
/* Time the device is sleeping in milliseconds = 2 minutes * 60 seconds * 1000 milliseconds */
#define SLEEP_TIME 20 * 60 * 1000

uint8_t nodeDeviceEUI[8] = {0xAC, 0x1F, 0x09, 0xFF, 0xFE, 0x08, 0xF5, 0x20};
uint8_t nodeAppEUI[8] = {0x45, 0x34, 0x52, 0x34, 0x23, 0x49, 0x20, 0x34};
uint8_t nodeAppKey[16] = {0x3A, 0xCC, 0x06, 0xB0, 0xE9, 0x13, 0xAB, 0x7C, 0x99, 0x94, 0xB6, 0x89, 0x50, 0x0C, 0xDF, 0x5B};
DeviceClass_t gCurrentClass = CLASS_A;
LoRaMacRegion_t gCurrentRegion = LORAMAC_REGION_EU868;

/** Semaphore used by events to wake up loop task */
SemaphoreHandle_t taskEvent = NULL;
/** Timer to wakeup task frequently and send message */
SoftwareTimer taskWakeupTimer;

/** Buffer for received LoRaWan data */
uint8_t rcvdLoRaData[256];
/** Length of received data */
uint8_t rcvdDataLen = 0;

/**
 * @brief Flag for the event type
 * -1 => no event
 * 0 => LoRaWan data received
 * 1 => Timer wakeup
 * 2 => tbd
 * ...
 */
uint8_t eventType = -1;

/**
 * @brief Timer event that wakes up the loop task frequently
 *
 * @param unused
 */
void periodicWakeup(TimerHandle_t unused)
{
  eventType = 1;
  // Give the semaphore, so the loop task will wake up
  xSemaphoreGiveFromISR(taskEvent, pdFALSE);
}

float readVBAT(void)
{
  // Set the analog reference to 3.0V (default = 3.6V)
  analogReference(AR_INTERNAL_3_0);
  // Set the resolution to 12-bit (0..4095)
  analogReadResolution(12); // Can be 8, 10, 12 or 14
  // Let the ADC settle
  delay(1);
  float raw;
  // Get the raw 12-bit, 0..3000mV ADC value
  raw = analogRead(vbat_pin);
  return raw * REAL_VBAT_MV_PER_LSB;
}

/*
 * Get the response as string from the SDI-12, split by "+" sign and
 * assign the values to the variables waterContent, ecPore and temperature.
 */
void parseResponse(String response)
{
  int index = 0;
  int nextIndex = 0;
  int counter = 0;
  String value = "";
  while (index < response.length())
  {
    nextIndex = response.indexOf('+', index);

    // If no other "+" sign is found, get the rest of the string.
    if (nextIndex == -1)
    {
      nextIndex = response.length();
    }

    value = response.substring(index, nextIndex);
    switch (counter)
    {
    case 0:
      // Address of the sensor
      break;
    case 1:
      waterContent = value.toFloat();
      break;
    case 2:
      ecPore = value.toFloat();
      break;
    case 3:
      temperature = value.toFloat();
      break;
    case 4:
      ecBulk = value.toFloat();
      break;
    case 5:
      permittivity = value.toFloat();
      break;
    default:
      break;
    }

    index = nextIndex + 1;
    counter = counter + 1;
  }
}

void setAddress()
{
  // Power the sensor.
  pinMode(WB_IO2, OUTPUT);
  digitalWrite(WB_IO2, HIGH);
  delay(3000);
  mySDI12.begin();
  delay(500);
  mySDI12.clearBuffer();
  delay(3000);
  mySDI12.sendCommand("0Aa!");
  delay(30);

  mySDI12.end();
  digitalWrite(WB_IO2, LOW);
}

void readFromSensor()
{
  // Power the sensor.
  pinMode(WB_IO2, OUTPUT);
  digitalWrite(WB_IO2, HIGH);
  delay(1500);
  mySDI12.begin();
  delay(500);
  mySDI12.clearBuffer();
  delay(500);
  mySDI12.sendCommand(String(SENSOR_ADDRESS) + sdiCommand);
  delay(30);

  sdiResponse = mySDI12.readStringUntil('\n');
#ifndef MAX_SAVE
  Serial.println("Response TIME: " + sdiResponse);
#endif

  // Should be gather from the response
  delay(2000);

  mySDI12.clearBuffer();
  mySDI12.sendCommand(String(SENSOR_ADDRESS) + "D0!");
  sdiResponse = mySDI12.readStringUntil('\n');
#ifndef MAX_SAVE
  Serial.println("Response: " + sdiResponse);
#endif
  parseResponse(sdiResponse);

  mySDI12.end();
  digitalWrite(WB_IO2, LOW);
}

/**
   @brief Initialize LoRa HW and LoRaWan MAC layer

   @return int8_t result
    0 => OK
   -1 => SX126x HW init failure
   -2 => LoRaWan MAC initialization failure
   -3 => Subband selection failure
*/
int8_t initLoRaWan(void)
{
  // Initialize LoRa chip.
  if (lora_rak4630_init() != 0)
  {
    return -1;
  }

  // Setup the EUIs and Keys
  lmh_setDevEui(nodeDeviceEUI);
  lmh_setAppEui(nodeAppEUI);
  lmh_setAppKey(nodeAppKey);

  // Initialize LoRaWan
  if (lmh_init(&lora_callbacks, lora_param_init, true, gCurrentClass, gCurrentRegion) != 0)
  {
    return -2;
  }

  // For some regions we might need to define the sub band the gateway is listening to
  // This must be called AFTER lmh_init()
  if (!lmh_setSubBandChannels(1))
  {
    return -3;
  }

  // Start Join procedure
#ifndef MAX_SAVE
  Serial.println("Start network join request");
#endif
  lmh_join();

  return 0;
}

/**
   @brief LoRa function for handling HasJoined event.
*/
static void lorawan_has_joined_handler(void)
{
  uint32_t otaaDevAddr = lmh_getDevAddr();
#ifndef MAX_SAVE
  Serial.printf("OTAA joined and got dev address %08X\n", otaaDevAddr);
#endif

  // Default is Class A, where the SX1262 transceiver is in sleep mode unless a package is sent
  // If switched to Class C the power consumption is higher because the SX1262 chip remains in RX mode
  // lmh_class_request(CLASS_C);

  // Now we are connected, start the timer that will wakeup the loop frequently
  taskWakeupTimer.begin(SLEEP_TIME, periodicWakeup);
  taskWakeupTimer.start();
}
/**@brief LoRa function for handling OTAA join failed
 */
static void lorawan_join_failed_handler(void)
{
  Serial.println("OVER_THE_AIR_ACTIVATION failed!");
  Serial.println("Check your EUI's and Keys's!");
  Serial.println("Check if a Gateway is in range!");
}
/**
   @brief Function for handling LoRaWan received data from Gateway

   @param app_data  Pointer to rx data
*/
static void lorawan_rx_handler(lmh_app_data_t *app_data)
{
#ifndef MAX_SAVE
  Serial.printf("LoRa Packet received on port %d, size:%d, rssi:%d, snr:%d\n",
                app_data->port, app_data->buffsize, app_data->rssi, app_data->snr);
#endif
  switch (app_data->port)
  {
  case 3:
    // Port 3 changes the command for the sensor
    if (app_data->buffsize == 1)
    {
      sdiCommandIndex = app_data->buffer[0];
      sdiCommand = "M" + String(sdiCommandIndex) + "!";
    }
    break;

  case LORAWAN_APP_PORT:
    // Copy the data into loop data buffer
    memcpy(rcvdLoRaData, app_data->buffer, app_data->buffsize);
    rcvdDataLen = app_data->buffsize;
    eventType = 0;
    // Notify task about the event
    if (taskEvent != NULL)
    {
#ifndef MAX_SAVE
      Serial.println("Waking up loop task");
#endif
      xSemaphoreGive(taskEvent);
    }
  }
}

/**
   @brief Callback for class switch confirmation

   @param Class The new class
*/
static void lorawan_confirm_class_handler(DeviceClass_t Class)
{
#ifndef MAX_SAVE
  Serial.printf("switch to class %c done\n", "ABC"[Class]);
#endif

  // Informs the server that switch has occurred ASAP
  m_lora_app_data.buffsize = 0;
  m_lora_app_data.port = LORAWAN_APP_PORT;
  lmh_send(&m_lora_app_data, LMH_UNCONFIRMED_MSG);
}

/**
   @brief Send a LoRaWan package

   @return result of send request
*/
bool sendLoRaFrame(void)
{
  if (lmh_join_status_get() != LMH_SET)
  {
    // Not joined, try again later
#ifndef MAX_SAVE
    Serial.println("Did not join network, skip sending frame");
#endif
    return false;
  }

  // KIND: ecp
  m_lora_app_data.port = 7;
  batteryVoltage = readVBAT();

  //******************************************************************
  /// \todo here some more usefull data should be put into the package
  //******************************************************************

  uint8_t buffSize = 0;
  // First byte is here for future use
  m_lora_app_data_buffer[buffSize++] = highByte(00);
  m_lora_app_data_buffer[buffSize++] = lowByte(00);
  // TODO battery voltage
  m_lora_app_data_buffer[buffSize++] = highByte((int)batteryVoltage);
  m_lora_app_data_buffer[buffSize++] = lowByte((int)batteryVoltage);
  m_lora_app_data_buffer[buffSize++] = highByte((int)(waterContent * 100));
  m_lora_app_data_buffer[buffSize++] = lowByte((int)(waterContent * 100));
  m_lora_app_data_buffer[buffSize++] = highByte((int)(temperature * 100));
  m_lora_app_data_buffer[buffSize++] = lowByte((int)(temperature * 100));
  m_lora_app_data_buffer[buffSize++] = highByte((int)(ecPore * 10));
  m_lora_app_data_buffer[buffSize++] = lowByte((int)(ecPore * 10));
  m_lora_app_data_buffer[buffSize++] = highByte((int)(SLEEP_TIME / 1000));
  m_lora_app_data_buffer[buffSize++] = lowByte((int)(SLEEP_TIME / 1000));
  m_lora_app_data_buffer[buffSize++] = highByte((int)(sdiCommandIndex));
  m_lora_app_data_buffer[buffSize++] = lowByte((int)(sdiCommandIndex));

  m_lora_app_data.buffsize = buffSize;

  lmh_error_status error = lmh_send(&m_lora_app_data, LMH_UNCONFIRMED_MSG);

  return (error == 0);
}

/**
 * @brief Arduino setup function. Called once after power-up or reset
 *
 */
void setup(void)
{
  setAddress();
  // Create the LoRaWan event semaphore
  taskEvent = xSemaphoreCreateBinary();
  // Initialize semaphore
  xSemaphoreGive(taskEvent);

  // Initialize LoRaWan and start join request
  int8_t loraInitResult = initLoRaWan();

#ifndef MAX_SAVE
  if (loraInitResult != 0)
  {
    switch (loraInitResult)
    {
    case -1:
      Serial.println("SX126x init failed");
      break;
    case -2:
      Serial.println("LoRaWan init failed");
      break;
    case -3:
      Serial.println("Subband init error");
      break;
    case -4:
      Serial.println("LoRa Task init error");
      break;
    default:
      Serial.println("LoRa init unknown error");
      break;
    }

    // Without working LoRa we just stop here
    while (1)
    {
      Serial.println("Nothing I can do, just loving you");
      delay(5000);
    }
  }
  Serial.println("LoRaWan init success");
#endif

  // // Take the semaphore so the loop will go to sleep until an event happens
  // xSemaphoreTake(taskEvent, 10);
  eventType = 1;
}

/**
 * @brief Arduino loop task. Called in a loop from the FreeRTOS task handler
 *
 */
void loop(void)
{
  // Sleep until we are woken up by an event
  if (xSemaphoreTake(taskEvent, portMAX_DELAY) == pdTRUE)
  {
    // Check the wake up reason
    switch (eventType)
    {
    case 0: // Wakeup reason is package downlink arrived
#ifndef MAX_SAVE
      Serial.println("Received package over LoRaWan");
#endif
      if (rcvdLoRaData[0] > 0x1F)
      {
#ifndef MAX_SAVE
        Serial.printf("%s\n", (char *)rcvdLoRaData);
#endif
      }
      else
      {
#ifndef MAX_SAVE
        for (int idx = 0; idx < rcvdDataLen; idx++)
        {
          Serial.printf("%X ", rcvdLoRaData[idx]);
        }
        Serial.println("");
#endif
      }

      break;
    case 1: // Wakeup reason is timer
#ifndef MAX_SAVE
      Serial.println("Timer wakeup");
#endif
      readFromSensor();
      // Send the data package
      if (sendLoRaFrame())
      {
#ifndef MAX_SAVE
        Serial.println("LoRaWan package sent successfully");
#endif
      }
      else
      {
#ifndef MAX_SAVE
        Serial.println("LoRaWan package send failed");
        /// \todo maybe you need to retry here?
#endif
      }

      break;
    default:
#ifndef MAX_SAVE
      Serial.println("This should never happen ;-)");
#endif
      break;
    }
    // Go back to sleep
    xSemaphoreTake(taskEvent, 10);
  }
}