* Initial release for SX1302 CoreCell Reference Design.
This commit is contained in:
Michael Coracin 2019-07-12 15:40:13 +02:00
commit 4c61c5d48e
79 changed files with 30157 additions and 0 deletions

93
packet_forwarder/Makefile Normal file
View file

@ -0,0 +1,93 @@
### get external defined data
include ../target.cfg
### Application-specific constants
APP_NAME := lora_pkt_fwd
### Environment constants
LGW_PATH ?= ../libloragw
LIB_PATH ?= ../libtools
ARCH ?=
CROSS_COMPILE ?=
OBJDIR = obj
INCLUDES = $(wildcard inc/*.h)
### External constant definitions
# must get library build option to know if mpsse must be linked or not
include $(LGW_PATH)/library.cfg
RELEASE_VERSION := `cat ../VERSION`
### Constant symbols
CC := $(CROSS_COMPILE)gcc
AR := $(CROSS_COMPILE)ar
CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I. -I../libtools/inc
VFLAG := -D VERSION_STRING="\"$(RELEASE_VERSION)\""
### Constants for Lora concentrator HAL library
# List the library sub-modules that are used by the application
LGW_INC =
ifneq ($(wildcard $(LGW_PATH)/inc/config.h),)
# only for HAL version 1.3 and beyond
LGW_INC += $(LGW_PATH)/inc/config.h
endif
LGW_INC += $(LGW_PATH)/inc/loragw_hal.h
### Linking options
LIBS := -lloragw -ltinymt32 -lparson -lbase64 -lrt -lpthread -lm
### General build targets
all: $(APP_NAME)
clean:
rm -f $(OBJDIR)/*.o
rm -f $(APP_NAME)
ifneq ($(strip $(TARGET_IP)),)
ifneq ($(strip $(TARGET_DIR)),)
ifneq ($(strip $(TARGET_USR)),)
install:
@echo "---- Copying packet_forwarder files to $(TARGET_IP):$(TARGET_DIR)"
@ssh $(TARGET_USR)@$(TARGET_IP) "mkdir -p $(TARGET_DIR)"
@scp lora_pkt_fwd $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR)
install_conf:
@echo "---- Copying packet_forwarder conf files to $(TARGET_IP):$(TARGET_DIR)"
@ssh $(TARGET_USR)@$(TARGET_IP) "mkdir -p $(TARGET_DIR)"
@scp global_conf.json.sx1250 $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR)
@scp global_conf.json.sx1257 $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR)
else
@echo "ERROR: TARGET_USR is not configured in target.cfg"
endif
else
@echo "ERROR: TARGET_DIR is not configured in target.cfg"
endif
else
@echo "ERROR: TARGET_IP is not configured in target.cfg"
endif
### Sub-modules compilation
$(OBJDIR):
mkdir -p $(OBJDIR)
$(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR)
$(CC) -c $(CFLAGS) -I$(LGW_PATH)/inc $< -o $@
### Main program compilation and assembly
$(OBJDIR)/$(APP_NAME).o: src/$(APP_NAME).c $(LGW_INC) $(INCLUDES) | $(OBJDIR)
$(CC) -c $(CFLAGS) $(VFLAG) -I$(LGW_PATH)/inc $< -o $@
$(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(LGW_PATH)/libloragw.a $(OBJDIR)/jitqueue.o
$(CC) -L$(LGW_PATH) -L$(LIB_PATH) $< $(OBJDIR)/jitqueue.o -o $@ $(LIBS)
### EOF

View file

@ -0,0 +1,477 @@
______ _
/ _____) _ | |
( (____ _____ ____ _| |_ _____ ____| |__
\____ \| ___ | (_ _) ___ |/ ___) _ \
_____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
(C)2019 Semtech
Basic communication protocol between LoRa gateway and Network Server
====================================================================
## 1. Introduction
The protocol between the gateway and the server is purposefully very basic and
for demonstration purpose only, or for use on private and reliable networks.
There is no authentication of the gateway or the server, and the acknowledges
are only used for network quality assessment, not to correct UDP datagrams
losses (no retries).
## 2. System schematic and definitions
((( Y )))
|
|
+ - -|- - - - - - - - - - - - - + xxxxxxxxxxxx +--------+
| +--+-----------+ +------+ | xx x x xxx | |
| | | | | | xx Internet xx | |
| | Concentrator |<--->| Host |<-------xx or xx-------->| |
| | | SPI | | | xx Intranet xx | Server |
| +--------------+ +------+ | xxxx x xxxx | |
| ^ ^ | xxxxxxxx | |
| | PPS +-------+ NMEA | | | |
| +-----| GPS |-------+ | +--------+
| | (opt) | |
| +-------+ |
| |
| Gateway |
+- - - - - - - - - - - - - - - -+
__Concentrator__: radio RX/TX board, based on Semtech multichannel modems
(SX130x), transceivers (SX135x) and/or low-power stand-alone modems (SX127x).
__Host__: embedded computer on which the packet forwarder is run. Drives the
concentrator through a SPI link.
__GPS__: GNSS (GPS, Galileo, GLONASS, etc) receiver with a "1 Pulse Per Second"
output and a serial link to the host to send NMEA frames containing time and
geographical coordinates data. Optional.
__Gateway__: a device composed of at least one radio concentrator, a host, some
network connection to the internet or a private network (Ethernet, 3G, Wifi,
microwave link), and optionally a GPS receiver for synchronization.
__Server__: an abstract computer that will process the RF packets received and
forwarded by the gateway, and issue RF packets in response that the gateway
will have to emit.
It is assumed that the gateway can be behind a NAT or a firewall stopping any
incoming connection.
It is assumed that the server has an static IP address (or an address solvable
through a DNS service) and is able to receive incoming connections on a
specific port.
## 3. Upstream protocol
### 3.1. Sequence diagram ###
+---------+ +---------+
| Gateway | | Server |
+---------+ +---------+
| -----------------------------------\ |
|-| When 1-N RF packets are received | |
| ------------------------------------ |
| |
| PUSH_DATA (token X, GW MAC, JSON payload) |
|------------------------------------------------------------->|
| |
| PUSH_ACK (token X) |
|<-------------------------------------------------------------|
| ------------------------------\ |
| | process packets *after* ack |-|
| ------------------------------- |
| |
### 3.2. PUSH_DATA packet ###
That packet type is used by the gateway mainly to forward the RF packets
received, and associated metadata, to the server.
Bytes | Function
:------:|---------------------------------------------------------------------
0 | protocol version = 2
1-2 | random token
3 | PUSH_DATA identifier 0x00
4-11 | Gateway unique identifier (MAC address)
12-end | JSON object, starting with {, ending with }, see section 4
### 3.3. PUSH_ACK packet ###
That packet type is used by the server to acknowledge immediately all the
PUSH_DATA packets received.
Bytes | Function
:------:|---------------------------------------------------------------------
0 | protocol version = 2
1-2 | same token as the PUSH_DATA packet to acknowledge
3 | PUSH_ACK identifier 0x01
## 4. Upstream JSON data structure
The root object can contain an array named "rxpk":
``` json
{
"rxpk":[ {...}, ...]
}
```
That array contains at least one JSON object, each object contain a RF packet
and associated metadata with the following fields:
Name | Type | Function
:----:|:------:|--------------------------------------------------------------
time | string | UTC time of pkt RX, us precision, ISO 8601 'compact' format
tmms | number | GPS time of pkt RX, number of milliseconds since 06.Jan.1980
tmst | number | Internal timestamp of "RX finished" event (32b unsigned)
freq | number | RX central frequency in MHz (unsigned float, Hz precision)
chan | number | Concentrator "IF" channel used for RX (unsigned integer)
rfch | number | Concentrator "RF chain" used for RX (unsigned integer)
mid | number | Concentrator modem ID on which pkt has been received
stat | number | CRC status: 1 = OK, -1 = fail, 0 = no CRC
modu | string | Modulation identifier "LORA" or "FSK"
datr | string | LoRa datarate identifier (eg. SF12BW500)
datr | number | FSK datarate (unsigned, in bits per second)
codr | string | LoRa ECC coding rate identifier
rssi | number | RSSI of the channel in dBm (signed integer, 1 dB precision)
rssis| number | RSSI of the signal in dBm (signed integer, 1 dB precision)
lsnr | number | Lora SNR ratio in dB (signed float, 0.1 dB precision)
foff | number | LoRa frequency offset in Hz (signed interger)
size | number | RF packet payload size in bytes (unsigned integer)
data | string | Base64 encoded RF packet payload, padded
Example (white-spaces, indentation and newlines added for readability):
``` json
{"rxpk":[
{
"time":"2013-03-31T16:21:17.528002Z",
"tmst":3512348611,
"chan":2,
"rfch":0,
"freq":866.349812,
"stat":1,
"modu":"LORA",
"datr":"SF7BW125",
"codr":"4/6",
"rssi":-35,
"lsnr":5.1,
"size":32,
"data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"
},{
"time":"2013-03-31T16:21:17.530974Z",
"tmst":3512348514,
"chan":9,
"rfch":1,
"freq":869.1,
"stat":1,
"modu":"FSK",
"datr":50000,
"rssi":-75,
"size":16,
"data":"VEVTVF9QQUNLRVRfMTIzNA=="
},{
"time":"2013-03-31T16:21:17.532038Z",
"tmst":3316387610,
"chan":0,
"rfch":0,
"freq":863.00981,
"stat":1,
"modu":"LORA",
"datr":"SF10BW125",
"codr":"4/7",
"rssi":-38,
"lsnr":5.5,
"size":32,
"data":"ysgRl452xNLep9S1NTIg2lomKDxUgn3DJ7DE+b00Ass"
}
]}
```
The root object can also contain an object named "stat" :
``` json
{
"rxpk":[ {...}, ...],
"stat":{...}
}
```
It is possible for a packet to contain no "rxpk" array but a "stat" object.
``` json
{
"stat":{...}
}
```
That object contains the status of the gateway, with the following fields:
Name | Type | Function
:----:|:------:|--------------------------------------------------------------
time | string | UTC 'system' time of the gateway, ISO 8601 'expanded' format
lati | number | GPS latitude of the gateway in degree (float, N is +)
long | number | GPS latitude of the gateway in degree (float, E is +)
alti | number | GPS altitude of the gateway in meter RX (integer)
rxnb | number | Number of radio packets received (unsigned integer)
rxok | number | Number of radio packets received with a valid PHY CRC
rxfw | number | Number of radio packets forwarded (unsigned integer)
ackr | number | Percentage of upstream datagrams that were acknowledged
dwnb | number | Number of downlink datagrams received (unsigned integer)
txnb | number | Number of packets emitted (unsigned integer)
Example (white-spaces, indentation and newlines added for readability):
``` json
{"stat":{
"time":"2014-01-12 08:59:28 GMT",
"lati":46.24000,
"long":3.25230,
"alti":145,
"rxnb":2,
"rxok":2,
"rxfw":2,
"ackr":100.0,
"dwnb":2,
"txnb":2
}}
```
## 5. Downstream protocol
### 5.1. Sequence diagram ###
+---------+ +---------+
| Gateway | | Server |
+---------+ +---------+
| -----------------------------------\ |
|-| Every N seconds (keepalive time) | |
| ------------------------------------ |
| |
| PULL_DATA (token Y, MAC@) |
|------------------------------------------------------------->|
| |
| PULL_ACK (token Y) |
|<-------------------------------------------------------------|
| |
+---------+ +---------+
| Gateway | | Server |
+---------+ +---------+
| ------------------------------------------------------\ |
| | Anytime after first PULL_DATA for each packet to TX |-|
| ------------------------------------------------------- |
| |
| PULL_RESP (token Z, JSON payload) |
|<-------------------------------------------------------------|
| |
| TX_ACK (token Z, JSON payload) |
|------------------------------------------------------------->|
### 5.2. PULL_DATA packet ###
That packet type is used by the gateway to poll data from the server.
This data exchange is initialized by the gateway because it might be
impossible for the server to send packets to the gateway if the gateway is
behind a NAT.
When the gateway initialize the exchange, the network route towards the
server will open and will allow for packets to flow both directions.
The gateway must periodically send PULL_DATA packets to be sure the network
route stays open for the server to be used at any time.
Bytes | Function
:------:|---------------------------------------------------------------------
0 | protocol version = 2
1-2 | random token
3 | PULL_DATA identifier 0x02
4-11 | Gateway unique identifier (MAC address)
### 5.3. PULL_ACK packet ###
That packet type is used by the server to confirm that the network route is
open and that the server can send PULL_RESP packets at any time.
Bytes | Function
:------:|---------------------------------------------------------------------
0 | protocol version = 2
1-2 | same token as the PULL_DATA packet to acknowledge
3 | PULL_ACK identifier 0x04
### 5.4. PULL_RESP packet ###
That packet type is used by the server to send RF packets and associated
metadata that will have to be emitted by the gateway.
Bytes | Function
:------:|---------------------------------------------------------------------
0 | protocol version = 2
1-2 | random token
3 | PULL_RESP identifier 0x03
4-end | JSON object, starting with {, ending with }, see section 6
### 5.5. TX_ACK packet ###
That packet type is used by the gateway to send a feedback to the server
to inform if a downlink request has been accepted or rejected by the gateway.
The datagram may optionnaly contain a JSON string to give more details on
acknoledge. If no JSON is present (empty string), this means than no error
occured.
Bytes | Function
:------:|---------------------------------------------------------------------
0 | protocol version = 2
1-2 | same token as the PULL_RESP packet to acknowledge
3 | TX_ACK identifier 0x05
4-11 | Gateway unique identifier (MAC address)
12-end | [optional] JSON object, starting with {, ending with }, see section 6
## 6. Downstream JSON data structure
The root object of PULL_RESP packet must contain an object named "txpk":
``` json
{
"txpk": {...}
}
```
That object contain a RF packet to be emitted and associated metadata with the
following fields:
Name | Type | Function
:----:|:------:|--------------------------------------------------------------
imme | bool | Send packet immediately (will ignore tmst & tmms)
tmst | number | Send packet on a certain timestamp value (will ignore tmms)
tmms | number | Send packet at a certain GPS time (GPS synchronization required)
freq | number | TX central frequency in MHz (unsigned float, Hz precision)
rfch | number | Concentrator "RF chain" used for TX (unsigned integer)
powe | number | TX output power in dBm (unsigned integer, dBm precision)
modu | string | Modulation identifier "LORA" or "FSK"
datr | string | LoRa datarate identifier (eg. SF12BW500)
datr | number | FSK datarate (unsigned, in bits per second)
codr | string | LoRa ECC coding rate identifier
fdev | number | FSK frequency deviation (unsigned integer, in Hz)
ipol | bool | Lora modulation polarization inversion
prea | number | RF preamble size (unsigned integer)
size | number | RF packet payload size in bytes (unsigned integer)
data | string | Base64 encoded RF packet payload, padding optional
ncrc | bool | If true, disable the CRC of the physical layer (optional)
Most fields are optional.
If a field is omitted, default parameters will be used.
Examples (white-spaces, indentation and newlines added for readability):
``` json
{"txpk":{
"imme":true,
"freq":864.123456,
"rfch":0,
"powe":14,
"modu":"LORA",
"datr":"SF11BW125",
"codr":"4/6",
"ipol":false,
"size":32,
"data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"
}}
```
``` json
{"txpk":{
"imme":true,
"freq":861.3,
"rfch":0,
"powe":12,
"modu":"FSK",
"datr":50000,
"fdev":3000,
"size":32,
"data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"
}}
```
In case of error or warning, the root object of TX_ACK packet must contain an
object named "txpk_ack":
``` json
{
"txpk_ack": {...}
}
```
That object contain status information concerning the associated PULL_RESP packet.
Name | Type | Function
:----:|:------:|-----------------------------------------------------------------------------------------
error | string | Indicates the type of failure that occurred for downlink request (optional)
warn | string | Indicates that downlink request has been accepted with limitation (optional)
value | string | When a warning is raised, it gives indications about the limitation (optional)
value | number | When a warning is raised, it gives indications about the limitation (optional)
The possible values of the "error" field are:
Value | Definition
:-----------------:|---------------------------------------------------------------------
TOO_LATE | Rejected because it was already too late to program this packet for downlink
TOO_EARLY | Rejected because downlink packet timestamp is too much in advance
COLLISION_PACKET | Rejected because there was already a packet programmed in requested timeframe
COLLISION_BEACON | Rejected because there was already a beacon planned in requested timeframe
TX_FREQ | Rejected because requested frequency is not supported by TX RF chain
GPS_UNLOCKED | Rejected because GPS is unlocked, so GPS timestamp cannot be used
The possible values of the "warn" field are:
Value | Definition
:-----------------:|---------------------------------------------------------------------
TX_POWER | The requested power is not supported by the gateway, the power actually used is given in the value field
Examples (white-spaces, indentation and newlines added for readability):
``` json
{"txpk_ack":{
"error":"COLLISION_PACKET"
}}
```
``` json
{"txpk_ack":{
"warn":"TX_POWER",
"value":20
}}
```
## 7. Revisions
### v1.5 ###
* Moved TX_POWER from "error" category to "warn" category in "txpk_ack" object
### v1.4 ###
* Added "tmms" field for GPS time as a monotonic number of milliseconds
ellapsed since January 6th, 1980 (GPS Epoch). No leap second.
### v1.3 ###
* Added downlink feedback from gateway to server (PULL_RESP -> TX_ACK)
### v1.2 ###
* Added value of FSK bitrate for upstream.
* Added parameters for FSK bitrate and frequency deviation for downstream.
### v1.1 ###
* Added syntax for status report JSON object on upstream.
### v1.0 ###
* Initial version.

View file

@ -0,0 +1,98 @@
{
"SX130x_conf": {
"spidev_path": "/dev/spidev0.0",
"lorawan_public": true,
"clksrc": 0,
"antenna_gain": 0, /* antenna gain, in dBi */
"full_duplex": false,
"precision_timestamp": {
"enable": false,
"max_ts_metrics": 255,
"nb_symbols": 1
},
"radio_0": {
"enable": true,
"type": "SX1250",
"freq": 867500000,
"rssi_offset": -215.4,
"rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0},
"tx_enable": true,
"tx_freq_min": 863000000,
"tx_freq_max": 870000000,
"tx_gain_lut":[
{"rf_power": 12, "pa_gain": 0, "pwr_idx": 16},
{"rf_power": 13, "pa_gain": 0, "pwr_idx": 17},
{"rf_power": 14, "pa_gain": 0, "pwr_idx": 18},
{"rf_power": 15, "pa_gain": 0, "pwr_idx": 20},
{"rf_power": 16, "pa_gain": 1, "pwr_idx": 0},
{"rf_power": 17, "pa_gain": 1, "pwr_idx": 1},
{"rf_power": 18, "pa_gain": 1, "pwr_idx": 2},
{"rf_power": 19, "pa_gain": 1, "pwr_idx": 3},
{"rf_power": 20, "pa_gain": 1, "pwr_idx": 4},
{"rf_power": 21, "pa_gain": 1, "pwr_idx": 5},
{"rf_power": 22, "pa_gain": 1, "pwr_idx": 6},
{"rf_power": 23, "pa_gain": 1, "pwr_idx": 7},
{"rf_power": 24, "pa_gain": 1, "pwr_idx": 8},
{"rf_power": 25, "pa_gain": 1, "pwr_idx": 9},
{"rf_power": 26, "pa_gain": 1, "pwr_idx": 11},
{"rf_power": 27, "pa_gain": 1, "pwr_idx": 13}
]
},
"radio_1": {
"enable": true,
"type": "SX1250",
"freq": 868500000,
"rssi_offset": -215.4,
"rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0},
"tx_enable": false
},
"chan_multiSF_0": {"enable": true, "radio": 1, "if": -400000},
"chan_multiSF_1": {"enable": true, "radio": 1, "if": -200000},
"chan_multiSF_2": {"enable": true, "radio": 1, "if": 0},
"chan_multiSF_3": {"enable": true, "radio": 0, "if": -400000},
"chan_multiSF_4": {"enable": true, "radio": 0, "if": -200000},
"chan_multiSF_5": {"enable": true, "radio": 0, "if": 0},
"chan_multiSF_6": {"enable": true, "radio": 0, "if": 200000},
"chan_multiSF_7": {"enable": true, "radio": 0, "if": 400000},
"chan_Lora_std": {"enable": true, "radio": 1, "if": -200000, "bandwidth": 250000, "spread_factor": 7,
"implicit_hdr": false, "implicit_payload_length": 17, "implicit_crc_en": false, "implicit_coderate": 1},
"chan_FSK": {"enable": true, "radio": 1, "if": 300000, "bandwidth": 125000, "datarate": 50000}
},
"gateway_conf": {
"gateway_ID": "AA555A0000000000",
/* change with default server address/ports */
"server_address": "localhost",
"serv_port_up": 1730,
"serv_port_down": 1730,
/* adjust the following parameters for your network */
"keepalive_interval": 10,
"stat_interval": 30,
"push_timeout_ms": 100,
/* forward only valid packets */
"forward_crc_valid": true,
"forward_crc_error": false,
"forward_crc_disabled": false,
/* GPS configuration */
"gps_tty_path": "/dev/ttyS0",
/* GPS reference coordinates */
"ref_latitude": 0.0,
"ref_longitude": 0.0,
"ref_altitude": 0,
/* Beaconing parameters */
"beacon_period": 128,
"beacon_freq_hz": 869525000,
"beacon_datarate": 9,
"beacon_bw_hz": 125000,
"beacon_power": 14,
"beacon_infodesc": 0
},
"debug_conf": {
"ref_payload":[
{"id": "0xCAFE1234"},
{"id": "0xCAFE2345"}
],
"log_file": "loragw_hal.log"
}
}

