Class C RX2 downlink timed in RX1 window


Hi, we are facing an issue when sending downlink packets within 2 seconds of an uplink message. The system is trying to downlink a packet on the RX2 frequency while the device is in its RX1 window. We are not sure if the issue stems from the LNS or Basicstation, but I will do my best to outline all logs and behavior to help steer this.


  • MTC Conduit (fw version 5.3.0), running basicstation 2.0.5
  • Chirpstack NS 3.12.1 3.12.2
  • Chirpstack AS 3.14
  • Chirpstack GB 3.10
  • End device: mbed-os-lorawan node as class C
  • No messages use ‘confirmed’


Generate a downlink packet with the LNS less than 2 seconds after an uplink has occurred. What we find is that the packet is sent with RX2 parameters, on the RX2 frequency, while the device is in its RX1 window. I’ll use the lorawan spec picture here to illustrate:

Supporting Logs

The timestamps were sync’d down to the millisecond, as seen by the RX event in the station logs and the Transmission completed in the device logs.

Basicstation Log

As you can see, station gets a downlink packet (line 4) about 1.5 seconds after an uplink occurs. It ends up scheduling and sending the packet with RX2

2021-03-23 14:44:55.329 [any:XDEB] RX mod=LORA f=902500000 bw=125 sz=43 dr=2 40B0F90001003600B6315AC9D1ED7962F702EF9560FD7C32A1259C8601EC01D0FC9EC0EDB29BD3E8102F21
2021-03-23 14:44:55.329 [S2E:VERB] RX 902.5MHz DR3 SF7/BW125 snr=10.2 rssi=-29 xtime=0xA00001D1E0453 - updf mhdr=40 DevAddr=0100F9B0 FCtrl=00 FCnt=54 FOpts=[] B6315AC9..9BD3 mic=556732648 (43 bytes)
2021-03-23 14:44:55.330 [AIO:XDEB] [4|WS] > {"msgtype":"updf","MHdr":64,"DevAddr":16841136,"FCtrl":0,"FCnt":54,"FOpts":"","FPort":182,"FRMPayload":"315AC9D1ED7962F702EF9560FD7C32A1259C8601EC01D0FC9EC0EDB29BD3","MIC":556732648,"RefTime":0.000000,"DR":3,"Freq":902500000,"upinfo":{"rctx":0,"xtime":2814750255613011,"gpstime":0,"fts":-1,"rssi":-29,"snr":10.25,"rxtime":1616510695.330084}}
2021-03-23 14:44:56.936 [AIO:XDEB] [4|WS] < {"msgtype":"dnmsg","DevEui":"01-01-01-01-01-01-01-01","dC":2,"diid":7771,"pdu":"60b0f90001002800b48d8c0d191c6d","priority":1,"RX2DR":10,"RX2Freq":923300000,"xtime":2814750255613011,"rctx":0}
2021-03-23 14:44:56.936 [S2E:WARN] RxDelay mapped to 1 as it was not present!
2021-03-23 14:44:56.936 [S2E:VERB] 101:101:101:101 diid=7771 [ant#0] - starting TX in 49ms769us
2021-03-23 14:44:57.008 [any:XDEB] TX STATUS: 0x00000004
2021-03-23 14:44:57.008 [AIO:XDEB] [4|WS] > {"msgtype":"dntxed","seqno":7771,"diid":7771,"DR":10,"Freq":923300000,"DevEui":"01-01-01-01-01-01-01-01","rctx":0,"xtime":2814750257280904,"txtime":676.119413,"gpstime":0}
2021-03-23 14:44:57.008 [S2E:INFO] TX 101:101:101:101 diid=7771 [ant#0] - dntxed: 923.3MHz 30.0dBm ant#0(0) DR10 SF10/BW500 frame=60B0F90001002800B48D8C0D191C6D
2021-03-23 14:44:57.058 [S2E:DEBU] Tx done diid=7771

Device Log

Shows the timing of the Class C device of when it opens its windows. Looks normal for recv_delay of 1s

[03-23-21 14:44:55.329] [DBG ][LSTK]: Transmission completed
[03-23-21 14:44:55.329] [DBG ][LMAC]: RX2 slot open, Freq = 923300000
[03-23-21 14:44:56.294] [DBG ][LMAC]: RX1 slot open, Freq = 923900000
[03-23-21 14:44:57.317] [DBG ][LMAC]: RX2 slot open, Freq = 923300000


  1. Who should decide which window to target based on the dnmsg seen here?
  2. Looking at the downlink message protocol here, it has two types:
    " A Class C downlink frame which answers an uplink and is aimed at RX1"
    " A Class C downlink frame not answering an uplink"
    I am not sure what that means really, but it doesn’t matter if it is “answering” or not. If a completely unsolicited message happens to occur during this time it will exhibit the same behavior, whether its an “answer” or not. I do notice that in our example the LNS does NOT send RX1DR/RX1Freq in the dnmsg, perhaps it should?
  3. It further goes on to say “The transmission will be delayed until the Station finds a convenient time slot”, indicating the Station should be aware of what RX window the device is in, and should have delayed the transmission in the above case?
1 Like

I’ll look into this in the next days :slight_smile:

There is a mechanism in place which puts a lock when a Class-C downlink is sent, to make sure that multiple Class-C downlinks (to the same device), have some interval in between them.

I think what needs to be done is to add a lock as well when a Class-A uplink is received from a Class-C capable device. With such lock, the Class-C scheduler would wait sending any Class-C downlinks to the device until the lock has expired.

One situation which we can’t avoid I’m afraid is when the NS sends a Class-C downlink to the gateway more or less at the same time as when the device sends a Class-A uplink. The gateway has no knowledge about devices and their capabilities. In such a case there is still a possibility that the Class-C downlink ends up in the RX1 receive window.

What do you think?

I agree for the latter case, we can never avoid that one.

Something interesting to note, we switched from BasicStation back to PacketForwarder, and the issue goes away. I wonder if Packet Forwarder is playing a little better with scheduling the downlink on the radio? I’d need to get logs to determine that.

I am just not sure who (concentrator or LNS) is responsible for that scheduling. I thought LNS should be, and I think the last uplink time lock sounds like a good design fix if so

1 Like

@brocaar. What if the LNS uses the below dnmsg instead? There are 2 message types documented here, and seems like it might be an easier fix instead of implementing a queue?

A Class C downlink frame which answers an uplink and is aimed at RX1, looks almost identical to Class A dnmsg messages. The only difference is the field dC, which declares a Class C interaction. If the transmission cannot make the RX1 opportunity, the Station will switch over to RX2 parameters and choose a convenient time to send the frame. There is a maximum time Station will postpone transmission before the frame is dropped.

  "msgtype"  : "dnmsg",
  "DevEui"   : "EUI",
  "dC"       : 2,         // Class C
  "diid"     : INT64,
  "pdu"      : "HEX",
  "RxDelay"  : INT(0..15),
  "RX1DR"    : INT(0..15),
  "RX1Freq"  : INT,
  "RX2DR"    : INT(0..15),
  "RX2Freq"  : INT,
  "priority" : INT(0..255),
  "xtime"    : INT64,
  "rctx"     : INT64

The LNS is responsible for scheduling the downlink and defining the correct parameters (e.g. timestamp, frequency, data-rate etc…), the gateway is responsible for sending the downlink based on the provided parameters by the LNS. The gateway has no knowledge about the state of each device.

In short, these are the different scheduling types:

  • Based on internal concentrator counter (for Class-A)
  • Based on GPS epoch timestamp (for Class-B)
  • Immediately / as soon as possible (for Class-C)

This is then correctly mapped by the ChirpStack Gateway Bridge for each packet-forwarder implementation (Semtech UDP, BasicStation, ChirpStack Concentratord).

In the latest test-versions if the gateway would for example nACK a Class-A downlink because it is unable to schedule it (e.g. too late), then ChirpStack would keep the payload in the queue and it would be picked up by the Class-C scheduler for transmission.