ESP32 LoRa gateway battery optimized

I really love the tutorial written by JP how to use ESP32 Deep Sleep. For the beginners who are not familiar with ESP-IDF and prefer to work with ArduinoIDE (or PlatformIO), I want to take JP’s lesson over to Arduino framework in a practical example.

In my other tutorial LoRa Gateway I showed you how to write the software for a simple LoRa gateway. This original code is not considering any power saving methods, so if you want to run your gateway from battery, it will not last very long.

Now for the example, I used an Adafruit ESP32 Feather board and the brand-new Circuitrocks Alora RFM1262 board. The RFM1262 is a new Feather compatible LoRa board with the latest Semtech SX1262 LoRa transceiver.

How can we save energy? The gateway must be running all the time, as we do not know at what times the sensor nodes will send their data packages. But there are a few options to make the gateway battery powered.

Reduce CPU frequency

As JP mentioned in his tutorial, we can reduce the frequency of the ESP32. If programming in ESP-IDF, this is done by setting the CPU frequency in the menu config. For Arduino users this option is available as well with the simple function setCpuFrequencyMhz(). By default the ESP32 is running on a 240MHz clock. This can be reduced to

  • 160MHz (WiFi and Bluetooth ok)
  • 80MHz (WiFi and Bluetooth ok)
  • 40MHz (no WiFi and no Bluetooth)
  • 26MHz (no WiFi and no Bluetooth)
  • 20MHz (no WiFi and no Bluetooth)
  • 13MHz (no WiFi and no Bluetooth)
  • 10MHz (no WiFi and no Bluetooth)

As you can already see in my comments, if you are planning to use WiFi or Bluetooth, you choices are limited. In the gateway we will need WiFi, because we need to forward the received LoRa data packages to the cloud.

So the preferred solution is to set the CPU frequency to 80MHz with the command setCpuFrequencyMhz(80);

Use Deep Sleep

JP’s tutorial shows us how to put the ESP32 into deep sleep and wake him up from a a touch-pad event. Here instead of the touch pad we will use an external signal. In deep sleep mode most parts of the ESP32 are turned off. That is what we are looking for.

To put the ESP32 into deep sleep the function is
esp_sleep_enable_ext0_wakeup(GPIO pin number, trigger edge);
followed by
esp_deep_sleep_start();
The first command tells the ESP32 on which GPIO it should look for a signal change defined by trigger edge (Rising or Falling). The second command will put the ESP32 into deep sleep mode.

We will use the SX1262 DIO1 signal as our external wake up signal. As DIO1 changes it status from Low to High on an event, we set the trigger edge to Rising.

esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_LORA_DIO_1, RISING);
esp_deep_sleep_start();

Receiving data while the ESP32 sleeps

But how do we receive LoRa messages while our ESP32 is sleeping? In the original gateway code we learned that we have to start listening to LoRa data packages with Radio.Rx(0), which will trigger an interrupt on DIO1 once the LoRa chip received a package and the ESP32 can read it from the SX126x. While waiting for a LoRa package, we kept the ESP32 running all the time just waiting for the interrupt (or a time out) to happen. Now instead of having the ESP32 awake while waiting for a data package, we will send it into deep sleep after we initialized the SX1262 in receive mode. But the function Radio.Rx() will stop after a given time with a RX timeout interrupt. So we need something else to avoid waking up the ESP32 without reason.

Luckily the Semtech SX126x LoRa transceivers have a very nice feature which is called RX Duty Cycle. When activating the RX Duty Cycle the SX126x will go into low power mode and wake up only for a short period of time to listen if any LoRa packages are about to be sent. Semtech has a good application note how this works, the document SX1261_AN1200.36_SX1261-2_RxDutyCycle_V1.0. I strongly recommend that you read this document before falling into a trap and your ESP32 is forever in deep sleep (unless you push the reset button). For those who don’t want to go through the whole document, I use some excerpts from the application to show you how RX Duty Cycle works and what settings need your special attention.

The principle of operation
RxDutyCycle is a special RX mode that allows decreasing power consumption by putting periodically the radio in reception, while keeping the microcontroller in sleep mode. When a packet is received by the SX1261/2 it triggers an interrupt for the microcontroller in order to wake it up.

Calculation of sleep and receive time length

The important stuff is to calculate the “Rx duration” and the “Sleep duration” times. If you set your sleep time too long, the SX1262 might miss a preamble which is the beginning of a data package.