View file

@ -0,0 +1,98 @@
{
"SX130x_conf": {
"spidev_path": "/dev/spidev0.0",
"lorawan_public": true,
"clksrc": 0,
"antenna_gain": 0, /* antenna gain, in dBi */
"full_duplex": false,
"precision_timestamp": {
"enable": false,
"max_ts_metrics": 255,
"nb_symbols": 1
},
"radio_0": {
"enable": true,
"type": "SX1257",
"freq": 867500000,
"rssi_offset": -196.0,
"rssi_tcomp": {"coeff_a": -0.006, "coeff_b": 0.789, "coeff_c": -14.992, "coeff_d": 1988.572, "coeff_e": 105236.996},
"tx_enable": true,
"tx_freq_min": 863000000,
"tx_freq_max": 870000000,
"tx_gain_lut":[
{"rf_power": -6, "pa_gain": 0, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0},
{"rf_power": -3, "pa_gain": 0, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 0, "pa_gain": 0, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0},
{"rf_power": 3, "pa_gain": 1, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0},
{"rf_power": 6, "pa_gain": 1, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 10, "pa_gain": 1, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0},
{"rf_power": 11, "pa_gain": 1, "dac_gain": 3, "mix_gain": 13, "dig_gain": 0},
{"rf_power": 12, "pa_gain": 2, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0},
{"rf_power": 13, "pa_gain": 1, "dac_gain": 3, "mix_gain": 15, "dig_gain": 0},
{"rf_power": 14, "pa_gain": 2, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 16, "pa_gain": 2, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0},
{"rf_power": 20, "pa_gain": 3, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0},
{"rf_power": 23, "pa_gain": 3, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 25, "pa_gain": 3, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0},
{"rf_power": 26, "pa_gain": 3, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0},
{"rf_power": 27, "pa_gain": 3, "dac_gain": 3, "mix_gain": 14, "dig_gain": 0}
]
},
"radio_1": {
"enable": true,
"type": "SX1257",
"freq": 868500000,
"rssi_offset": -196.0,
"rssi_tcomp": {"coeff_a": -0.006, "coeff_b": 0.789, "coeff_c": -14.992, "coeff_d": 1988.572, "coeff_e": 105236.996},
"tx_enable": false
},
"chan_multiSF_0": {"enable": true, "radio": 1, "if": -400000},
"chan_multiSF_1": {"enable": true, "radio": 1, "if": -200000},
"chan_multiSF_2": {"enable": true, "radio": 1, "if": 0},
"chan_multiSF_3": {"enable": true, "radio": 0, "if": -400000},
"chan_multiSF_4": {"enable": true, "radio": 0, "if": -200000},
"chan_multiSF_5": {"enable": true, "radio": 0, "if": 0},
"chan_multiSF_6": {"enable": true, "radio": 0, "if": 200000},
"chan_multiSF_7": {"enable": true, "radio": 0, "if": 400000},
"chan_Lora_std": {"enable": true, "radio": 1, "if": -200000, "bandwidth": 250000, "spread_factor": 7,
"implicit_hdr": false, "implicit_payload_length": 17, "implicit_crc_en": false, "implicit_coderate": 1},
"chan_FSK": {"enable": true, "radio": 1, "if": 300000, "bandwidth": 125000, "datarate": 50000}
},
"gateway_conf": {
"gateway_ID": "AA555A0000000000",
/* change with default server address/ports */
"server_address": "localhost",
"serv_port_up": 1730,
"serv_port_down": 1730,
/* adjust the following parameters for your network */
"keepalive_interval": 10,
"stat_interval": 30,
"push_timeout_ms": 100,
/* forward only valid packets */
"forward_crc_valid": true,
"forward_crc_error": false,
"forward_crc_disabled": false,
/* GPS configuration */
"gps_tty_path": "/dev/ttyS0",
/* GPS reference coordinates */
"ref_latitude": 0.0,
"ref_longitude": 0.0,
"ref_altitude": 0,
/* Beaconing parameters */
"beacon_period": 128,
"beacon_freq_hz": 869525000,
"beacon_datarate": 9,
"beacon_bw_hz": 125000,
"beacon_power": 14,
"beacon_infodesc": 0
},
"debug_conf": {
"ref_payload":[
{"id": "0xCAFE1234"},
{"id": "0xCAFE2345"}
],
"log_file": "loragw_hal.log"
}
}

