WiFi LoRa 32 V3: Send sensor data via LoRa, then go back to deep sleep


I want to send sensor data via LoRa and then send the board back to deep sleep.

The sending data part works fine by itself, and so does the deep sleep.

However, when I combine both, no data is being sent anymore.

My codes can be found in this repository: https://github_com/JuliaSteiwer/IoT-Water-Quality-Monitoring/tree/main (replace _ with .)

  • The file x_lora_allSensors_5Vsupply.ino contains the code for sending sensor data via LoRa (this works fine on its own).
  • The file deepSleep.ino contains the code for putting the board into deep sleep (this also works fine on its own).
  • The file x_LoRa_deepSleep_sensorValues.ino contains the combined code, which does not work.

Could anyone kindly take a look over my code and point out what I did wrong there? I feel like it should work this way, but it doesn’t.

Thank you very much in advance for any help or suggestions; I appreciate it a lot!

[Please note that in the .ino files, you have to replace the x with the name of this forum’s owner (I didn’t write it out because maybe people see that as ad/vert/!s!ng).]

In Dutch there’s a saying along the lines of “you’ve heard the bell ring but have no clue where the clapper is” - no idea if there’s a German equivalent. It roughly translates to: you have some notion of what should happen, but got none of the details right.

So the most important part is this: in the setup(), the deviceState is set to INIT. In the loop(), there’s a so-called “state machine” - may be useful to look up the idea behind that. It loops over the deviceState, which initially is set to INIT - then to JOIN, then SEND, CYCLE, SLEEP… rinse and repeat once the sleep has finished. That is the usual behaviour if you don’t do a deepsleep(), because the loop() runs over and over.

Now to the combined version: here the deviceState is set to INIT in the setup(), but then in the loop() you immediately set the radio comms to sleep(???) with all the ANALOG stuff, you configure deepsleep timer etc - while the device is still in INIT state!
And then the device executes the INIT part of the code, the switch() ends, the code ends up at the end of the loop, and goes into deepsleep. But the deviceState only switched from INIT to JOIN…!

I suggest you start off with the normal LoRaWAN sketch and find out what is happening behind the screen - add a lot of print statements in the loop etc.
And you will find out that you will only want to put the device into deepsleep once it ends up in the deviceState “…_SLEEP”.

Good luck! And don’t forget that contributions like these technically count as contributions that should be listed in your assignment… don’t need the credits, but be aware of how much help you request.

1 Like

Hi, thank you very much for your reply.

Yes, there were some details I didn’t quite understand because some of the docs I read were confusing me a little. Now that you pointed out what the issue is, I feel like a fool, because it’s so plain obvious that it cannot work when everything is turned off before the switch-case is called. I think the appropriate German idiom for that is “den Wald vor lauter Bäumen nicht sehen” (not seeing the forest because of the trees; when you don’t see the whole issue because you were focusing on just a part of it).

I will add the people who helped and the resources I used to my GitHub repo, that’s the least I can do as a thanks for their help.

Sounds like an applicable situation indeed! Hopefully the hints are enough to get you there, but if you get stuck for longer than a day, feel free to post a follow-up question.
And if all is well in the end, please follow up as well because we love to hear successes instead of the endless stream of questions on these fora :slight_smile:

1 Like

Okay, I have been racking my brain these past hours and tested various things, but I so far no real progress.

Looking at the LoRaWAN example code and taking your hints into account, it is obvious that the sleep mode happens in case DEVICE_STATE_SLEEP: { }, so naturally any code for sleeping should be there. I mean, the function LoRaWAN.sleep(loraWanClass); is already there and contains a call to sleep. That function seemingly only turns off the LoRaWAN, nothing else (I say seemingly because looking up LoRaWAN.sleep() online only returns threads of other people having issues with that function [so far none that I could apply to my issue] but no documentation that explains what this does).

But to reduce the power usage, I want my device to enter an actual “deep sleep”, where the radio is off, the board LED is off, the sensors are not powered, etc. – from Quency-D’s code there is more code that should be added to turn off things. Now here is where things stopped making sense to me.

If I leave everything as it is in my code, the data is transmitted, I get it printed out to the serial monitor and it appears on TTN. Good.

When I remove the LoRaWAN.sleep() function in the sleep state, it displays “forward join-accept message” in TTN but never gets values. The same thing happens when I replace that sleep function with the esp deep sleep function, when I put the deep sleep function before or after the LoRaWAN.sleep() function. Which makes sense, because after that function there is a break statement which takes us back to initialization, and when that break is not executed, nothing else in the switch function is executed.