See also  Controlling Servo Movement with a Joystick Using Arduino Uno

But how do we know the preamble length? Semtech has a nice tool, the “SX1261 Calculator Tool“. You definitly want to download and install this tool, because you need it to calculate the length of the sleep period.

Once you have the tool installed start it. In order to get the length of the preamble (which determines the length of the sleep period) you have to enter 4 parameters in the tool.

  • Spreading factor
  • Bandwidth
  • Code Rate
  • Preamble Length

These parameters define the length of a single symbol.

Check the screenshot above where I marked in yellow the parameters you need to enter. The red circled numbers are what we need to calculate the preamble length.

In above example I set the SF to 7, the bandwidth to 125kHz, the CR to 4/5 and the preamble length to 8. That is what I will use later in the code.

You can see that with this settings, a symbol has a length of 1024ms and the preamble is 12.25 symbols long. That means that our preamble length is 1024ms * 12.25 = 12544ms long. So we know we have to set our sleep time shorter than that. To be able to catch at least 2 symbols when trying to catch the beginning of a data packet, I defined the sleep time to 10 symbols length which is 1024ms * 10 = 10240ms.

Ok, so now we know how long we can put the SX126x into sleep without missing a package. For the receive window, the time the SX126x is actually awake and listening, I chose a time of 2 symbols which gives us 1024ms * 2 = 2048ms.

If we calculated the sleep and receive times correct, we will be able to catch any incoming data packet, even in the worst case that we see only the last two symbols of the preamble.

Power savings

Now if you look on these two numbers, sleep time 10ms and awake (receive) time 2ms, that means that the SX126x is basically sleeping 8/10th of the time. And the ESP32 will be in deep sleep as well most of the time and only wake up after the SX1262 received a data package. That should give us a good power saving.

Important!

As the RESET and CHIP-SELECT inputs of the SX1262 are floating while the ESP32 is in deep sleep, you will have to add pull-up resistors to this 2 inputs. I chose 10k Ohm resistors which works just fine.

Example code

Now we put the whole thing into a working example. To keep it simple, the example will just print the received data over the Serial debug output. At that point you will have to add code to process the data, send it to the cloud or whatever you want to do with it.

Prerequisite for the example code is that you have the SX126x-Arduino library version 1.0.5 installed. Only from this version on the deep sleep for the ESP32 is supported.

You can install the library over the library manager of ArduinoIDE or PlatformIO. Just search for SX126x-Arduino. It has some nice examples too for LoRa communication, LoRaWan and even a full deep sleep example

Looking into the code, you will see that the first thing to do is to check why the ESP32 started the code.

// Check the reasons the CPU's are started. We need this later to decide if a full initialization
// of the SX126x is required or if the chip just woke up triggered by an interrupt from the SX126x
RESET_REASON cpu0WakeupReason = rtc_get_reset_reason(0);
RESET_REASON cpu1WakeupReason = rtc_get_reset_reason(1);

We need to do this, because on a new start, we have to do a full initialization of the SX1262 chip, which includes a reset of the chip. This is what lora_hardware_init(hwConfig); does. But when we wake up from deep sleep after a data package was received, we do not want to do the reset, because it would erase the received data. lora_hardware_re_init(hwConfig); is what we want to use in this case.

// Initialize the LoRa chip
if ((cpu0WakeupReason == DEEPSLEEP_RESET) || (cpu1WakeupReason == DEEPSLEEP_RESET))
{
	// Wake up reason was a DEEPSLEEP_RESET, which means we were woke up by the SX126x
	Serial.println("Starting lora_hardware_re_init");
	lora_hardware_re_init(hwConfig);
}
else
{
	// Other wake up reasons mean we need to do a complete initialization of the SX126x
	Serial.println("Starting lora_hardware_init");
	lora_hardware_init(hwConfig);
}

Next is the initialization of the radio functions. Again, after a power-up start or a reset, a full initialization is required (frequency, RX and TX settings, …), while after waking up from deep sleep, we only need to do a basic initialization and can start reading the data from the SX1262.