View file

@ -0,0 +1,93 @@
{
"SX130x_conf": {
"spidev_path": "/dev/spidev0.0",
"lorawan_public": true,
"clksrc": 1,
"antenna_gain": 0, /* antenna gain, in dBi */
"full_duplex": true,
"precision_timestamp": {
"enable": false,
"max_ts_metrics": 255,
"nb_symbols": 1
},
"radio_0": {
"enable": true,
"type": "SX1257",
"freq": 913800000,
"rssi_offset": -196.0,
"tx_enable": true,
"tx_freq_min": 100000000,
"tx_freq_max": 950000000,
"tx_gain_lut":[
{"rf_power": -6, "pa_gain": 0, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0},
{"rf_power": -3, "pa_gain": 0, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 0, "pa_gain": 0, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0},
{"rf_power": 3, "pa_gain": 1, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0},
{"rf_power": 6, "pa_gain": 1, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 10, "pa_gain": 1, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0},
{"rf_power": 11, "pa_gain": 1, "dac_gain": 3, "mix_gain": 13, "dig_gain": 0},
{"rf_power": 12, "pa_gain": 2, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0},
{"rf_power": 13, "pa_gain": 1, "dac_gain": 3, "mix_gain": 15, "dig_gain": 0},
{"rf_power": 14, "pa_gain": 2, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 16, "pa_gain": 2, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0},
{"rf_power": 20, "pa_gain": 3, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0},
{"rf_power": 23, "pa_gain": 3, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 25, "pa_gain": 3, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0},
{"rf_power": 26, "pa_gain": 3, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0},
{"rf_power": 27, "pa_gain": 3, "dac_gain": 3, "mix_gain": 14, "dig_gain": 0}
]
},
"radio_1": {
"enable": true,
"type": "SX1257",
"freq": 914600000,
"rssi_offset": -196.0,
"tx_enable": true,
"tx_freq_min": 100000000,
"tx_freq_max": 950000000,
"tx_gain_lut":[
{"rf_power": -6, "pa_gain": 0, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0},
{"rf_power": -3, "pa_gain": 0, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 0, "pa_gain": 0, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0},
{"rf_power": 3, "pa_gain": 1, "dac_gain": 3, "mix_gain": 8, "dig_gain": 0},
{"rf_power": 6, "pa_gain": 1, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 10, "pa_gain": 1, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0},
{"rf_power": 11, "pa_gain": 1, "dac_gain": 3, "mix_gain": 13, "dig_gain": 0},
{"rf_power": 12, "pa_gain": 2, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0},
{"rf_power": 13, "pa_gain": 1, "dac_gain": 3, "mix_gain": 15, "dig_gain": 0},
{"rf_power": 14, "pa_gain": 2, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 16, "pa_gain": 2, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0},
{"rf_power": 20, "pa_gain": 3, "dac_gain": 3, "mix_gain": 9, "dig_gain": 0},
{"rf_power": 23, "pa_gain": 3, "dac_gain": 3, "mix_gain": 10, "dig_gain": 0},
{"rf_power": 25, "pa_gain": 3, "dac_gain": 3, "mix_gain": 11, "dig_gain": 0},
{"rf_power": 26, "pa_gain": 3, "dac_gain": 3, "mix_gain": 12, "dig_gain": 0},
{"rf_power": 27, "pa_gain": 3, "dac_gain": 3, "mix_gain": 14, "dig_gain": 0}
]
},
"chan_multiSF_0": {"enable": true, "radio": 0, "if": -300000},
"chan_multiSF_1": {"enable": true, "radio": 0, "if": -100000},
"chan_multiSF_2": {"enable": true, "radio": 0, "if": 100000},
"chan_multiSF_3": {"enable": true, "radio": 0, "if": 300000},
"chan_multiSF_4": {"enable": true, "radio": 1, "if": -300000},
"chan_multiSF_5": {"enable": true, "radio": 1, "if": -100000},
"chan_multiSF_6": {"enable": true, "radio": 1, "if": 100000},
"chan_multiSF_7": {"enable": true, "radio": 1, "if": 300000}
},
"gateway_conf": {
"gateway_ID": "AA555A0000000000",
/* change with default server address/ports */
"server_address": "localhost",
"serv_port_up": 1750,
"serv_port_down": 1750,
/* adjust the following parameters for your network */
"keepalive_interval": 10,
"stat_interval": 30,
"push_timeout_ms": 100,
/* forward only valid packets */
"forward_crc_valid": true,
"forward_crc_error": false,
"forward_crc_disabled": false
}
}

View file

@ -0,0 +1,154 @@
/*
/ _____) _ | |
( (____ _____ ____ _| |_ _____ ____| |__
\____ \| ___ | (_ _) ___ |/ ___) _ \
_____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
(C)2019 Semtech
Description:
LoRa concentrator : Just In Time TX scheduling queue
License: Revised BSD License, see LICENSE.TXT file include in the project
*/
#ifndef _LORA_PKTFWD_JIT_H
#define _LORA_PKTFWD_JIT_H
/* -------------------------------------------------------------------------- */
/* --- DEPENDANCIES --------------------------------------------------------- */
#include <stdint.h> /* C99 types */
#include <stdbool.h> /* bool type */
#include <sys/time.h> /* timeval */
#include "loragw_hal.h"
/* -------------------------------------------------------------------------- */
/* --- PUBLIC CONSTANTS ----------------------------------------------------- */
#define JIT_QUEUE_MAX 32 /* Maximum number of packets to be stored in JiT queue */
#define JIT_NUM_BEACON_IN_QUEUE 3 /* Number of beacons to be loaded in JiT queue at any time */
/* -------------------------------------------------------------------------- */
/* --- PUBLIC TYPES --------------------------------------------------------- */
enum jit_pkt_type_e {
JIT_PKT_TYPE_DOWNLINK_CLASS_A,
JIT_PKT_TYPE_DOWNLINK_CLASS_B,
JIT_PKT_TYPE_DOWNLINK_CLASS_C,
JIT_PKT_TYPE_BEACON
};
enum jit_error_e {
JIT_ERROR_OK, /* Packet ok to be sent */
JIT_ERROR_TOO_LATE, /* Too late to send this packet */
JIT_ERROR_TOO_EARLY, /* Too early to queue this packet */
JIT_ERROR_FULL, /* Downlink queue is full */
JIT_ERROR_EMPTY, /* Downlink queue is empty */
JIT_ERROR_COLLISION_PACKET, /* A packet is already enqueued for this timeframe */
JIT_ERROR_COLLISION_BEACON, /* A beacon is planned for this timeframe */
JIT_ERROR_TX_FREQ, /* The required frequency for downlink is not supported */
JIT_ERROR_TX_POWER, /* The required power for downlink is not supported */
JIT_ERROR_GPS_UNLOCKED, /* GPS timestamp could not be used as GPS is unlocked */
JIT_ERROR_INVALID /* Packet is invalid */
};
struct jit_node_s {
/* API fields */
struct lgw_pkt_tx_s pkt; /* TX packet */
enum jit_pkt_type_e pkt_type; /* Packet type: Downlink, Beacon... */
/* Internal fields */
uint32_t pre_delay; /* Amount of time before packet timestamp to be reserved */
uint32_t post_delay; /* Amount of time after packet timestamp to be reserved (time on air) */
};
struct jit_queue_s {
uint8_t num_pkt; /* Total number of packets in the queue (downlinks, beacons...) */
uint8_t num_beacon; /* Number of beacons in the queue */
struct jit_node_s nodes[JIT_QUEUE_MAX]; /* Nodes/packets array in the queue */
};
/* -------------------------------------------------------------------------- */
/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
/**
@brief Check if a JiT queue is full.
@param queue[in] Just in Time queue to be checked.
@return true if queue is full, false otherwise.
*/
bool jit_queue_is_full(struct jit_queue_s *queue);
/**
@brief Check if a JiT queue is empty.
@param queue[in] Just in Time queue to be checked.
@return true if queue is empty, false otherwise.
*/
bool jit_queue_is_empty(struct jit_queue_s *queue);
/**
@brief Initialize a Just in Time queue.
@param queue[in] Just in Time queue to be initialized. Memory should have been allocated already.
This function is used to reset every elements in the allocated queue.
*/
void jit_queue_init(struct jit_queue_s *queue);
/**
@brief Add a packet in a Just-in-Time queue
@param queue[in/out] Just in Time queue in which the packet should be inserted
@param time_us[in] Current concentrator time
@param packet[in] Packet to be queued in JiT queue
@param pkt_type[in] Type of packet to be queued: Downlink, Beacon
@return success if the function was able to queue the packet
This function is typically used when a packet is received from server for downlink.
It will check if packet can be queued, with several criterias. Once the packet is queued, it has to be
sent over the air. So all checks should happen before the packet being actually in the queue.
*/
enum jit_error_e jit_enqueue(struct jit_queue_s *queue, uint32_t time_us, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e pkt_type);
/**
@brief Dequeue a packet from a Just-in-Time queue
@param queue[in/out] Just in Time queue from which the packet should be removed
@param index[in] in the queue where to get the packet to be removed
@param packet[out] that was at index
@param pkt_type[out] Type of packet dequeued: Downlink, Beacon
@return success if the function was able to dequeue the packet
This function is typically used when a packet is about to be placed on concentrator buffer for TX.
The index is generally got using the jit_peek function.
*/
enum jit_error_e jit_dequeue(struct jit_queue_s *queue, int index, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e *pkt_type);
/**
@brief Check if there is a packet soon to be sent from the JiT queue.
@param queue[in] Just in Time queue to parse for peeking a packet
@param time_us[in] Current concentrator time
@param pkt_idx[out] Packet index which is soon to be dequeued.
@return success if the function was able to parse the queue. pkt_idx is set to -1 if no packet found.
This function is typically used to check in JiT queue if there is a packet soon to be sent.
It search the packet with the highest priority in queue, and check if its timestamp is near
enough the current concentrator time.
*/
enum jit_error_e jit_peek(struct jit_queue_s *queue, uint32_t time_us, int *pkt_idx);
/**
@brief Debug function to print the queue's content on console
@param queue[in] Just in Time queue to be displayed
@param show_all[in] Indicates if empty nodes have to be displayed or not
*/
void jit_print_queue(struct jit_queue_s *queue, bool show_all, int debug_level);
#endif
/* --- EOF ------------------------------------------------------------------ */

