Summary of the problem
I have built a class A end device running the Zephyr RTOS. It joins OTA and sends an unconfirmed uplink every 10 minutes. I have registered it in my application on The Things Stack Community Edition (EU1) as a LoRaWAN V1.1.0 device using the RP002 1.0.1 regional parameters, as this matches the version of the loramac-node library used by Zephyr.
I am unable to send Downlink messages to the device – the Downlink tab in the TTN console gives me the following error: “Downlinks can only be scheduled for end devices with a valid session. Please make sure your end device is properly connected to the network.”
Can anyone give me some tips on how to troubleshoot this, or know what might be causing this? As far as I can tell, my device has successfully joined the network and should have a valid session. I think that the problem is not on the end device, and is related to the Application Session (see below), but have hit a wall in trying to troubleshoot further.
Troubleshooting thus far
Join procedure
The device appears to join OTA successfully. Examining the live data for the device, I see the expected steps for a V1.1 join procedure:
-
Join-request
message from end device withDevNonce
value that increases incrementally from0
.
{
"name": "ns.up.join.receive",
"time": "2023-07-05T02:52:14.097832Z",
"data": {
"@type": "type.googleapis.com/ttn.lorawan.v3.UplinkMessage",
"raw_payload": "AAAAAAAAAAAACvQF0H7Vs3ADAFoZlRc=",
"payload": {
"m_hdr": {},
"mic": "WhmVFw==",
"join_request_payload": {
"join_eui": "0000000000000000",
"dev_eui": "70B3D57ED005F40A",
"dev_nonce": "0003"
}
},
-
Join-accept
reply from Join server with appropriateJoinNonce
value andOptNeg
bit set (indicating V1.1).
{
"name": "ns.down.join.schedule.attempt",
"time": "2023-07-05T02:52:15.900862406Z",
"data": {
"@type": "type.googleapis.com/ttn.lorawan.v3.DownlinkMessage",
"raw_payload": "IPMAQjYO2Y/hW4+IhfEW4bzkuSeyQoKjdoXr2UkWFoOx",
"payload": {
"m_hdr": {
"m_type": "JOIN_ACCEPT"
},
"join_accept_payload": {
"net_id": "000013",
"dev_addr": "260B6B15",
"dl_settings": {
"rx2_dr": 8,
"opt_neg": true
},
"rx_delay": 5,
"cf_list": {
(I have independently decrypted the raw payload to confirm that the JoinNonce
value is sensible)
-
RekeyInd
MAC command inFOpts
payload of first unconfirmed-data-up message from end device.
{
"name": "ns.up.data.receive",
"time": "2023-07-05T02:52:19.625521894Z",
"data": {
"@type": "type.googleapis.com/ttn.lorawan.v3.UplinkMessage",
"raw_payload": "QBVrCyaCAQCyzgIZYFiQqkdTuPg=",
"payload": {
"m_hdr": {
"m_type": "UNCONFIRMED_UP"
},
"mic": "R1O4+A==",
"mac_payload": {
"f_hdr": {
"dev_addr": "260B6B15",
"f_ctrl": {
"adr": true
},
"f_cnt": 1,
"f_opts": "ss4="
},
"f_port": 2,
"frm_payload": "GWBYkKo=",
"full_f_cnt": 1
}
Decrypting the f_opts
field, I get the value 0x0b01
, which is a RekeyInd
MAC command from the end device.
-
RekeyConf
MAC reply from network server.
{
"name": "ns.down.data.schedule.attempt",
"time": "2023-07-05T02:52:20.037002128Z",
"data": {
"@type": "type.googleapis.com/ttn.lorawan.v3.DownlinkMessage",
"raw_payload": "YBVrCyaHAAByps9P0VlgsF+LIg==",
"payload": {
"m_hdr": {
"m_type": "UNCONFIRMED_DOWN"
},
"mic": "sF+LIg==",
"mac_payload": {
"f_hdr": {
"dev_addr": "260B6B15",
"f_ctrl": {
"adr": true
},
"f_opts": "cqbPT9FZYA=="
}
}
},
Decrypting the f_opts
field, I get the value 0x0b01033100ff01
, which has multiple MAC commands. 0b01
completes the key update (RekeyConf
), and 033100ff01
updates the end device power and channel mask (LinkADRReq
).
The successful join procedure means that the Join-accept message and both the FOpts
& FRMPayload
fields of uplinks and downlinks are decrypted by the end device and the server, so they share the same NwkSEncKey
and AppSKey
encryption keys.
Downlinks
The Network Server is successfully downlinking MAC commands to the end device to confirm rekeying (RekeyConf
, 0x0B
) (see Join procedure above), but also to adjust its data rate (LinkADRReq
, 0x03
) and to request device status (DevStatusReq
, 0x06
). This series of successful MAC-level exchanges between the end device and the Network Server implies that the Network Session context is fine.
My conclusion is that the problem lies with the other part of a LoRaWAN session: the Application Session context. This context consists of AppSKey
, FCntUp
, and AFCntDown
. The fact that I can successfully decrypt FRMPayload
means that the end device and the Application Server share the same state for AppSKey
and FCntUp
(these values are used in the encryption of FRMPayload
). I can’t figure out how to examine AFCntDown
.
Is this the source of my problem? Or is there somewhere else I should be looking?