if ((cpu0WakeupReason == DEEPSLEEP_RESET) || (cpu1WakeupReason == DEEPSLEEP_RESET))
{
	// Wake up reason was a DEEPSLEEP_RESET, just re-initialize the callbacks
	Serial.println("Trying to handle SX1262 event after deep sleep wakeup");
	// Re-Initialize the Radio
	Radio.ReInit(&RadioEvents);

	// Handle LoRa events to find out what the reason for the wake up was and handle the data
	Radio.IrqProcessAfterDeepSleep();
}
else
{
	Serial.println("Power on reset, reinitialize the Radio");
	// Other wake up reasons mean we need to do a complete initialization of the SX126x
	// Initialize the Radio
	Radio.Init(&RadioEvents);

	// Set Radio channel
	Radio.SetChannel(RF_FREQUENCY);

	// Set Radio TX configuration
	Radio.SetTxConfig(MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
				LORA_SPREADING_FACTOR, LORA_CODINGRATE,
				LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
				true, 0, 0, LORA_IQ_INVERSION_ON, TX_TIMEOUT_VALUE);

		// Set Radio RX configuration
	Radio.SetRxConfig(MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
				LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
				LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
				0, true, 0, 0, LORA_IQ_INVERSION_ON, true);
}

The function Radio.IrqProcessAfterDeepSleep(); will check the IRQ status of the SX1262 and then call the approriate call back function, which in this case will be OnRxDone()

/**@brief Function to be executed on Radio Rx Done event
 */
void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr)
{
	Serial.println("Receive finished");
	char debugOutput[1024];
	for (int idx = 0; idx < size; idx++)
	{
		sprintf(&debugOutput[(idx * 2)], "%02X", payload[idx]);
	}
	Serial.printf("Data: %s\n", debugOutput);

	// Now its up to YOU to do something with the data

	// LoRa receive finished, go back to bed
	goToSleep();
}

The OnRxDone function receives a pointer to the date, the size of the packet, the signal strength and the signal to noise ratio.
As said earlier, here we just print out the data, but in a real application, we would start WiFi, connect to an AP and send the data to the cloud.
After the data is processed, we go back into sleep by calling goToSleep();

See also  Alora RFM1262 - LoRa Mesh Network

The other Radio even callbacks are not used in this example. But as you can see, just in case they are called, all of them end up with a call to goToSleep().

/**
 * Put SX126x into RX mode and ESP32 into deep-sleep mode
 */
void goToSleep(void)
{
	// Start waiting for data package
	Radio.Standby();
	SX126xSetDioIrqParams(IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
				IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
				IRQ_RADIO_NONE, IRQ_RADIO_NONE);
	// To get maximum power savings we use Radio.SetRxDutyCycle instead of Radio.Rx(0)
	// This function keeps the SX1261/2 chip most of the time in sleep and only wakes up short times
	// to catch incoming data packages 
	Radio.SetRxDutyCycle(2 * 1024 * 1000 * 15.625, 10 * 1024 * 1000 * 15.625);

	// Go back to bed
	Serial.println("Start sleeping");
    // Make sure the DIO1, RESET and NSS GPIOs are hold on required levels during deep sleep 
	rtc_gpio_set_direction((gpio_num_t)PIN_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
	rtc_gpio_pulldown_en((gpio_num_t)PIN_LORA_DIO_1);
	// Setup deep sleep with wakeup by external source
	esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_LORA_DIO_1, RISING);
	// Finally set ESP32 into sleep
	esp_deep_sleep_start();
}

In goToSleep, the first thing we do is to initiate the RX Duty Cycle function of the SX1262 with the above calculated sleep and receive times.
Radio.SetRxDutyCycle (receive time in 15.625 uS fragments, sleep time in 15.625 uS fragments) is the function. So we use 2 symbols for the receive time and 10 symbols for the sleep time.
Radio.SetRxDutyCycle(2 * 1024 * 1000 * 15.625, 10 * 1024 * 1000 * 15.625);
If you are wondering about the * 15.625 in the calculation, the SX1262 expects the time values not in milliseconds, but in fractions of 15.625 to match its internal clock.

Then to avoid that the SX1262 gets confused by some random levels on its RESET or NSS (chip select) inputs, we ask the RTC (one of the few things awake while the ESP32 is in deep sleep) to keep these GPIOs on a fixed level.

And last, we setup the deep sleep with external wake up interrupt and finally put the ESP32 into deep sleep.

And here is the complete code:

#include <Arduino.h>
#include <SPI.h>
#include <Ticker.h>
#include <rom/rtc.h>
#include <driver/rtc_io.h>
#include <SX126x-Arduino.h>

#define LOG_ON

