v1.0.0
* Initial release for SX1302 CoreCell Reference Design.
This commit is contained in:
parent
c7a5171a47
commit
4c61c5d48e
79 changed files with 30157 additions and 0 deletions
93
packet_forwarder/Makefile
Normal file
93
packet_forwarder/Makefile
Normal 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
|
||||
477
packet_forwarder/PROTOCOL.md
Normal file
477
packet_forwarder/PROTOCOL.md
Normal 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.
|
||||
98
packet_forwarder/global_conf.json.sx1250
Normal file
98
packet_forwarder/global_conf.json.sx1250
Normal 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"
|
||||
}
|
||||
}
|
||||
98
packet_forwarder/global_conf.json.sx1257
Normal file
98
packet_forwarder/global_conf.json.sx1257
Normal 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"
|
||||
}
|
||||
}
|
||||
93
packet_forwarder/global_conf.json.us915.full_duplex
Normal file
93
packet_forwarder/global_conf.json.us915.full_duplex
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
154
packet_forwarder/inc/jitqueue.h
Normal file
154
packet_forwarder/inc/jitqueue.h
Normal 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 ------------------------------------------------------------------ */
|
||||
39
packet_forwarder/inc/trace.h
Normal file
39
packet_forwarder/inc/trace.h
Normal 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
253
packet_forwarder/readme.md
Normal 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 timestamp’s 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
|
||||
- Gateway’s 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
|
||||
criteria’s
|
||||
- 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*
|
||||
458
packet_forwarder/src/jitqueue.c
Normal file
458
packet_forwarder/src/jitqueue.c
Normal 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);
|
||||
}
|
||||
}
|
||||
3226
packet_forwarder/src/lora_pkt_fwd.c
Normal file
3226
packet_forwarder/src/lora_pkt_fwd.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue