This document describes a simple support for downlink transmission requests for the Raspberry-based single-channel gateway only. Iif you are using the multi-channel version, downlinks will be taken care by the legacy lora_pkt_fwd
program (see dedicated README.
For downlink support, there are no changes in the way the gateway (lora_gateway
program) is launched or interact with the post-processing stage (post_processing_gw.py
). The single-channel downlink transmission mechanism works as follows:
- create a
downlink
folder in yourlora_gateway
folder, ALL downlink related files will be stored in thisdownlink
folder - the
lora_gateway
program will check for adownlink.txt
file after each LoRa packet reception. This behavior can be disable with--ndl
option lora_gateway
checks 3 times for `downlink.txt after a packet reception at t_r (to work with Network Server sending PULL_RESP in just-in-time mode)- first, after a delay of
DELAY_DNWFILE
(typically set to 700ms) to target RX1. There is no reception possible - second, after t_r+DELAY_DNWFILE+DELAY_RX2 (additional delay of 1000ms, no reception possible) to target RX2
- last, after t_r+DELAY_DNWFILE+DELAY_RX2+RCV_TIMEOUT_JACC2 (radio in reception mode for 3500ms) to target either RX1 or RX2 for join-accept
- if a new packet is received during these 3500ms then it has priority
- in which case
lora_gateway
then starts a new cycle of downlink checking attempts for this new message
- first, after a delay of
downlink/downlink.txt
should contain ONLY one line in JSON format:- non-LoRaWAN downlink: e.g.
{"status":"send_request","dst":3,"data":"hello"}
- LoRaWAN downlink: e.g.
{"txpk":{"imme":false,"rfch":0,"powe":14,"ant":0,"brd":0,"tmst":1580280908,"freq":868.1,"modu":"LORA","datr":"SF12BW125","codr":"4/5","ipol":true,"size":19,"data":"YCEXASYHKAAFANKthAgBt6sC6g=="}}
- non-LoRaWAN downlink: e.g.
- after reading
downlink/downlink.txt
, the file will be deleted bylora_gateway
downlink.txt
file can be renamed asdownlink-backup-2020-01-20T13:42:23.txt
(with the current date indicated) iflora_gateway
is compiled with#define KEEP_DOWNLINK_BACKUP_FILE
- there is no reliability mechanism implemented
- there are some differences between the non-LoRaWAN downlink and the LoRaWAN downlink that are described in the next paragraphs
For LoRaWAN downlink, downlink messages come from the Network Server, following the LoRaWAN specification and the initial protocol using UDP to communicate with the Network Server. Devices can be programmed using the LMIC LoRaWAN stack which has been ported to Arduino. As the gateway is single-channel, the device must only use one uplink frequency at a fixed SF value that need to match those of the single-channel gateway. The Arduino_LoRa_LMIC_ABP_BASIC example shows how to program an Arduino with a simple LoRaWAN device. A more elaborated example is Arduino_LoRa_LMIC_ABP_temp with low-power operation mode support. However you need a slightly modified version of LMIC as described here. Again, as the gateway is single-channel, the support of join operations for OTAA mode requires some modifications in the LMIC code to fix the join frequency and datarate to the one used by uplink packets (and matching those of the single-channel gateway). More details will be provided later on. The LoRaWAN downlink mechanism on the single-channel gateway works as follows:
- a LoRaWAN Network Server needs to be defined (e.g. TTN or ChirpStack for instance. For ChirpStack, a local instance is possible)
- uplink messages use a LoRaWAN cloud script (e.g.
CloudTTN.py
orCloudChirpStack.py
for instance) - the gateway and the devices need to be registered on the LoRaWAN Network Server, see the README-TTN.md for instance
["gateway_conf"]["downlink_lorawan"]=true
enables periodic PULL_DATA on Network Server to get downlink messages["gateway_conf"]["downlink_network_server"]="127.0.0.1"
indicates a LoRaWAN Network Server, here, it is the local ChirpStackstart_gw.py
will run a python script in background (scripts/lorawan_stats/downlink_lorawan.py
) to perform periodicPULL_DATA
to Network Server and getPULL_RESP
with downlink message. Downlink message (json) will then be written directly todownlink/downlink.txt
file forlora_gateway
program{"txpk":{"imme": false, "rfch": 0, "powe": 14, "ant": 0, "brd": 0, "tmst": 1580596494, "freq": 868.1, "modu": "LORA", "datr": "SF12BW125", "codr": "4/5", "ipol": true, "size": 25, "data": "YCQfBCYCtAAIAQG5APUOXbA2QvlB76KdaQ=="}}
lora_gateway
will use thetmst
field in the downlink message to decide which receive window to use: normally a difference of 1s betweentmst
of the downlink message and the time the uplink packet was received means received window 1 (RX1, using same parameter as uplink) while a value of 2s means receive window 2 (RX2, using 869.5235MHz and SF12)- for join-request procedure, the RX1 window is 5s and the RX2 window is 6s
lora_gateway
will include the received packet's timestamptmst
in the time information string:^t2020-01-21T17:33:27.740034*4155747970
. The value after the '*' is the local timestamp in microsecond that will be passed topost_processing_gw.py
and then to cloud scripts when uploading the LoRaWAN packet to the Network Server- when issuing a downlink message, the Network Server will normally indicate (in case of class A devices) the time at which the downlink packet should be sent. The uplink
tmst
is usually increased either by the value of RX1 window delay, i.e. 1s or 5s, or by the value of RX2 window delay, i.e. 2s or 6s.
Configure the single-channel gateway in LoRaWAN mode:
- set
["radio_conf"]["mode"]
to 11 ingateway_conf.json
to configure for native (encrypted) LoRaWAN reception on single channel. The single-channel gateway will be configured with BW=125MHz, CR=4/5, SF=12 by default. The frequency is set by default to 868.1MHz for BAND868, 923.2MHz for BAND900 and 433.175 for BAND433 - set
["gateway_conf"]["raw"]
to true ingateway_conf.json
- set
["gateway_conf"]["aes_lorawan"]
to false as encrypted data will be pushed to TTN or ChirpStack - set
["gateway_conf"]["downlink_lorawan"]
to true to enable periodic PULL_DATA and get PULL_RESP (downlink messages) from Network Server - indicate in
["gateway_conf"]["downlink_network_server"]
the address of the Network Server for downlink messages - enable
python CloudTTN.py
orpython CloudChirpStack.py
in the LoRaWAN encrypted cloud section ofclouds.json
. Enable one by setting"enabled"
totrue
"lorawan_encrypted_clouds" : [
{
"name":"ChirpStack network server",
"script":"python CloudChirpStack.py",
"type":"lorawan",
"enabled":false
},
...
]
It is actually recommended to use the web admin interface and use the easy Configure for LoRaWAN
functionality that automatize the whole process to set the gateway in LoRaWAN mode.
Start the gateway
> sudo python start_gw.py
Output at gateway when starting, 868.1MHZ SF12BW125, CloudChirpStack.py
is used to transmit all uplink messages to ChirpStack Network Server
raw output from low-level gateway. post_processing_gw will handle packet format
post_processing_gw.py found an alert_conf section
Parsing cloud declarations
[u'python CloudThingSpeak.py']
Parsed all cloud declarations
post_processing_gw.py got cloud list:
[u'python CloudThingSpeak.py']
Parsing cloud declarations
Parsed all cloud declarations
post_processing_gw.py got encrypted cloud list:
[]
Parsing cloud declarations
[u'python CloudChirpStack.py']
Parsed all cloud declarations
post_processing_gw.py got LoRaWAN encrypted cloud list:
[u'python CloudChirpStack.py']
Starting thread to perform periodic gw status/tasks
2020-01-21 09:37:51.309005
post status: gw ON
post status: executing periodic tasks
post status: start running
cs_stats: gw id B827EBFFFFCE23C2
cs_stats: Opening UDP socket to 127.0.0.1 (127.0.0.1) port 1700...
cs_stats: Try to send UDP packet: ???'????#?{"stat": {"rxok": 0, "txnb": 0, "ackr": 0, "dwnb": 0, "alti": 0, "time": "2020-01-21 09:37:51 GMT", "rxnb": 0, "rxfw": 0, "lati": 10.0, "long": 20.0}}
post status: exiting
Starting thread to perform fast statistics tasks
Current working directory: /home/pi/lora_gateway
SX1276 detected, starting.
SX1276 LF/HF calibration
...
**********Power ON: state 0
LoRa CR 5: state 0
LoRa SF 12: state 0
LoRa BW 125: state 0
Configuring for LoRaWAN
Set frequency to 868.1MHz: state 0
Use PA_BOOST amplifier line
Set LoRa power dBm to 14
Power: state 0
Get Preamble Length: state 0
Preamble Length: 8
LoRa addr 1: state 0
Raw format, not assuming any header in reception
SX1272/76 configured as LR-BS. Waiting RF input for transparent RF-serial bridge
Default sync word: 0x12
Set sync word to 0x34
LoRa sync word: state 0
Low-level gateway has downlink support
2020-01-21T17:33:08.861087
Low-level gw status ON
...
Use Arduino_LoRa_LMIC_ABP_BASIC as LoRAWAN device, but only enabling 868.1MHz frequency. The device is sending Hello from LMIC
. Then go on your Network Server console to schedule a downlink. Here we use local ChirpStack to schedule helloworld
, i.e. aGVsbG93b3JsZA==
in base64 format. Be sure to schedule the downlink before starting the device which will send almost immediately.
Gateway output, continued, when receiving the packet from the LoRaWAN device
...
--- rxlora. dst=0 type=0x00 src=0 seq=0 len=28 SNR=7 RSSIpkt=-19 BW=125 CR=4/5 SF=12
2020-01-21T17:33:27.741324
rcv ctrl pkt info (^p): 0,0,0,0,28,7,-19
splitted in: [0, 0, 0, 0, 28, 7, -19]
rawFormat(len=28 SNR=7 RSSI=-19)
rcv ctrl radio info (^r): 125,5,12,868100
splitted in: [125, 5, 12, 868100]
(BW=125 CR=5 SF=12)
rcv timestamp (^t): 2020-01-21T17:33:27.740034*4155747970
adding time zone info
new rcv timestamp (^t): 2020-01-21T17:33:27.740034+01:00
got first framing byte
--> got LoRa data prefix
raw format from LoRa gateway
LoRaWAN?
update ctrl pkt info (^p): 256,64,0x26041F24,0,15,7,-19
+++ rxlora[868100]. lorawan type=0x40 src=0x26041F24 seq=0 len=15 SNR=7 RSSIpkt=-19 BW=125 CR=4/5 SF=12
--> number of LoRaWAN enabled clouds is 1
--> LoRaWAN encrypted cloud[0]
uploading with python CloudChirpStack.py
python CloudChirpStack.py "QCQfBCaAAAABMHpb8pE1UwwjMG/l7IoiMC8T+A==" "256,64,637804324,0,15,7,-19" "125,5,12,868100" "2020-01-21T17:33:27.740034+01:00*4155747970" "0000B827EBCE23C2"
CloudChirpStack: gw id: B827EBFFFFCE23C2
CloudChirpStack: Opening UDP socket to 127.0.0.1 (127.0.0.1) port 1700...
--> LoRaWAN encrypted cloud end
--> DATA encrypted: local AES LoRaWAN not activated
-----------------------------------------------------
Check for downlink requests 2020-01-21T17:33:28.439246
Read downlink: 204
Downlink entry: {"txpk":{"imme":false,"rfch":0,"powe":14,"ant":0,"brd":0,"tmst":4156747970,"freq":868.1,"modu":"LORA","datr":"SF12BW125","codr":"4/5","ipol":true,"size":25,"data":"YCQfBCYC9QAIAQF+eOi0BvnZlM6hkIt/wA=="}}
Process downlink request
LoRaWAN downlink request
Target RX1
Packet sent, state 0
-----------------------------------------------------
...
At LMIC device in ABP, with debug output activated to give more details
Starting
RXMODE_RSSI
354: engineUpdate, opmode=0x808
414: Uplink data pending
460: Considering band 0, which is available at 345
1170: Considering band 3, which is available at 0
1990: No channel found in band 3
2518: Considering band 0, which is available at 345
3372: No channel found in band 0
3899: Considering band 1, which is available at 345
4752: Airtime available at 345 (channel duty limit)
5588: Ready for uplink
6770: Updating info for TX at 413, airtime will be 102919. Setting available time for band 1 to 207159296
7762: TXMODE, freq=868100000, len=28, SF=12, BW=125, CR=4/5, IH=0
Packet queued
FREQ=868100000
110088: irq: dio: 0x0 flags: 0x8
110170: Scheduled job 0x254, cb 0x184a ASAP
110306: Running job 0x254, cb 0x184a, deadline 0
111113: Scheduled job 0x254, cb 0x185d at 163245
163249: Running job 0x254, cb 0x185d, deadline 163245
163412: RXMODE_SINGLE, freq=868100000, SF=12, BW=125, CR=4/5, IH=0
275799: irq: dio: 0x0 flags: 0x40
275905: Scheduled job 0x254, cb 0x1865 ASAP
276033: Running job 0x254, cb 0x1865, deadline 0
277621: Received downlink, window=RX1, port=1, ack=0
277720[4443]: EV_TXCOMPLETE (includes waiting for RX windows)
Received
10
bytes of payload
68656C6C6F776F726C64279591: Scheduled job 0x208, cb 0x1b68 at 19029586
280416: engineUpdate, opmode=0x800
...
You can see that the 10 bytes of downlink data have been received in the RX1 window. It is also possible to target RX2 window as shown in the following gateway output
...
--- rxlora. dst=0 type=0x00 src=0 seq=0 len=28 SNR=-6 RSSIpkt=-29 BW=125 CR=4/5 SF=12
2020-01-21T17:36:07.881986
rcv ctrl pkt info (^p): 0,0,0,0,28,-6,-29
splitted in: [0, 0, 0, 0, 28, -6, -29]
rawFormat(len=28 SNR=-6 RSSI=-29)
rcv ctrl radio info (^r): 125,5,12,868100
splitted in: [125, 5, 12, 868100]
(BW=125 CR=5 SF=12)
rcv timestamp (^t): 2020-01-21T17:36:07.880541*20921181
adding time zone info
new rcv timestamp (^t): 2020-01-21T17:36:07.880541+01:00
got first framing byte
--> got LoRa data prefix
raw format from LoRa gateway
LoRaWAN?
update ctrl pkt info (^p): 256,64,0x26041F24,0,15,-6,-29
+++ rxlora[868100]. lorawan type=0x40 src=0x26041F24 seq=0 len=15 SNR=-6 RSSIpkt=-29 BW=125 CR=4/5 SF=12
--> number of LoRaWAN enabled clouds is 1
--> LoRaWAN encrypted cloud[0]
uploading with python CloudChirpStack.py
python CloudChirpStack.py "QCQfBCaAAAABMHpb8pE1UwwjMG/l7IoiMC8T+A==" "256,64,637804324,0,15,-6,-29" "125,5,12,868100" "2020-01-21T17:36:07.880541+01:00*20921181" "0000B827EBCE23C2"
CloudChirpStack: gw id: B827EBFFFFCE23C2
CloudChirpStack: Opening UDP socket to 127.0.0.1 (127.0.0.1) port 1700...
--> LoRaWAN encrypted cloud end
--> DATA encrypted: local AES LoRaWAN not activated
-----------------------------------------------------
Check for downlink requests 2020-01-21T17:36:08.580379
Read downlink: 202
Downlink entry: {"txpk":{"imme":false,"rfch":0,"powe":14,"ant":0,"brd":0,"tmst":22921181,"freq":868.1,"modu":"LORA","datr":"SF12BW125","codr":"4/5","ipol":true,"size":25,"data":"YCQfBCYC9gAIAQGKNMDN8rwMbcvyYfH2bQ=="}}
Process downlink request
LoRaWAN downlink request
Target RX2
Packet sent, state 0
Set back frequency: state 0
Set back SF: state 0
-----------------------------------------------------
...
At the LMIC device, we can see that the downlink data is received in RX2 window.
Starting
RXMODE_RSSI
439: engineUpdate, opmode=0x808
498: Uplink data pending
545: Considering band 0, which is available at 430
1253: Considering band 3, which is available at 0
2073: No channel found in band 3
2601: Considering band 0, which is available at 430
3457: No channel found in band 0
3983: Considering band 1, which is available at 430
4830: Airtime available at 430 (channel duty limit)
5672: Ready for uplink
6853: Updating info for TX at 497, airtime will be 102919. Setting available time for band 1 to 212664320
7846: TXMODE, freq=868100000, len=28, SF=12, BW=125, CR=4/5, IH=0
Packet queued
FREQ=868100000
110046: irq: dio: 0x0 flags: 0x8
110129: Scheduled job 0x254, cb 0x184a ASAP
110264: Running job 0x254, cb 0x184a, deadline 0
111071: Scheduled job 0x254, cb 0x185d at 163203
163208: Running job 0x254, cb 0x185d, deadline 163203
163370: RXMODE_SINGLE, freq=868100000, SF=12, BW=125, CR=4/5, IH=0
197542: irq: dio: 0x1 flags: 0x80
197626: Scheduled job 0x254, cb 0x1865 ASAP
197776: Running job 0x254, cb 0x1865, deadline 0
198583: Scheduled job 0x254, cb 0x187c at 213415
213419: Running job 0x254, cb 0x187c, deadline 213415
213582: RXMODE_SINGLE, freq=869525000, SF=12, BW=125, CR=4/5, IH=0
337719: irq: dio: 0x0 flags: 0x40
337825: Scheduled job 0x254, cb 0x17f0 ASAP
337953: Running job 0x254, cb 0x17f0, deadline 0
339541: Received downlink, window=RX2, port=1, ack=0
339640[5434]: EV_TXCOMPLETE (includes waiting for RX windows)
Received
10
bytes of payload
68656C6C6F776F726C64341511: Scheduled job 0x208, cb 0x1b68 at 19091506
342336: engineUpdate, opmode=0x800
...
If you look at the gateway output, you can see that the received packet's timestamps at the gateway is 20921181 (rcv timestamp (^t): 2020-01-21T17:36:07.880541*20921181
). The downlink message from the Network Server indicates the time at which the downlink data should be sent back to the device: {"txpk":{"imme":false,"rfch":0,"powe":14,"ant":0,"brd":0,"tmst":22921181,...
. You can see that the timestamp is 22921181 which indicates a time difference of 2s, thus the usage of RX2 window. For the device, according to LoRaWAN specification, if data is received in RX1, then RX2 window is not open. Here, as the gateway is using RX2, the device receives nothing in RX1 and will then open RX2 to try to receive a downlink data.
The downlink support for the single-channel gateway also allows for OTAA mode. However, OTAA with a single-channel gateway needs to fix the join frequency and datarate to the one used by uplink packets (and matching those of the single-channel gateway). Therefore you need to use our slightly modified LMIC stack (the one that includes the low-power mode) and uncomment in lmic/config.h
the #define LMIC_SCG
statement. This will avoid LMIC to use SF7 for join-request and will use the same datarate than for uplinks (the one set with LMIC_setDrTxpow(DR_SF12, 14);
for instance).
Here we show an LMIC device in OTAA mode, with debug output activated to give more details. You can see that the assigned device address is 0x005C8ADC.
Starting
RXMODE_RSSI
410: engineUpdate, opmode=0x8
482: Scheduled job 0x27e, cb 0x155b ASAP
Packet queued
880: Running job 0x27e, cb 0x155b, deadline 0
1626[26ms]: EV_JOINING
2015: engineUpdate, opmode=0xc
2521: Uplink join pending
2942: Airtime available at 235280 (previously determined)
3885: Uplink delayed until 235280
4442: Scheduled job 0x27e, cb 0x434 at 235155
235159: Running job 0x27e, cb 0x434, deadline 235155
235261: engineUpdate, opmode=0xc
235515: Uplink join pending
235969: Airtime available at 235280 (previously determined)
236944: Ready for uplink
237892: Updating info for TX at 235514, airtime will be 92678. Setting available time for band 0 to 3211395072
239669: TXMODE, freq=868100000, len=23, SF=12, BW=125, CR=4/5, IH=0
331597: irq: dio: 0x0 flags: 0x8
331680: Scheduled job 0x27e, cb 0x1823 ASAP
331815: Running job 0x27e, cb 0x1823, deadline 0
332898: Scheduled job 0x27e, cb 0x17f8 at 616322
616327: Running job 0x27e, cb 0x17f8, deadline 616322
616490: RXMODE_SINGLE, freq=868100000, SF=12, BW=125, CR=4/5, IH=0
726986: irq: dio: 0x0 flags: 0x40
727091: Scheduled job 0x27e, cb 0x1a87 ASAP
727221: Running job 0x27e, cb 0x1a87, deadline 0
728818[11661ms]: EV_JOINED
netid: 0
devaddr: 5C8ADC
artKey: F9-AA-FE-19-B7-11-C9-E0-F0-ED-6E-83-72-74-53-EA
nwkKey: EC-44-A5-5D-84-0D-21-BA-0C-45-6B-1F-3D-0C-7C-F1
730763: engineUpdate, opmode=0x808
731328: Uplink data pending
731783: Considering band 0, which is available at 728500
732710: Considering band 3, which is available at 0
733562: No channel found in band 3
734123: Considering band 0, which is available at 728500
735059: No channel found in band 0
735619: Considering band 1, which is available at 728500
736552: Airtime available at 728500 (channel duty limit)
737470: Ready for uplink
738683: Updating info for TX at 731327, airtime will be 102919. Setting available time for band 1 to 863698944
739757: TXMODE, freq=868100000, len=26, SF=12, BW=125, CR=4/5, IH=0
841957: irq: dio: 0x0 flags: 0x8
842040: Scheduled job 0x27e, cb 0x17e9 ASAP
842175: Running job 0x27e, cb 0x17e9, deadline 0
842981: Scheduled job 0x27e, cb 0x17f4 at 901258
901263: Running job 0x27e, cb 0x17f4, deadline 901258
901425: RXMODE_SINGLE, freq=868100000, SF=12, BW=125, CR=4/5, IH=0
923273: irq: dio: 0x1 flags: 0x80
923357: Scheduled job 0x27e, cb 0x17fc ASAP
923507: Running job 0x27e, cb 0x17fc, deadline 0
924313: Scheduled job 0x27e, cb 0x1813 at 957614
957619: Running job 0x27e, cb 0x1813, deadline 957614
957782: RXMODE_SINGLE, freq=869525000, SF=12, BW=125, CR=4/5, IH=0
991954: irq: dio: 0x1 flags: 0x80
992038: Scheduled job 0x27e, cb 0x178f ASAP
992187: Running job 0x27e, cb 0x178f, deadline 0
992984[15887ms]: EV_TXCOMPLETE (includes waiting for RX windows)
995111: Scheduled job 0x237, cb 0x43e at 4745105
995210: engineUpdate, opmode=0x900
...
At the gateway we have the following output. Note the RX1 window for join-accept set to 5s: packet reception timestamp is 4149760124 (us) and downlink message timestamp is 4154760124 (us) which means 5s of delay.
...
--- rxlora. dst=0 type=0x00 src=0 seq=0 len=23 SNR=10 RSSIpkt=-21 BW=125 CR=4/5 SF=12
2020-01-22T15:01:51.165045
rcv ctrl pkt info (^p): 0,0,0,0,23,10,-21
splitted in: [0, 0, 0, 0, 23, 10, -21]
rawFormat(len=23 SNR=10 RSSI=-21)
rcv ctrl radio info (^r): 125,5,12,868100
splitted in: [125, 5, 12, 868100]
(BW=125 CR=5 SF=12)
rcv timestamp (^t): 2020-01-22T15:01:51.163516*4149760124
adding time zone info
new rcv timestamp (^t): 2020-01-22T15:01:51.163516+01:00
got first framing byte
--> got LoRa data prefix
raw format from LoRa gateway
LoRaWAN?
update ctrl pkt info (^p): 256,0,0xD0019A7C,46037,10,10,-21
+++ rxlora[868100]. lorawan type=0x00 src=0xD0019A7C seq=46037 len=10 SNR=10 RSSIpkt=-21 BW=125 CR=4/5 SF=12
--> number of LoRaWAN enabled clouds is 1
--> LoRaWAN encrypted cloud[0]
uploading with python CloudChirpStack.py
python CloudChirpStack.py "AHyaAdB+1bNwsqjUyJAaiwAXoHP6x4o=" "256,0,3489766012,46037,10,10,-21" "125,5,12,868100" "2020-01-22T15:01:51.163516+01:00*4149760124" "0000B827EBCE23C2"
CloudChirpStack: gw id: B827EBFFFFCE23C2
CloudChirpStack: Opening UDP socket to 127.0.0.1 (127.0.0.1) port 1700...
--> LoRaWAN encrypted cloud end
--> DATA encrypted: local AES LoRaWAN not activated
-----------------------------------------------------
Check for downlink requests 2020-01-22T15:01:51.863006
Read downlink: 192
Downlink entry: {"txpk":{"imme":false,"rfch":0,"powe":14,"ant":0,"brd":0,"tmst":4154760124,"freq":868.1,"modu":"LORA","datr":"SF12BW125","codr":"4/5","ipol":true,"size":17,"data":"ILgw53xkkcEsiZL+UC8AWvE="}}
Process downlink request
LoRaWAN downlink request
Target RX1
Packet sent, state 0
-----------------------------------------------------
...
After sending the downlink data (join-accept), the gateway receives the first data packet from the newly activated device and checks for downlink message. In the example, there are no.
...
--- rxlora. dst=0 type=0x00 src=0 seq=0 len=26 SNR=9 RSSIpkt=-24 BW=125 CR=4/5 SF=12
2020-01-22T15:01:59.334796
rcv ctrl pkt info (^p): 0,0,0,0,26,9,-24
splitted in: [0, 0, 0, 0, 26, 9, -24]
rawFormat(len=26 SNR=9 RSSI=-24)
rcv ctrl radio info (^r): 125,5,12,868100
splitted in: [125, 5, 12, 868100]
(BW=125 CR=5 SF=12)
rcv timestamp (^t): 2020-01-22T15:01:59.333712*4157930320
adding time zone info
new rcv timestamp (^t): 2020-01-22T15:01:59.333712+01:00
got first framing byte
--> got LoRa data prefix
raw format from LoRa gateway
LoRaWAN?
update ctrl pkt info (^p): 256,64,0x005C8ADC,0,13,9,-24
+++ rxlora[868100]. lorawan type=0x40 src=0x005C8ADC seq=0 len=13 SNR=9 RSSIpkt=-24 BW=125 CR=4/5 SF=12
--> number of LoRaWAN enabled clouds is 1
--> LoRaWAN encrypted cloud[0]
uploading with python CloudChirpStack.py
python CloudChirpStack.py "QNyKXACAAAABOoNDLqQpr03fViE93BHetKY=" "256,64,6064860,0,13,9,-24" "125,5,12,868100" "2020-01-22T15:01:59.333712+01:00*4157930320" "0000B827EBCE23C2"
CloudChirpStack: gw id: B827EBFFFFCE23C2
CloudChirpStack: Opening UDP socket to 127.0.0.1 (127.0.0.1) port 1700...
--> LoRaWAN encrypted cloud end
--> DATA encrypted: local AES LoRaWAN not activated
-----------------------------------------------------
Check for downlink requests 2020-01-22T15:02:00.033007
NO NEW DOWNLINK ENTRY
...
The non-LoRaWAN downlink mechanism is for non-LoRaWAN scenarios where there is no LoRaWAN Network Server. It is ONLY intended for non-LoRaWAN devices and does not have downlink data encryption feature. The Arduino_LoRa_temp is an example to have a simple non-LoRaWAN device receiving the non-LoRaWAN downlink. The non-LoRaWAN downlink mechanism works as follows:
downlink/downlink.txt
will be generated bypost_processing_gw.py
post_processing_gw.py
periodically check fordownlink/downlink-post.txt
and will build a queue of downlink requests["gateway_conf"]["downlink"]
indicates the time interval (in second) forpost_processing_gw.py
to check for adownlink-post.txt
file- for instance: "downlink" :
["gateway_conf"]["downlink"]=60
- set to 0 to disable the
post_processing_gw.py
periodically checking - set to -1 to disable the
lora_gateway
downlink checking (start_gw.py
will launchlora_gateway
with--ndl
option, i.e. no downlink)
- for instance: "downlink" :
post_processing_gw.py
readsdownlink-post.txt
and generates a newdownlink-post-queued.txt
file containing all queued/pending requestspost_processing_gw.py
then deletesdownlink-post.txt
- it means that if you want to send a message to a device, as the device will open a receive window only after its own data transmission, it is advised to post the downlink message in advance
- when a LoRa packet from device i is processed by
post_processing_gw.py
, it will check for a pending downlink request for device i - if it is the case, then
post_processing_gw.py
generates the correspondingdownlink.txt
file which will contain the downlink entry post_processing_gw.py
then remove the pending requests and generates a newdownlink-post-queued.txt
file containing all remaining queued/pending requests- in case the LoRa gateway is restarted with an existing
downlink-post-queued.txt
file, then these requests will be read and queued for future transmission - the
downlink-post.txt
file contains a series of lines in JSON format:{"status":"send_request","dst":3,"data":"/@Px#"}
- mandatory keys are
"status"
,"dst"
and"data"
."status"
must be"send_request"
- each line must be terminated by
\n
(0x0A). Do not leave an empty line at the end, just\n
after the last line \r
(0x0D) characters will be removed (some OS/tools use\r\n
for next line), as well as all empty lines- you can add other fields for your logging/information purposes but they will not be processed
downlink-post.txt
can be created in various ways: interactive mode (e.g. echo), MQTT, ftp, http, Python HTTP server,...- we provide an example using a simple python HTTP server with upload feature
- the web admin interface can also be used to issue downlink requests
- a
downlink-send.txt
file be appended with the new downlink transmission, marked as"status":"sent"
or"status":"sent_fail"
iflora_gateway
is compiled with#define KEEP_DOWNLINK_SENT_FILE
["gateway_conf"]["downlink"]=30
Start the gateway with post-processing features:
> sudo ./lora_gateway --mode 1 | python post_processing_gw.py
Output at gateway when starting
> sudo ./lora_gateway --mode 1 | python post_processing_gw.py
...
2020-01-20 17:17:16.755276
post downlink: checking for existing downlink/downlink-post-queued.txt
post downlink: reading existing downlink/downlink-post-queued.txt
post downlink: start with current list of pending downlink requests
Loading lib to compute downlink MIC
Starting thread to check for downlink requests every 30 seconds
Starting thread to perform fast statistics tasks
Current working directory: /home/pi/lora_gateway
SX1276 detected, starting.
SX1276 LF/HF calibration
...
**********Power ON: state 0
LoRa mode 1
Setting mode: state 0
Channel CH_10_868: state 0
Use PA_BOOST amplifier line
Set LoRa power dBm to 14
Power: state 0
Get Preamble Length: state 0
Preamble Length: 8
LoRa addr 1: state 0
SX1272/76 configured as LR-BS. Waiting RF input for transparent RF-serial bridge
Default sync word: 0x12
Low-level gateway has downlink support
2020-01-20T17:17:18.581158
Low-level gw status ON
...
New downlink-post.txt
created interactively (to test). Log on the gateway in another terminal
> cd lora_gateway/downlink
> echo "{\"status\":\"send_request\",\"dst\":6,\"data\":\"hello from gw\"}" > downlink-post.txt
Gateway's output, continued
...
Low-level gw status ON
post downlink: reading downlink/downlink-post.txt
{u'status': u'send_request', u'dst': 6, u'data': u'hello from gw'}
post downlink: writing to downlink/downlink-post-queued.txt
...
See how post_processing_gw.py
process the downlink/downlink-post.txt
file. The request has been queued and the content of downlink-post-queued.txt
is
{"status":"send_request","dst":6,"data":"hello from gw"}
Then use Arduino_LoRa_temp
, the address of the device is 6. The configuration is as follows to have a simple non-LoRaWAN device:
#define WITH_EEPROM
#define STRING_LIB
#define LOW_POWER
#define LOW_POWER_HIBERNATE
#define SHOW_LOW_POWER_CYCLE
#define WITH_RCVW
///////////////////////////////////////////////////////////////////
// CHANGE HERE THE NODE ADDRESS BETWEEN 2 AND 255
uint8_t node_addr=6;
//////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////
// CHANGE HERE THE LORA MODE
#define LORAMODE 1
//////////////////////////////////////////////////////////////////
Arduino_LoRa_temp
illustrates an example where the device opens a receive window after every transmission (#define WTH_RCVW
) to wait for downlink message coming from the gateway.
LoRa temperature sensor, extended version
Arduino Pro Mini detected
ATmega328P detected
SX1276 detected, starting
SX1276 LF/HF calibration
...
Get back previous sx1272 config
Using packet sequence number of 110
Forced to use default parameters
Using node addr of 6
Using idle period of 10
Setting Mode: state 0
Setting frequency: state 0
Setting Power: state 0
Setting node addr: state 0
SX1272 successfully configured
Mean temp is 22.50
Sending \!TC/22.50
Real payload size is 10
--> CS1
--> CAD 549
OK1
End send: 8560
LoRa pkt size 10
LoRa pkt seq 113
LoRa Sent in 1405
LoRa Sent w/CAD in 1956
Packet sent, state 0
...
Gateway's output continued
...
--- rxlora. dst=1 type=0x10 src=6 seq=110 len=10 SNR=6 RSSIpkt=-11 BW=125 CR=4/5 SF=12
2020-01-20T17:19:59.294221
rcv ctrl pkt info (^p): 1,16,6,110,10,6,-11
splitted in: [1, 16, 6, 110, 10, 6, -11]
(dst=1 type=0x10(DATA) src=6 seq=110 len=10 SNR=6 RSSI=-11)
post downlink: receive from 6 with pending request
in unicast mode
post downlink: downlink data is "hello from gw"
post downlink: generate downlink/downlink.txt from entry
{"status":"send_request","dst":6,"data":"hello from gw"}
did not find device 0x00000006 in device key list
using AppSKey and NwkSKey from default device
post downlink: write
{"status": "send_request", "dst": 6, "MIC3": "0x33", "MIC2": "0x27", "MIC1": "0x8e", "MIC0": "0x11", "data": "hello from gw"}
rcv ctrl radio info (^r): 125,5,12,865199
splitted in: [125, 5, 12, 865199]
(BW=125 CR=5 SF=12)
rcv timestamp (^t): 2020-01-20T17:19:59.292992
adding time zone info
new rcv timestamp (^t): 2020-01-20T17:19:59.292992+01:00
got first framing byte
--> got LoRa data prefix
+++ rxlora[865199]. dst=1 type=0x10 src=6 seq=110 len=10 SNR=6 RSSIpkt=-11 BW=125 CR=4/5 SF=12
valid app key: accept data
number of enabled clouds is 1
--> cloud[0]
uploading with python CloudThingSpeak.py
python CloudThingSpeak.py "TC/22.50" "1,16,6,110,10,6,-11" "125,5,12,865199" "2020-01-20T17:19:59.292992+01:00" "0000B827EBCE23C2"
ThingSpeak: uploading (multiple)
rcv msg to log (\!) on ThingSpeak ( default , default ):
ThingSpeak: will issue curl cmd
curl -s -k -X POST --data field1=22.50 https://api.thingspeak.com/update?key=SGSH52UGPVAUYG3S
ThingSpeak: returned code from server is 1008058
--> cloud end
...
It means that lora_gateway
program received the packet from device 6, and post_processing_gw.py
will generate the downlink.txt
file as there is one pending downlink request for device 6. After about 1.5s, lora_gateway
will look for the downlink/downlink.txt
file as illustrated below:
...
-----------------------------------------------------
Check for downlink requests 2020-01-20T17:20:00.345958
Read downlink: 126
Downlink entry: {"status": "send_request", "dst": 6, "MIC3": "0x33", "MIC2": "0x27", "MIC1": "0x8e", "MIC0": "0x11", "data": "hello from gw"}
Process downlink request
dst = 6
--> CAD duration 201
OK1
--> RSSI -126
Packet sent, state 0
-----------------------------------------------------
...
Content of downlink-backup-2016-10-10T11:41:20.txt
{"status":"send_request","dst":6,"data":"hello from gw"}
Content of downlink-queued.txt
2016-10-10T11:41:20.300 {"status":"queued","dst":6,"data":"hello from gw"}
Content of downlink-sent.txt
2016-10-10T11:41:20.502 {"status":"sent","dst":6,"data":"hello from gw"}
At device 6, we can see the received message
...
Wait for incoming packet
^p6,16,1,0,13,7,-47
hello from gw
The Arduino_LoRa_temp
template shows for instance how an '/@L2#' command from the gateway can be parsed to toggle a LED connected to digital pin 2 and also how the device can set (change) its address when receiving the '/@Ax#' command. For instance, sending '/@A10#' to a device will set its address to 10. As a device already has a hardcoded address (the default one indicated in the Arduino_LoRa_temp example
is 6) it is possible to use the broadcast address, i.e. 0, to send to the device if you don't know its exact address. The procedure is as follows to set the device's address to 10:
- start you gateway
- create a
downlink-post.txt
with a single entrycd lora_gateway/downlink echo "{"status":"send_request","dst":0,"data":"/@A10#"}" > downlink-post.txt
- wait at least for the downlink timer declared in
gateway_conf.json
, e.g. "downlink" : 60 - once the gateway has queued this downlink request, you can switch on your device
- when power on, the device will transmit a first sample, then will open a receive window
- as the downlink request specifies a broadcast address, the gateway will broadcast the downlink message
- as the message is broadcasted, the device will accept the incoming message
- as the message is "/@A10#", the device will set its address to 10
- this new address will be saved in EEPROM so that a reset of the board will keep the new address
- if you have several devices to configure, switch off the previous device and start again at step 2 with a new device
Note that, alternatively, if you don't want to use the downlink feature from the gateway to change a device's address, you can use an interactive device dedicated for this task. In this case, switch on your device (you can check if the gateway receives from the device) and then wait for about 5s (or about 8s if you are not checking reception on the gateway) before issuing the following command "/@D0#/@A10#" to the interactive device. This command means "broadcast /@A10#". To check if the new address has be taken into account, switch off and then switch on your device and check if the gateway receive a message from the device, indicating the new address as the source address.
The web admin interface has a downlink request
tab that can be used to issue downlink requests.
You can use scp
to copy new downlink-post.txt
file into the gateway:
> echo "{\"status\":\"send_request\",\"dst\":6,\"data\":\"reply from gw\"}" > downlink-post.txt
> scp downlink.txt pi@<gw_ip_addr>:/home/pi/lora_gateway/downlink
Use the simple Python-based "SimpleHTTPRequestHandler" at https://gist.github.com/UniIsland/3346170. You have to modify it this way as described at https://gist.github.com/rctay/25bed284cd4bcc1477f4/revisions. Line 73:
- f.write("<br><a href=\"%s\">back</a>" % self.headers['referer'])
+ if 'referer' in self.headers:
+ f.write("<br><a href=\"%s\">back</a>" % self.headers['referer'])
Then also line 102-105, remove (or comment) first 2 lines. Copy this file into the downlink
folder and run it in background:
> python SimpleHTTPServerWithUpload.py &
When you want to upload a new transmission request, create a new downlink-post.txt
file:
> echo "{\"status\":\"send_request\",\"dst\":6,\"data\":\"reply from gw\"}" > downlink-post.txt
Use your web browser on http://<gw_ip_addr>:8000 to view the directory content and upload the new downlink-post.txt
file using the provided tab. Or with curl-based command line:
> curl -X POST -F file=@downlink.txt http://<gw_ip_addr>:8000
post_processing_gw.py
will then queue this request at the next downlink-post
request check.
You can implement many various ways with stronger authentication mechanisms. You can also use MQTT client/server with a Python piece of code to create the donwlink-post.txt
file.
The low-cost gateway program is not multi-threaded. The radio module is anyway not capable of simultaneous reception and transmission. Therefore while the radio is transmitting downlink requests, it is not doing reception. It means that the gateway may not "see" some incoming packets if they are transmitted in the same time interval. Fortunately, if the downlink request is only a few bytes long, the gateway should spend a minimum time to handle a downlink transmissions, e.g. less than 2 seconds when RX1 is used. However, if you know that you do not need the downlink features (that includes OTAA) then set ["gateway_conf"]["downlink"]
to -1 to disable the lora_gateway
downlink checking after each LoRa packet reception.
The JSON parser
The JSON parser is RapidJSON (https://github.com/miloyip/rapidjson). It works great on the Raspberry and on many other platforms. Performances of RapidJSON are reported here.
Simple HTTP Server With Upload
Use the simple Python-based "SimpleHTTPRequestHandler" (https://gist.github.com/UniIsland/3346170).
Enjoy! C. Pham