View file

@ -0,0 +1,39 @@
/*
/ _____) _ | |
( (____ _____ ____ _| |_ _____ ____| |__
\____ \| ___ | (_ _) ___ |/ ___) _ \
_____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
(C)2019 Semtech
Description:
LoRa concentrator : Packet Forwarder trace helpers
License: Revised BSD License, see LICENSE.TXT file include in the project
*/
#ifndef _LORA_PKTFWD_TRACE_H
#define _LORA_PKTFWD_TRACE_H
#define DEBUG_PKT_FWD 0
#define DEBUG_JIT 0
#define DEBUG_JIT_ERROR 1
#define DEBUG_TIMERSYNC 0
#define DEBUG_BEACON 0
#define DEBUG_LOG 1
#define MSG(args...) printf(args) /* message that is destined to the user */
#define MSG_DEBUG(FLAG, fmt, ...) \
do { \
if (FLAG) \
fprintf(stdout, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__); \
} while (0)
#define MSG_PRINTF(FLAG, fmt, ...) \
do { \
if (FLAG) \
fprintf(stdout, fmt, ##__VA_ARGS__); \
} while (0)
#endif
/* --- EOF ------------------------------------------------------------------ */

253
packet_forwarder/readme.md Normal file
View file

@ -0,0 +1,253 @@
/ _____) _ | |
( (____ _____ ____ _| |_ _____ ____| |__
\____ \| ___ | (_ _) ___ |/ ___) _ \
_____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
(C)2019 Semtech
SX1302 packet forwarder
=======================
## 1. Introduction
The packet forwarder is a program running on the host of a Lora gateway that
forwards RF packets receive by the concentrator to a server through a IP/UDP
link, and emits RF packets that are sent by the server. It can also emit a
network-wide GPS-synchronous beacon signal used for coordinating all nodes of
the network.
To learn more about the network protocol between the gateway and the server,
please read the PROTOCOL.TXT document.
## 2. System schematic and definitions
((( Y )))
|
|
+- -|- - - - - - - - - - - - -+ xxxxxxxxxxxx +--------+
|+--+-----------+ +------+| xx x x xxx | |
|| | | || xx Internet xx | |
|| Concentrator |<----+ Host |<------xx or xx-------->| |
|| | SPI | || xx Intranet xx | Server |
|+--------------+ +------+| xxxx x xxxx | |
| ^ ^ | xxxxxxxx | |
| | PPS +-----+ NMEA | | | |
| +------| GPS |-------+ | +--------+
| +-----+ |
| |
| Gateway |
+- - - - - - - - - - - - - - -+
Concentrator: radio RX/TX board, based on Semtech multichannel modems (SX130x),
transceivers (SX135x) and/or low-power stand-alone modems (SX127x).
Host: embedded computer on which the packet forwarder is run. Drives the
concentrator through a SPI link.
Gateway: a device composed of at least one radio concentrator, a host, some
network connection to the internet or a private network (Ethernet, 3G, Wifi,
microwave link), and optionally a GPS receiver for synchronization.
Server: an abstract computer that will process the RF packets received and
forwarded by the gateway, and issue RF packets in response that the gateway
will have to emit.
## 3. Dependencies
This program uses the Parson library (http://kgabis.github.com/parson/) by
Krzysztof Gabis for JSON parsing.
Many thanks to him for that very practical and well written library.
This program is statically linked with the libloragw Lora concentrator library.
It was tested with v1.3.0 of the library but should work with any later
version provided the API is v1 or a later backward-compatible API.
Data structures of the received packets are accessed by name (ie. not at a
binary level) so new functionalities can be added to the API without affecting
that program at all.
This program follows the v1.3 version of the gateway-to-server protocol.
The last dependency is the hardware concentrator (based on FPGA or SX130x
chips) that must be matched with the proper version of the HAL.
## 4. Usage
* Pick the global_conf.json file that fit with your platform, region and feature
need.
* Update the JSON configuration (global and local) files, as explained below.
* Run:
./lora_pkt_fwd -c global_conf.json
To stop the application, press Ctrl+C.
Unless it is manually stopped or encounter a critical error, the program will
run forever.
The way the program takes configuration files into account is the following:
* if a specific configuration file is given as program argument, use the
one specified.
* if there is a global_conf.json parse it.
The global configuration file should be exactly the same throughout your
network, contain all global parameters (parameters for "sensor" radio
channels) and preferably default "safe" values for parameters that are
specific for each gateway (eg. specify a default MAC address).
In each configuration file, the program looks for a JSON object named
"SX130x_conf" that should contain the parameters for the Lora concentrator
board (RF channels definition, modem parameters, etc) and another JSON object
called "gateway_conf" that should contain the gateway parameters (gateway MAC
address, IP address of the server, keep-alive time, etc).
To learn more about the JSON configuration format, read the provided JSON
files and the libloragw API documentation.
Every X seconds (parameter settable in the configuration files) the program
display statistics on the RF packets received and sent, and the network
datagrams received and sent.
The program also send some statistics to the server in JSON format.
## 5. "Just-In-Time" downlink scheduling
The LoRa concentrator can have only one TX packet programmed for departure at a
time. The actual departure of a downlink packet will be done based on its
timestamp, when the concentrator internal counter reaches timestamps value.
The departure of a beacon will be done based on a GPS PPS event.
It may happen that, due to network variable latency, the gateway receives one
or many downlink packets from the server while a TX is already programmed in the
concentrator. The packet forwarder has to store and order incoming downlink
packets (in a queue), so that they can all be programmed in the concentrator at
the proper time and sent over the air.
Possible failures that may occur and that have to be reported to the server are:
- It is too early or too late to send a given packet
- A packet collides with another packet already queued
- A packet collides with a beacon
- TX RF parameters (frequency, power) are not supported by gateway
- Gateways GPS is unlocked, so cannot process Class B downlink
It is called "Just-in-Time" (JiT) scheduling, because the packet forwarder will
program a downlink or a beacon packet in the concentrator just before it has to
be sent over the air.
Another benefit of JiT is to optimize the gateway downlink capacity by avoiding
to keep the concentrator TX buffer busy for too long.
In order to achieve "Just-in-Time" scheduling, the following software elements
have been added:
- A JiT queue, with associated enqueue/peek/dequeue functions and packet
acceptance criterias. It is where downlink packets are stored, waiting to be
sent.
- A JiT thread, which regularly checks if there is a packet in the JiT queue
ready to be programmed in the concentrator, based on current concentrator
internal time.
### 5.1. Concentrator vs GPS time synchronization
There are 2 cases for which we need to convert a GPS time to concentrator
counter:
- Class B downlink: when the “time” field of JSON “txpk” is filled instead
of the “tmst” field, we need to be able to determine if the packet can be
queued in JiT queue or not, based on its corresponding concentrator
counter value.
Note: even if a Class-B downlink is given with a GPS timestamp, the
concentrator TX mode is configured as “TIMESTAMP”, and not “ON_GPS”. So
at the end, it is the counter value which will be used for transmission.
- Beacons: beacons transmission time is based on GPS clock, and the
concentrator TX mode is configured as “ON_GPS” for accurate beacon
transmission on GPS PPS event. In this case, the concentrator does not
need the packet counter to be set. But, as the JiT thread decides if a
packet has to be peeked or not, based on its concentrator counter, we need
to have the beacon packet counter set (see next chapter for more details
on JiT scheduling).
We also need to convert a SX1302 counter value to GPS UTC time when we receive
an uplink, in order to fill the “time” field of JSON “rxpk” structure.
### 5.2. TX scheduling
The JiT queue implemented is a static array of nodes, where each node contains:
- the downlink packet, with its type (beacon, downlink class A, B or C)
- a “pre delay” which depends on packet type (BEACON_GUARD, TX_START_DELAY…)
- a “post delay” which depends on packet type (“time on air” of this packet
computed based on its size, datarate and coderate, or BEACON_RESERVED)
Several functions are implemented to manipulate this queue or get info from it:
- init: initialize array with default values
- is full / is empty: gives queue status
- enqueue: checks if the given packet can be queued or not, based on several
criterias
- peek: checks if the queue contains a packet that must be passed
immediately to the concentrator for transmission and returns corresponding
index if any.
- dequeue: actually removes from the queue the packet at index given by peek
function
The queue is always kept sorted on ascending timestamp order.
The JiT thread will regularly check in the JiT queue if there is a packet to be
sent soon. If a packet is matching, it is dequeued and programmed in the
concentrator TX buffer.
### 5.3. Fine tuning parameters
There are few parameters of the JiT queue which could be tweaked to adapt to
different system constraints.
- inc/jitqueue.h:
JIT_QUEUE_MAX: The maximum number of nodes in the queue.
- src/jitqueue.c:
TX_JIT_DELAY: The number of milliseconds a packet is programmed in the
concentrator TX buffer before its actual departure time.
TX_MARGIN_DELAY: Packet collision check margin
6. License
-----------
Copyright (C) 2019, SEMTECH S.A.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Semtech corporation nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
6. License for Parson library
------------------------------
Parson ( http://kgabis.github.com/parson/ )
Copyright (C) 2012 Krzysztof Gabis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*EOF*

View file

@ -0,0 +1,458 @@
/*
/ _____) _ | |
( (____ _____ ____ _| |_ _____ ____| |__
\____ \| ___ | (_ _) ___ |/ ___) _ \
_____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
(C)2019 Semtech
Description:
LoRa concentrator : Just In Time TX scheduling queue
License: Revised BSD License, see LICENSE.TXT file include in the project
*/
/* -------------------------------------------------------------------------- */
/* --- DEPENDANCIES --------------------------------------------------------- */
#define _GNU_SOURCE /* needed for qsort_r to be defined */
#include <stdlib.h> /* qsort_r */
#include <stdio.h> /* printf, fprintf, snprintf, fopen, fputs */
#include <string.h> /* memset, memcpy */
#include <pthread.h>
#include <assert.h>
#include <math.h>
#include "trace.h"
#include "jitqueue.h"
/* -------------------------------------------------------------------------- */
/* --- PRIVATE MACROS ------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
/* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */
#define TX_START_DELAY 1500 /* microseconds */
/* TODO: get this value from HAL? */
#define TX_MARGIN_DELAY 1000 /* Packet overlap margin in microseconds */
/* TODO: How much margin should we take? */
#define TX_JIT_DELAY 30000 /* Pre-delay to program packet for TX in microseconds */
#define TX_MAX_ADVANCE_DELAY ((JIT_NUM_BEACON_IN_QUEUE + 1) * 128 * 1E6) /* Maximum advance delay accepted for a TX packet, compared to current time */
#define BEACON_GUARD 3000000 /* Interval where no ping slot can be placed,
to ensure beacon can be sent */
#define BEACON_RESERVED 2120000 /* Time on air of the beacon, with some margin */
/* -------------------------------------------------------------------------- */
/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */
static pthread_mutex_t mx_jit_queue = PTHREAD_MUTEX_INITIALIZER; /* control access to JIT queue */
/* -------------------------------------------------------------------------- */
/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
/* -------------------------------------------------------------------------- */
/* --- PUBLIC FUNCTIONS DEFINITION ----------------------------------------- */
bool jit_queue_is_full(struct jit_queue_s *queue) {
bool result;
pthread_mutex_lock(&mx_jit_queue);
result = (queue->num_pkt == JIT_QUEUE_MAX)?true:false;
pthread_mutex_unlock(&mx_jit_queue);
return result;
}
bool jit_queue_is_empty(struct jit_queue_s *queue) {
bool result;
pthread_mutex_lock(&mx_jit_queue);
result = (queue->num_pkt == 0)?true:false;
pthread_mutex_unlock(&mx_jit_queue);
return result;
}
void jit_queue_init(struct jit_queue_s *queue) {
int i;
pthread_mutex_lock(&mx_jit_queue);
memset(queue, 0, sizeof(*queue));
for (i=0; i<JIT_QUEUE_MAX; i++) {
queue->nodes[i].pre_delay = 0;
queue->nodes[i].post_delay = 0;
}
pthread_mutex_unlock(&mx_jit_queue);
}
int compare(const void *a, const void *b, void *arg)
{
struct jit_node_s *p = (struct jit_node_s *)a;
struct jit_node_s *q = (struct jit_node_s *)b;
int *counter = (int *)arg;
int p_count, q_count;
p_count = p->pkt.count_us;
q_count = q->pkt.count_us;
if (p_count > q_count)
*counter = *counter + 1;
return p_count - q_count;
}
void jit_sort_queue(struct jit_queue_s *queue) {
int counter = 0;
if (queue->num_pkt == 0) {
return;
}
MSG_DEBUG(DEBUG_JIT, "sorting queue in ascending order packet timestamp - queue size:%u\n", queue->num_pkt);
qsort_r(queue->nodes, queue->num_pkt, sizeof(queue->nodes[0]), compare, &counter);
MSG_DEBUG(DEBUG_JIT, "sorting queue done - swapped:%d\n", counter);
}
bool jit_collision_test(uint32_t p1_count_us, uint32_t p1_pre_delay, uint32_t p1_post_delay, uint32_t p2_count_us, uint32_t p2_pre_delay, uint32_t p2_post_delay) {
if (((p1_count_us - p2_count_us) <= (p1_pre_delay + p2_post_delay + TX_MARGIN_DELAY)) ||
((p2_count_us - p1_count_us) <= (p2_pre_delay + p1_post_delay + TX_MARGIN_DELAY))) {
return true;
} else {
return false;
}
}
enum jit_error_e jit_enqueue(struct jit_queue_s *queue, uint32_t time_us, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e pkt_type) {
int i = 0;
uint32_t packet_post_delay = 0;
uint32_t packet_pre_delay = 0;
uint32_t target_pre_delay = 0;
enum jit_error_e err_collision;
uint32_t asap_count_us;
MSG_DEBUG(DEBUG_JIT, "Current concentrator time is %u, pkt_type=%d\n", time_us, pkt_type);
if (packet == NULL) {
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: invalid parameter\n");
return JIT_ERROR_INVALID;
}
if (jit_queue_is_full(queue)) {
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: cannot enqueue packet, JIT queue is full\n");
return JIT_ERROR_FULL;
}
/* Compute packet pre/post delays depending on packet's type */
switch (pkt_type) {
case JIT_PKT_TYPE_DOWNLINK_CLASS_A:
case JIT_PKT_TYPE_DOWNLINK_CLASS_B:
case JIT_PKT_TYPE_DOWNLINK_CLASS_C:
packet_pre_delay = TX_START_DELAY + TX_JIT_DELAY;
packet_post_delay = lgw_time_on_air(packet) * 1000UL; /* in us */
break;
case JIT_PKT_TYPE_BEACON:
/* As defined in LoRaWAN spec */
packet_pre_delay = TX_START_DELAY + BEACON_GUARD + TX_JIT_DELAY;
packet_post_delay = BEACON_RESERVED;
break;
default:
break;
}
pthread_mutex_lock(&mx_jit_queue);
/* An immediate downlink becomes a timestamped downlink "ASAP" */
/* Set the packet count_us to the first available slot */
if (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_C) {
/* change tx_mode to timestamped */
packet->tx_mode = TIMESTAMPED;
/* Search for the ASAP timestamp to be given to the packet */
asap_count_us = time_us + 1E6; /* TODO: Take 1 second margin, to be refined */
if (queue->num_pkt == 0) {
/* If the jit queue is empty, we can insert this packet */
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink, first in JiT queue (count_us=%u)\n", asap_count_us);
} else {
/* Else we can try to insert it:
- ASAP meaning NOW + MARGIN
- at the last index of the queue
- between 2 downlinks in the queue
*/
/* First, try if the ASAP time collides with an already enqueued downlink */
for (i=0; i<queue->num_pkt; i++) {
if (jit_collision_test(asap_count_us, packet_pre_delay, packet_post_delay, queue->nodes[i].pkt.count_us, queue->nodes[i].pre_delay, queue->nodes[i].post_delay) == true) {
MSG_DEBUG(DEBUG_JIT, "DEBUG: cannot insert IMMEDIATE downlink at count_us=%u, collides with %u (index=%d)\n", asap_count_us, queue->nodes[i].pkt.count_us, i);
break;
}
}
if (i == queue->num_pkt) {
/* No collision with ASAP time, we can insert it */
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink ASAP at %u (no collision)\n", asap_count_us);
} else {
/* Search for the best slot then */
for (i=0; i<queue->num_pkt; i++) {
asap_count_us = queue->nodes[i].pkt.count_us + queue->nodes[i].post_delay + packet_pre_delay + TX_JIT_DELAY + TX_MARGIN_DELAY;
if (i == (queue->num_pkt - 1)) {
/* Last packet index, we can insert after this one */
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink, last in JiT queue (count_us=%u)\n", asap_count_us);
} else {
/* Check if packet can be inserted between this index and the next one */
MSG_DEBUG(DEBUG_JIT, "DEBUG: try to insert IMMEDIATE downlink (count_us=%u) between index %d and index %d?\n", asap_count_us, i, i+1);
if (jit_collision_test(asap_count_us, packet_pre_delay, packet_post_delay, queue->nodes[i+1].pkt.count_us, queue->nodes[i+1].pre_delay, queue->nodes[i+1].post_delay) == true) {
MSG_DEBUG(DEBUG_JIT, "DEBUG: failed to insert IMMEDIATE downlink (count_us=%u), continue...\n", asap_count_us);
continue;
} else {
MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink (count_us=%u)\n", asap_count_us);
break;
}
}
}
}
}
/* Set packet with ASAP timestamp */
packet->count_us = asap_count_us;
}
/* Check criteria_1: is it already too late to send this packet ?
* The packet should arrive at least at (tmst - TX_START_DELAY) to be programmed into concentrator
* Note: - Also add some margin, to be checked how much is needed, if needed
* - Valid for both Downlinks and Beacon packets
*
* Warning: unsigned arithmetic (handle roll-over)
* t_packet < t_current + TX_START_DELAY + MARGIN
*/
if ((packet->count_us - time_us) <= (TX_START_DELAY + TX_MARGIN_DELAY + TX_JIT_DELAY)) {
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet REJECTED, already too late to send it (current=%u, packet=%u, type=%d)\n", time_us, packet->count_us, pkt_type);
pthread_mutex_unlock(&mx_jit_queue);
return JIT_ERROR_TOO_LATE;
}
/* Check criteria_2: Does packet timestamp seem plausible compared to current time
* We do not expect the server to program a downlink too early compared to current time
* Class A: downlink has to be sent in a 1s or 2s time window after RX
* Class B: downlink has to occur in a 128s time window
* Class C: no check needed, departure time has been calculated previously
* So let's define a safe delay above which we can say that the packet is out of bound: TX_MAX_ADVANCE_DELAY
* Note: - Valid for Downlinks only, not for Beacon packets
*
* Warning: unsigned arithmetic (handle roll-over)
t_packet > t_current + TX_MAX_ADVANCE_DELAY
*/
if ((pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_A) || (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_B)) {
if ((packet->count_us - time_us) > TX_MAX_ADVANCE_DELAY) {
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet REJECTED, timestamp seems wrong, too much in advance (current=%u, packet=%u, type=%d)\n", time_us, packet->count_us, pkt_type);
pthread_mutex_unlock(&mx_jit_queue);
return JIT_ERROR_TOO_EARLY;
}
}
/* Check criteria_3: does this new packet overlap with a packet already enqueued ?
* Note: - need to take into account packet's pre_delay and post_delay of each packet
* - Valid for both Downlinks and beacon packets
* - Beacon guard can be ignored if we try to queue a Class A downlink
*/
for (i=0; i<queue->num_pkt; i++) {
/* We ignore Beacon Guard for Class A/C downlinks */
if (((pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_A) || (pkt_type == JIT_PKT_TYPE_DOWNLINK_CLASS_C)) && (queue->nodes[i].pkt_type == JIT_PKT_TYPE_BEACON)) {
target_pre_delay = TX_START_DELAY;
} else {
target_pre_delay = queue->nodes[i].pre_delay;
}
/* Check if there is a collision
* Warning: unsigned arithmetic (handle roll-over)
* t_packet_new - pre_delay_packet_new < t_packet_prev + post_delay_packet_prev (OVERLAP on post delay)
* t_packet_new + post_delay_packet_new > t_packet_prev - pre_delay_packet_prev (OVERLAP on pre delay)
*/
if (jit_collision_test(packet->count_us, packet_pre_delay, packet_post_delay, queue->nodes[i].pkt.count_us, target_pre_delay, queue->nodes[i].post_delay) == true) {
switch (queue->nodes[i].pkt_type) {
case JIT_PKT_TYPE_DOWNLINK_CLASS_A:
case JIT_PKT_TYPE_DOWNLINK_CLASS_B:
case JIT_PKT_TYPE_DOWNLINK_CLASS_C:
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet (type=%d) REJECTED, collision with packet already programmed at %u (%u)\n", pkt_type, queue->nodes[i].pkt.count_us, packet->count_us);
err_collision = JIT_ERROR_COLLISION_PACKET;
break;
case JIT_PKT_TYPE_BEACON:
if (pkt_type != JIT_PKT_TYPE_BEACON) {
/* do not overload logs for beacon/beacon collision, as it is expected to happen with beacon pre-scheduling algorith used */
MSG_DEBUG(DEBUG_JIT_ERROR, "ERROR: Packet (type=%d) REJECTED, collision with beacon already programmed at %u (%u)\n", pkt_type, queue->nodes[i].pkt.count_us, packet->count_us);
}
err_collision = JIT_ERROR_COLLISION_BEACON;
break;
default:
MSG("ERROR: Unknown packet type, should not occur, BUG?\n");
assert(0);
break;
}
pthread_mutex_unlock(&mx_jit_queue);
return err_collision;
}
}
/* Finally enqueue it */
/* Insert packet at the end of the queue */
memcpy(&(queue->nodes[queue->num_pkt].pkt), packet, sizeof(struct lgw_pkt_tx_s));
queue->nodes[queue->num_pkt].pre_delay = packet_pre_delay;
queue->nodes[queue->num_pkt].post_delay = packet_post_delay;
queue->nodes[queue->num_pkt].pkt_type = pkt_type;
if (pkt_type == JIT_PKT_TYPE_BEACON) {
queue->num_beacon++;
}
queue->num_pkt++;
/* Sort the queue in ascending order of packet timestamp */
jit_sort_queue(queue);
/* Done */
pthread_mutex_unlock(&mx_jit_queue);
jit_print_queue(queue, false, DEBUG_JIT);
MSG_DEBUG(DEBUG_JIT, "enqueued packet with count_us=%u (size=%u bytes, toa=%u us, type=%u)\n", packet->count_us, packet->size, packet_post_delay, pkt_type);
return JIT_ERROR_OK;
}
enum jit_error_e jit_dequeue(struct jit_queue_s *queue, int index, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e *pkt_type) {
if (packet == NULL) {
MSG("ERROR: invalid parameter\n");
return JIT_ERROR_INVALID;
}
if ((index < 0) || (index >= JIT_QUEUE_MAX)) {
MSG("ERROR: invalid parameter\n");
return JIT_ERROR_INVALID;
}
if (jit_queue_is_empty(queue)) {
MSG("ERROR: cannot dequeue packet, JIT queue is empty\n");
return JIT_ERROR_EMPTY;
}
pthread_mutex_lock(&mx_jit_queue);
/* Dequeue requested packet */
memcpy(packet, &(queue->nodes[index].pkt), sizeof(struct lgw_pkt_tx_s));
queue->num_pkt--;
*pkt_type = queue->nodes[index].pkt_type;
if (*pkt_type == JIT_PKT_TYPE_BEACON) {
queue->num_beacon--;
MSG_DEBUG(DEBUG_BEACON, "--- Beacon dequeued ---\n");
}
/* Replace dequeued packet with last packet of the queue */
memcpy(&(queue->nodes[index]), &(queue->nodes[queue->num_pkt]), sizeof(struct jit_node_s));
memset(&(queue->nodes[queue->num_pkt]), 0, sizeof(struct jit_node_s));
/* Sort queue in ascending order of packet timestamp */
jit_sort_queue(queue);
/* Done */
pthread_mutex_unlock(&mx_jit_queue);
jit_print_queue(queue, false, DEBUG_JIT);
MSG_DEBUG(DEBUG_JIT, "dequeued packet with count_us=%u from index %d\n", packet->count_us, index);
return JIT_ERROR_OK;
}
enum jit_error_e jit_peek(struct jit_queue_s *queue, uint32_t time_us, int *pkt_idx) {
/* Return index of node containing a packet inline with given time */
int i = 0;
int idx_highest_priority = -1;
if (pkt_idx == NULL) {
MSG("ERROR: invalid parameter\n");
return JIT_ERROR_INVALID;
}
if (jit_queue_is_empty(queue)) {
return JIT_ERROR_EMPTY;
}
pthread_mutex_lock(&mx_jit_queue);
/* Search for highest priority packet to be sent */
for (i=0; i<queue->num_pkt; i++) {
/* First check if that packet is outdated:
* If a packet seems too much in advance, and was not rejected at enqueue time,
* it means that we missed it for peeking, we need to drop it
*
* Warning: unsigned arithmetic
* t_packet > t_current + TX_MAX_ADVANCE_DELAY
*/
if ((queue->nodes[i].pkt.count_us - time_us) >= TX_MAX_ADVANCE_DELAY) {
/* We drop the packet to avoid lock-up */
queue->num_pkt--;
if (queue->nodes[i].pkt_type == JIT_PKT_TYPE_BEACON) {
queue->num_beacon--;
MSG("WARNING: --- Beacon dropped (current_time=%u, packet_time=%u) ---\n", time_us, queue->nodes[i].pkt.count_us);
} else {
MSG("WARNING: --- Packet dropped (current_time=%u, packet_time=%u) ---\n", time_us, queue->nodes[i].pkt.count_us);
}
/* Replace dropped packet with last packet of the queue */
memcpy(&(queue->nodes[i]), &(queue->nodes[queue->num_pkt]), sizeof(struct jit_node_s));
memset(&(queue->nodes[queue->num_pkt]), 0, sizeof(struct jit_node_s));
/* Sort queue in ascending order of packet timestamp */
jit_sort_queue(queue);
/* restart loop after purge to find packet to be sent */
i = 0;
continue;
}
/* Then look for highest priority packet to be sent:
* Warning: unsigned arithmetic (handle roll-over)
* t_packet < t_highest
*/
if ((idx_highest_priority == -1) || (((queue->nodes[i].pkt.count_us - time_us) < (queue->nodes[idx_highest_priority].pkt.count_us - time_us)))) {
idx_highest_priority = i;
}
}
/* Peek criteria 1: look for a packet to be sent in next TX_JIT_DELAY ms timeframe
* Warning: unsigned arithmetic (handle roll-over)
* t_packet < t_current + TX_JIT_DELAY
*/
if ((queue->nodes[idx_highest_priority].pkt.count_us - time_us) < TX_JIT_DELAY) {
*pkt_idx = idx_highest_priority;
MSG_DEBUG(DEBUG_JIT, "peek packet with count_us=%u at index %d\n",
queue->nodes[idx_highest_priority].pkt.count_us, idx_highest_priority);
} else {
*pkt_idx = -1;
}
pthread_mutex_unlock(&mx_jit_queue);
return JIT_ERROR_OK;
}
void jit_print_queue(struct jit_queue_s *queue, bool show_all, int debug_level) {
int i = 0;
int loop_end;
if (jit_queue_is_empty(queue)) {
MSG_DEBUG(debug_level, "INFO: [jit] queue is empty\n");
} else {
pthread_mutex_lock(&mx_jit_queue);
MSG_DEBUG(debug_level, "INFO: [jit] queue contains %d packets:\n", queue->num_pkt);
MSG_DEBUG(debug_level, "INFO: [jit] queue contains %d beacons:\n", queue->num_beacon);
loop_end = (show_all == true) ? JIT_QUEUE_MAX : queue->num_pkt;
for (i=0; i<loop_end; i++) {
MSG_DEBUG(debug_level, " - node[%d]: count_us=%u - type=%d\n",
i,
queue->nodes[i].pkt.count_us,
queue->nodes[i].pkt_type);
}
pthread_mutex_unlock(&mx_jit_queue);
}
}

File diff suppressed because it is too large Load diff