// ESP32 Feather - SX126x pin configuration
/** LORA RESET */
int PIN_LORA_RESET = 32;
/** LORA DIO_1 */
int PIN_LORA_DIO_1 = 14;
/** LORA SPI BUSY */
int PIN_LORA_BUSY = 27;
/** LORA SPI CS */
int PIN_LORA_NSS = 33;
/** LORA SPI CLK */
int PIN_LORA_SCLK = 5;
/** LORA SPI MISO */
int PIN_LORA_MISO = 19;
/** LORA SPI MOSI */
int PIN_LORA_MOSI = 18;

/** LED on level */
#define LED_ON HIGH
/** LED off level */
#define LED_OFF LOW

// Define LoRa parameters
#define RF_FREQUENCY 915000000  // Hz
#define TX_OUTPUT_POWER 22		// dBm
#define LORA_BANDWIDTH 0		// [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
#define LORA_SPREADING_FACTOR 7 // [SF7..SF12]
#define LORA_CODINGRATE 1		// [1: 4/5, 2: 4/6,  3: 4/7,  4: 4/8]
#define LORA_PREAMBLE_LENGTH 8  // Same for Tx and Rx
#define LORA_SYMBOL_TIMEOUT 0   // Symbols
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false
#define RX_TIMEOUT_VALUE 3000
#define TX_TIMEOUT_VALUE 3000

#define BUFFER_SIZE 512 // Define the payload size here

/** Lora events */
static RadioEvents_t RadioEvents;

/** LoRa HW configuration */
hw_config hwConfig;

/** CAD repeat counter */
uint8_t cadRepeat;

// Event declarations
/** LoRa transmit success */
void OnTxDone(void);
/** LoRa receive success */
void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr);
/** LoRa transmit timeout */
void OnTxTimeout(void);
/** LoRa receive timeout */
void OnRxTimeout(void);
/** LoRa receive error */
void OnRxError(void);
/** LoRa CAD finished */
void OnCadDone(bool cadResult);

/** Print reset reason */
void print_reset_reason(RESET_REASON reason);

/** Node ID */
uint8_t deviceId[8];

/**
 * Put SX126x into RX mode and ESP32 into deep-sleep mode
 */