The sleep state is set in the cycle part with deviceState = DEVICE_STATE_SLEEP; and when I print out “going to sleep” before that line, I can observe a downlink message afterwards, a short pause, and then some booting etc. until the next data is sent (see “output” below).


02:36:10.131 -> enter sleep state. zzz

02:36:15.237 -> received unconfirmed downlink: rssi = -61, snr = 14, datarate = 5

02:36:25.189 -> ESP-ROM:esp32s3-20210327

02:36:25.230 -> Build:Mar 27 2021

02:36:25.230 -> rst:0x5 (DSLEEP),boot:0x9 (SPI_FAST_FLASH_BOOT)

02:36:25.230 -> pro cpu reset by JTAG

02:36:25.230 -> SPIWP:0xee

02:36:25.230 -> mode:DIO, clock div:1

02:36:25.230 -> load:0x3fce3808,len:0x43c

02:36:25.230 -> load:0x403c9700,len:0xbec

02:36:25.230 -> load:0x403cc700,len:0x2a3c

02:36:25.230 -> SHA-256 comparison failed:

02:36:25.230 -> Calculated: dcde8d8a4817d9bf5d5d69a7247667264e4e10ac7493514868b61f5aa6146539

02:36:25.230 -> Expected: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

02:36:25.254 -> Attempting to boot anyway…

02:36:25.254 -> entry 0x403c98d8

02:36:25.685 -> temperature: 22.55 | humidity: 72.50

02:36:25.685 -> confirmed uplink sending …

02:36:25.685 -> sent data

02:36:25.685 -> enter sleep state. zzz

So I know that my sleep resources have to be activated after the cycle state, to be active in the sleep state, and then the wakeup resource has to be defined there as well. Because none of the logic placements of the additional deep sleep resources worked, I then tried everything else that came to my mind, with the following findings:

  • Putting the resources instead of lorawan.sleep -> no data sent to TTN
  • Putting the resources after lorawan.sleep -> see above
  • Putting the resources before lorawan.sleep -> see above
  • Putting the resources in the cycle state -> no data sent to TTN
  • Putting the resources in the send state -> no data sent to TTN
  • Putting the resources in the join state -> doesn’t even communicate with the gateway but prints out devUI etc.
  • Putting the resources in the init state -> see above
  • Sandwiching the switch statement between the wakeup and the sleep resource -> see above

In the Heltec TimerWakeUp.ino file, everything that is supposed to be executed is place between the wakeup and the sleep resource, just in the setup rather than the loop part. However, that would just cause the same issue as the last point in the bullet list above.

TL;DR: I know the deep sleep resources have to be in the sleep cycle, when my device (or parts of it) are supposed to sleep. But somewhere, there is an issue, and my brain does not comprehend where it comes from and how to fix it.

So, I decided to take a look at the LoRaWAN_APP.cpp file: https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/blob/master/esp32/libraries/LoraWan102/src/LoRaWan_APP.cpp

  • If the LoRaWAN join request was successful, the state is changed to “sleep”. (I can already see that in my code, and it makes sense.)
  • In the sleep function, the following two lines of code are called: Radio.IrqProcess( ); Mcu.sleep(classMode,debugLevel);, which - to my understanding - sends an interrupt process for the radio and then puts the MCU to sleep.
  • In line 757 of the code, there is void LoRaWanClass::displayAck(), which at some point calls display.drawString(28, 50, "Into deep sleep in 2S"); – which tells me that deep sleep should be possible.

However, I’m still unsure how to incorporate any deep sleep resources into my code. Thank you in advance for the help.

Ah, we’re really getting somewhere here! Good research.
You might be able to make your life easy: create a bool at the beginning of your program that is set to false; set it to true when a message is sent (after the LoRaWAN.send()), and in the loop, only go to deepsleep once this boolean is set… would that work?

1 Like

Thank you for your reply.

I added bool messageSent = false; before static void prepareTxFrame( uint8_t port ), then under case DEVICE_STATE_SEND: I added messageSent = true; right after LoRaWAN.send();.

For entering the deep sleep dependent on this parameter, I am thinking of using this if statement:

#if(messageSent == true)
    esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);

I added that statement in case DEVICE_STATE_SLEEP: right after LoRaWAN.sleep(loraWanClass); – looking at my TTN live data, the data is going through. I’m just not sure if it’s actually because the device enters deep sleep or if the code is skipping past that statement – if I look at the time stamps on my TTN data, it sends every 15 seconds (duty cycle defined in uint32_t appTxDutyCycle = 15000;) but I do not see the 30 seconds of sleep time defined by #define uS_TO_S_FACTOR 1000000ULL (conversion factor) and #define TIME_TO_SLEEP 30 (sleep time in sec).

Here is what my code looks like now: https://github.com/JuliaSteiwer/IoT-Water-Quality-Monitoring/blob/main/deepSleep_staticValues_v2.ino

Don’t use #if-statements, the # tells that this statement should be checked when your code is actually being compiled - rather than during code execution! You should just use a simple normal if-statement:

if (messageSent == true) {
    esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
1 Like

Thank you for telling me this; while I know that C++ takes normal if statements, I thought the one with # was merely a different writing style for the Arduino IDE, my bad.

I changed things in the code, I just cannot test it atm because my LoRa gateway is at home, I’m currently at a train station like 25km away, and due to work I will stay at my apartment until perhaps even Saturday (yes, a train strike again) … I might be able to check it tomorrow at work because we have LoRa gateways there as well. I’ll test it and then report back here,

1 Like

@bns I tested the updated code today but the problem persists. The device sends data once when I play the code onto the device, then it (seemingly) enters deep sleep, and does not send any data anymore after (it still prints them out to the serial monitor, they just do not appear in the live data of my application).

Looking at the serial monitor though, the device prints out data, goes to sleep for 30 seconds (as defined), and then wakes up to send data again, before going back to sleep again. Below you can see what the serial monitor prints out.

15:54:41.779 -> going to sleep
15:55:11.754 -> ESP-ROM:esp32s3-20210327
15:55:11.754 -> Build:Mar 27 2021
15:55:11.754 -> rst:0x5 (DSLEEP),boot:0x29 (SPI_FAST_FLASH_BOOT)
15:55:11.754 -> pro cpu reset by JTAG
15:55:11.754 -> SPIWP:0xee
15:55:11.754 -> mode:DIO, clock div:1
15:55:11.754 -> load:0x3fce3808,len:0x43c
15:55:11.786 -> load:0x403c9700,len:0xbec
15:55:11.786 -> load:0x403cc700,len:0x2a3c
15:55:11.786 -> SHA-256 comparison failed:
15:55:11.786 -> Calculated: dcde8d8a4817d9bf5d5d69a7247667264e4e10ac7493514868b61f5aa6146539
15:55:11.786 -> Expected: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
15:55:11.786 -> Attempting to boot anyway...
15:55:11.786 -> entry 0x403c98d8
15:55:12.215 -> temperature: 22.55 | humidity: 72.50
15:55:12.215 -> confirmed uplink sending ...
15:55:12.248 -> sent data
15:55:12.248 -> enter sleep state. zzz
15:55:12.248 -> going to sleep

However, in TTN, I cannot see the new data. There, I only get data once (when the code is played to the device or when I hit the reset button), and then never again.

I tried adding a delay before changing to the sleep state in case the time is too short for sending, but that does not work. Going into light sleep instead of deep sleep does not fix the issue either. What I did notice though is that, as of right now, I only set the boolean for the sent message to true just like that, instead of checking if LoRaWAN.send() was actually successful or not. Also, I feel that something in the LoRaWAN_APP.h library bites itself with the esp_ commands.

Below is what I found regarding ESP in the LoRaWAN library:
For void LoRaWanClass::generateDeveuiByChipID() (line 483), there is

#if defined(ESP_PLATFORM)
	uint64_t id = getID();

In line 678, there is #ifndef ESP_PLATFORM, which apparently skips join when the saved network info is okay. The header file for the library also calls the libraries ESP32_Mcu.h and ESP32_LoRaWan_102.h. There is a cross-call to the esp_sleep.h library, but there I do not find anything that could cause issues with LoRaWAN.

Do you have any idea why this happens and how I could fix this issue?

Thank you in advance for your reply!

Two options:

  1. we try to get this working, which means we need to get access to a gateway console on TTN that shows data passing through. It may now be the case that your device is actually uplinking but e.g. with resetting frame counter. We’ll also need to know what version you set your device to on the TTN console.
  2. we switch to RadioLib which I know by heart (since I built its LoRaWAN stack lol).
1 Like

Hi Steven,

on my GitHub repo, user FritzOS opened an issue where he provided me with his code for communicating with TTN and sending the device to deep sleep (you can find the issue here). I tested it with some static data, and that worked just fine.

On the weekend, I will test it with my sensors, just to see if that behaves as expected or if there might be some unexpected errors. I will post an update here when that works.

Thank you so much for the help you have been offering; it has pushed both me and my knowledge of the subject further and I am truly thankful!

1 Like

@jsteiwer Thank you so much for that link, I really appreciate it. This solved so many of the issues I was having. Between this and what @bns put together I’ve finally got payloads coming through.

Steps I took:
Updating to 0.0.9
Manually configure the pinmap
Configure the channelmask

The link you posted Works on Chirpstack LNS as well once I made some adjustments.

You’re both a huge help! Thanks again! I don’t think @nmcc can bully me here so I’m safe to discuss :wink:

1 Like

No one was bullying you - you just weren’t able to follow the TTN forum guidelines - but I can flag your post for being an inappropriate personal attack because here I’m a user which has nothing to do with being a moderator on TTN which is a distinct and separate role. Without this, the technical forums would disintegrate rather quickly.

1 Like

Yeah, the wink was to indicate a light hearted joke but you do you…

Sure, be a winker, but I burn through hours every week just moderating - @bns teaches, I mentor - that’s where the fun is, getting people to success - being a forum cop is frankly draining.

@jsteiwer, sorry for this detour. I’d highly recommend you take RadioLib 6.3.0, do nothing other than get a TTN connection going as @colinmcmahon has, party :partying_face:, and then add in the sensor code, one sensor at a time. You may also find that https://www.thethingsnetwork.org/docs/lorawan/ will fill in any gaps in the LoRaWAN side of things - takes a couple hours to read - don’t watch Johan’s video until you’ve read the material and even then, don’t watch it all in one go.

Moderators: happy to drop the flag, let’s move on to making something great.


@nmcc This is probably a good example of everyone being at the redline all at the same time. If you’re smashing your face against the screen moderating, and I’m smashing my face against the screen trying to debug code, nobody is at their best. I was just sad because I finally found the handful of people in the world working with the the same hardware/software as me and all I wanted to do was help :confused: I’ve spent more time and money than I care to admit trying to prove a concept I feel is going to change the world so I’m absolutely not carrying myself as well as I should. @jsteiwer I’m sorry as well for the drama :sweat_smile: I’m sure whoever comes across this soap opera will have a wholesome giggle.

also this ↓ ↓

Hi, thank you very much for the suggestions.

I actually already have a working code that sends my sensor data to TTN, my main issue was just that I couldn’t get the deep sleep to work properly. Now that @/FritzOS from GitHub has provided me with a working code that continues sending after waking up from deep sleep, I should be able to make everything work just by adding my sensor code in. With static values, it already works, and the key differences between the static values and the sensor data are some libraries, some parameter definitions, and then function calls – I think it is manageable.

Once I got it working, I will report back here, so everyone knows that/if it also works with a sensor suite. I will also upload that code to my GitHub again so all users can profit of that.


Okay kings, I have tested the code with my sensors, and it works fine! I will test it with a 2200mAh battery later, to see how long it will now last me. Anyways, here is the code:

The only thing that is bothering me right now is the electrical conductivity (EC) sensor. DFRobot (the manufacturer) provides a library for calculating the EC value based on voltage and temperature. Currently, the sensor only ever shows 0.00 - on an Arduino UNO, it shows the right values. I have printed out the result from analogRead and the calculated voltage, and for both I get reasonable values, so I assume there is an issue with the calculation in the EC library.

According to their GitHub repo (find it here), they have not yet tested compatibility for the Heltec WiFi LoRa V3 board, or any board from Heltec for that matter. I checked the .cpp file, and there’s this line executed during compilation that says #if ARDUINO >= 100 (line 15 in the code). However, I could not yet figure out what exactly in the code clashes with my board. If any of you has an idea how this issue could be fixed, I’d appreciate it a lot if you could share these ideas.

Okay, looking a little deeper into the code:

float DFRobot_EC::readEC(float voltage, float temperature)
    float value = 0,valueTemp = 0;
    this->_rawEC = 1000*voltage/RES2/ECREF;
    valueTemp = this->_rawEC * this->_kvalue;
    //automatic shift process
    //First Range:(0,2); Second Range:(2,20)
    if(valueTemp > 2.5){
        this->_kvalue = this->_kvalueHigh;
    }else if(valueTemp < 2.0){
        this->_kvalue = this->_kvalueLow;

    value = this->_rawEC * this->_kvalue;             //calculate the EC value after automatic shift
    value = value / (1.0+0.0185*(temperature-25.0));  //temperature compensation
    this->_ecvalue = value;                           //store the EC value for Serial CMD calibration
    return value;

Above is the code that is called with the function .readEC() (defined in the .cpp file). We got #define RES2 820.0 and #define ECREF 200.0 to set _rawEC. I also found this->_kvalue = 1.0;. Lets see if I can do something with that.

Thank you in advance for your replies!

Update: It’s working properly now! :blush:
Update 2: Now with the battery it’s not working anymore :frowning: