LMiC's TX_COMPLETE event takes 20-30 seconds to fire

A short update of my project.

With the help of @matthijs and @platenspeler, my node is now really power effient. I’m using an arduino pro mini 8mhz 3.3V (with led and regulator removed) + RFM95W + DHT22. To join the network I’m using OTAA. When the arduino is in sleep (with lowpower library) it uses around 0.02mA.

The problem now is that it takes really long to send the data. With this code the device wakes up for 20-30 secondes. It looks like the device is waiting for a TX window. Is it possible to remove this? I think it must be possible to send the data really quick.

My code:

1 Like

No, because then you would violate the duty cycle regulations for the frequencies used.

What you could do, is create messages less often (so you will be certain that the frequencies are “free” again when you do, see Spreadsheet for LoRa airtime calculation for a tool to calculate this), or you could detect the case where your message is waiting for airtime and sleep in the mean time. Doing the latter might be tricky with the current LMIC code, since the next available tx time is not easily exposed. Look at the engineUpdate function in lmic.c to see what happens when the library decides to wait for airtime.

I would suggest to reduce your payload. This is a lot of data:

String message = “T:” + String(t)+“H:” + String(h)+“B:”+ String(b);

Depending on the resolution you need, you can probably reduce this to 2 bytes max!

1 byte for temperature (decide what your expected range will be, for example -20 -> 50 celcius, then normalize this so you use 1 byte (0…255), For the second byte combine the humidity (0…100) and battery level (0…100). Send those two bytes, and reconstruct your temperature, humidity and battery level in your application (you can even do this in the ttn backend, before the data is delivered by mqtt).

This will drastically reduce the time you have to wait between sending packets.

[edit] on a second thought, you actually need three bytes if you want to have temperature, humidity and battery level. Three bytes is 24 bits. If you use 7 bits for battery, 7 bits for humidity, then you have 10 bits left for temperature. 2^10 = resolution of 1024. For battery level you can even reduce to 5 or 6 bits, which gives you even more resolution for the temperature. Decide what resolutions you need for each as a minimum and fit it in the 24 bits (3 bytes)!

1 Like

battery level you can reduce to 1 bit if you use a SOC module (state of charge)
you can set an alert level at 30% or 60% or whatever.
If the battery hits this alert treshold level, the output is one, you can use this as an interrupt and you can read a register

1 Like

I think Diederik does not want to transmit more often*, but just wants to sleep more?

But indeed, it seems that the EV_TXCOMPLETE not only takes receive windows into account, but also duty cycles, and then keeps the device awake. But even then, 20-30 seconds seems far too long for about 21 bytes to send something like T:12.34H:12.34B:12.34 on SF7, where a 1% duty cycle yields something like 7 seconds waiting time after sending?

Or are the float values much longer than 12.34, @DiederikRhee?

* Even though the current sleep of 64 seconds seems very low for temperature readings, and yields a daily air time far above the TTN Fair Access Policy when sending 20+ bytes. So yes, send less often, and send binary, like already suggested.

While, officially, a different frequency could be used for the next transmission much sooner?

It should not take that long to send some data. I have been sending 14 bytes on SF12, that takes just under 2s.

Are you sure the device is not rejoining using OTAA every time it wakes up?

And regarding the interval on which you send data. I was wondering what a usable rate would be. Temperature does not change that much and not that fast. I was thinking of sending 1 message every 15 minutes.

True, but for me TX_COMPLETE is also taking about 12-13 seconds for 1 byte on SF7. (LMiC on ESP8266/Arduino.)


  • 62 ms to actually send data (which indeed I see on the gateway right then), plus
  • 2 seconds for RECEIVE_DELAY2 for the second receive window, plus
  • maybe, a bit over 5 seconds for the 1% duty cycle on SF7 (which LMiC should only take into account when handing a new transmission, but it seems it might be doing that after sending too), plus
  • somehow another 5-6 seconds to make it 12-13 seconds…

I need to peek into the code I guess, though I didn’t mind so far.

It’s not duty cycle related.

LMiC purposely delays the TX_COMPLETE event to prevent the next transmission to collide with a downlink that the node did not receive, but the gateway is still busy sending (and therefor not in receive mode).

It uses a worst-case for that, at SF12 in the RX2 window, that is defined by DNW2_SAFETY_ZONE (in lmic.c) with a default of 3 seconds.

Maybe this is bit conservative, also because TTN is use SF9 in RX2.

We could implement this another way, by not using the TX_COMPLETE event to prevent too early tx, such that the node can go in sleep as soon as possible after the RX2 preamble window.

But still, that would only be 3 seconds then (for EU; 750 ms for US I see now), on top of RECEIVE_DELAY2. So I still need to peek in the code one day, to see where the other seconds are lost…

(And aha, you explained earlier, but I misinterpreted then.)

What I meant was that, if you tell LMIC to transmit a packet shortly after a previous packet (e.g. when the duty cycle limitation from that previous packet is still preventing TX), then that duty cycle limitation will indeed delay TX_COMPLETE (but not from the duty cycle delay after the next packet, but before the next packet). This is not something that I think should change.

What you’d want to do in this case is sleep until the TX time has approached. LMIC does not currently offer a solid API to get at this info. Until this is changed, you could:

  • Detect that TX is delayed by checking LMIC.opmode for the OP_TXRXPEND (or something similar, didn’t check just now) bit. If it is set, TX is happening, if it is unset (after queueing a packet), LMIC is waiting for airtime to become available. In the latter case, you could cancel your packet, sleep for some time and try again, until you eventually see the TXRXPEND bit set. I’m using this approach in a sketch I’m using.
  • In addition to the above, you could pry in LMIC’s list of “osjobs” to see when the next job is pending, and sleep that long. In either case, you’ll have to make sure that the micros() counter is updated through sleep, which does not normally happen on AVR.

Thinking of the latter, the fact that micros() does not update might even be the cause of your problem in the first place: you queue a new packet after the duty cycle delay is over, but because you’ve been sleeping, LMIC doesn’t know that time has passed. Ideally, the micros() counter would be adapted to compensate for the sleep time which requires some changing some global variable internal to the Arduino code, but that might not be so portable. I wonder if Arduino should get a API to do this instead…

I’m planning to change this in the LMIC library, see this comment about what I’m planning to change if you want to do this now (to save these 3 seconds of “safety zone”).

Only now I noticed that since February @matthijs’ LMiC port the event itself is no longer delayed. Nice!