void goToSleep(void)
{
	// Start waiting for data package
	Radio.Standby();
	SX126xSetDioIrqParams(IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
						  IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
						  IRQ_RADIO_NONE, IRQ_RADIO_NONE);
	// To get maximum power savings we use Radio.SetRxDutyCycle instead of Radio.Rx(0)
	// This function keeps the SX1261/2 chip most of the time in sleep and only wakes up short times
	// to catch incoming data packages 
	Radio.SetRxDutyCycle(2 * 1024 * 1000 * 15.625, 10 * 1024 * 1000 * 15.625);

	// Go back to bed
#ifdef LOG_ON
	Serial.println("Start sleeping");
#endif
    // Make sure the DIO1, RESET and NSS GPIOs are hold on required levels during deep sleep 
	rtc_gpio_set_direction((gpio_num_t)PIN_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
	rtc_gpio_pulldown_en((gpio_num_t)PIN_LORA_DIO_1);
	// Setup deep sleep with wakeup by external source
	esp_sleep_enable_ext0_wakeup((gpio_num_t)PIN_LORA_DIO_1, RISING);
	// Finally set ESP32 into sleep
	esp_deep_sleep_start();
}

/**
 * ESP32 startup
 */
void setup()
{
#ifdef LOG_ON
	// Start serial communication
	Serial.begin(115200);
#endif

	// Check the reasons the CPU's are started. We need this later to decide if a full initialization
	// of the SX126x is required or if the chip just woke up triggered by an interrupt from the SX126x
	RESET_REASON cpu0WakeupReason = rtc_get_reset_reason(0);
	RESET_REASON cpu1WakeupReason = rtc_get_reset_reason(1);

#ifdef LOG_ON
	Serial.println("CPU0 reset reason: ");
	print_reset_reason(cpu0WakeupReason);

	Serial.println("CPU1 reset reason: ");
	print_reset_reason(cpu1WakeupReason);
#endif

	// Show we are awake
	pinMode(LED_BUILTIN, OUTPUT);
	digitalWrite(LED_BUILTIN, LED_ON);

	// Define the HW configuration between MCU and SX126x
	hwConfig.CHIP_TYPE = SX1262_CHIP;		  // Example uses an eByte E22 module with an SX1262
	hwConfig.PIN_LORA_RESET = PIN_LORA_RESET; // LORA RESET
	hwConfig.PIN_LORA_NSS = PIN_LORA_NSS;	 // LORA SPI CS
	hwConfig.PIN_LORA_SCLK = PIN_LORA_SCLK;   // LORA SPI CLK
	hwConfig.PIN_LORA_MISO = PIN_LORA_MISO;   // LORA SPI MISO
	hwConfig.PIN_LORA_DIO_1 = PIN_LORA_DIO_1; // LORA DIO_1
	hwConfig.PIN_LORA_BUSY = PIN_LORA_BUSY;   // LORA SPI BUSY
	hwConfig.PIN_LORA_MOSI = PIN_LORA_MOSI;   // LORA SPI MOSI
	hwConfig.RADIO_TXEN = -1;				  // LORA ANTENNA TX ENABLE
	hwConfig.RADIO_RXEN = -1;				  // LORA ANTENNA RX ENABLE
	hwConfig.USE_DIO2_ANT_SWITCH = true;	  // Example uses an CircuitRocks Alora RFM1262 which uses DIO2 pins as antenna control
	hwConfig.USE_DIO3_TCXO = true;			  // Example uses an CircuitRocks Alora RFM1262 which uses DIO3 to control oscillator voltage
	hwConfig.USE_DIO3_ANT_SWITCH = false;	 // Only Insight ISP4520 module uses DIO3 as antenna control

	// Slowing down the ESP32 to 1/4 of its speed saves more energy 
	setCpuFrequencyMhz(80);

	// Initialize the LoRa chip
	if ((cpu0WakeupReason == DEEPSLEEP_RESET) || (cpu1WakeupReason == DEEPSLEEP_RESET))
	{
		// Wake up reason was a DEEPSLEEP_RESET, which means we were woke up by the SX126x
#ifdef LOG_ON
		Serial.println("Starting lora_hardware_re_init");
#endif
		lora_hardware_re_init(hwConfig);
	}
	else
	{
		// Other wake up reasons mean we need to do a complete initialization of the SX126x
#ifdef LOG_ON
		Serial.println("Starting lora_hardware_init");
#endif
		lora_hardware_init(hwConfig);
	}

	// Initialize the Radio callbacks
	RadioEvents.TxDone = OnTxDone;
	RadioEvents.RxDone = OnRxDone;
	RadioEvents.TxTimeout = OnTxTimeout;
	RadioEvents.RxTimeout = OnRxTimeout;
	RadioEvents.RxError = OnRxError;
	RadioEvents.CadDone = OnCadDone;

	if ((cpu0WakeupReason == DEEPSLEEP_RESET) || (cpu1WakeupReason == DEEPSLEEP_RESET))
	{
		// Wake up reason was a DEEPSLEEP_RESET, just re-initialize the callbacks
#ifdef LOG_ON
		Serial.println("Trying to handle SX1262 event after deep sleep wakeup");
#endif
		// Initialize the Radio
		Radio.ReInit(&RadioEvents);

		// Handle LoRa events to find out what the reason for the wake up was and handle the data
		Radio.IrqProcessAfterDeepSleep();
	}
	else
	{
#ifdef LOG_ON
		Serial.println("Power on reset, reinitialize the Radio");
#endif
		// Other wake up reasons mean we need to do a complete initialization of the SX126x
		// Initialize the Radio
		Radio.Init(&RadioEvents);

		// Set Radio channel
		Radio.SetChannel(RF_FREQUENCY);

		// Set Radio TX configuration
		Radio.SetTxConfig(MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
						  LORA_SPREADING_FACTOR, LORA_CODINGRATE,
						  LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
						  true, 0, 0, LORA_IQ_INVERSION_ON, TX_TIMEOUT_VALUE);

		// Set Radio RX configuration
		Radio.SetRxConfig(MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
						  LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
						  LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
						  0, true, 0, 0, LORA_IQ_INVERSION_ON, true);
	}

	// goToSleep will start listening to LoRa data packages and put the ESP32 into deep sleep
	goToSleep();
}

/**
 * ESP32 main task
 */
void loop()
{
}

/**@brief Function to be executed on Radio Tx Done event
 */
void OnTxDone(void)
{
#ifdef LOG_ON
	Serial.println("Transmit finished");
#endif
	// LoRa TX finished, go back to bed
	goToSleep();
}

/**@brief Function to be executed on Radio Rx Done event
 */
void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr)
{
#ifdef LOG_ON
	Serial.println("Receive finished");
#endif
	char debugOutput[1024];
	for (int idx = 0; idx < size; idx++)
	{
		sprintf(&debugOutput[(idx * 2)], "%02X", payload[idx]);
	}
	Serial.printf("Data: %s\n", debugOutput);

	// Now its up to YOU to do something with the data

	// LoRa receive finished, go back to bed
	goToSleep();
}

/**@brief Function to be executed on Radio Tx Timeout event
 */
void OnTxTimeout(void)
{
#ifdef LOG_ON
	Serial.println("Transmit timeout");
#endif
	// LoRa TX failed, go back to bed
	goToSleep();
}

/**@brief Function to be executed on Radio Rx Timeout event
 */
void OnRxTimeout(void)
{
#ifdef LOG_ON
	Serial.println("Receive timeout");
#endif
	// LoRa RX failed, go back to bed
	goToSleep();
}

/**@brief Function to be executed on Radio Rx Error event
 */
void OnRxError(void)
{
#ifdef LOG_ON
	Serial.println("Receive error");
#endif
	// LoRa RX failed, go back to bed
	goToSleep();
}

/**@brief Function to be executed on Radio Rx Error event
 */
void OnCadDone(bool cadResult)
{
	cadRepeat++;
	Radio.Standby();
	if (cadResult)
	{
#ifdef LOG_ON
		Serial.printf("CAD returned channel busy %d times\n", cadRepeat);
#endif
		if (cadRepeat < 6)
		{
			// Retry CAD
			Radio.Standby();
			SX126xSetCadParams(LORA_CAD_08_SYMBOL, LORA_SPREADING_FACTOR + 13, 10, LORA_CAD_ONLY, 0);
			SX126xSetDioIrqParams(IRQ_CAD_DONE | IRQ_CAD_ACTIVITY_DETECTED,
								  IRQ_CAD_DONE | IRQ_CAD_ACTIVITY_DETECTED,
								  IRQ_RADIO_NONE, IRQ_RADIO_NONE);
			Radio.StartCad();
		}
		else
		{
			// LoRa is too busy? Go to bed
			goToSleep();
		}
	}
	else
	{
#ifdef LOG_ON
		Serial.printf("CAD returned channel free after %d times\n", cadRepeat);
#endif
		// If we need to send something, it should be done here
		// after CAD found the channel available
	}
}

/**
 * Print the reset reason.
 * Just for better understanding.
 * Not required for the function of the app
 */
void print_reset_reason(RESET_REASON reason)
{
	switch (reason)
	{
	case 1:
		Serial.println("POWERON_RESET");
		break; /**<1, Vbat power on reset*/
	case 3:
		Serial.println("SW_RESET");
		break; /**<3, Software reset digital core*/
	case 4:
		Serial.println("OWDT_RESET");
		break; /**<4, Legacy watch dog reset digital core*/
	case 5:
		Serial.println("DEEPSLEEP_RESET");
		break; /**<5, Deep Sleep reset digital core*/
	case 6:
		Serial.println("SDIO_RESET");
		break; /**<6, Reset by SLC module, reset digital core*/
	case 7:
		Serial.println("TG0WDT_SYS_RESET");
		break; /**<7, Timer Group0 Watch dog reset digital core*/
	case 8:
		Serial.println("TG1WDT_SYS_RESET");
		break; /**<8, Timer Group1 Watch dog reset digital core*/
	case 9:
		Serial.println("RTCWDT_SYS_RESET");
		break; /**<9, RTC Watch dog Reset digital core*/
	case 10:
		Serial.println("INTRUSION_RESET");
		break; /**<10, Instrusion tested to reset CPU*/
	case 11:
		Serial.println("TGWDT_CPU_RESET");
		break; /**<11, Time Group reset CPU*/
	case 12:
		Serial.println("SW_CPU_RESET");
		break; /**<12, Software reset CPU*/
	case 13:
		Serial.println("RTCWDT_CPU_RESET");
		break; /**<13, RTC Watch dog Reset CPU*/
	case 14:
		Serial.println("EXT_CPU_RESET");
		break; /**<14, for APP CPU, reseted by PRO CPU*/
	case 15:
		Serial.println("RTCWDT_BROWN_OUT_RESET");
		break; /**<15, Reset when the vdd voltage is not stable*/
	case 16:
		Serial.println("RTCWDT_RTC_RESET");
		break; /**<16, RTC Watch dog reset digital core and rtc module*/
	default:
		Serial.println("NO_MEAN");
	}
}