summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--LICENSE49
-rw-r--r--Makefile22
-rw-r--r--PROTOCOL.TXT461
-rw-r--r--VERSION1
-rwxr-xr-xcompile.sh75
-rw-r--r--lora_pkt_fwd/Makefile67
-rw-r--r--lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic188
-rw-r--r--lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon201
-rw-r--r--lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps194
-rw-r--r--lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic228
-rw-r--r--lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon241
-rw-r--r--lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps234
-rw-r--r--lora_pkt_fwd/cfg/global_conf.json.US902.basic104
-rw-r--r--lora_pkt_fwd/cfg/global_conf.json.US902.beacon119
-rw-r--r--lora_pkt_fwd/cfg/global_conf.json.US902.gps110
-rw-r--r--lora_pkt_fwd/global_conf.json228
-rw-r--r--lora_pkt_fwd/inc/base64.h62
-rw-r--r--lora_pkt_fwd/inc/jitqueue.h156
-rw-r--r--lora_pkt_fwd/inc/parson.h222
-rw-r--r--lora_pkt_fwd/inc/timersync.h32
-rw-r--r--lora_pkt_fwd/inc/trace.h37
-rw-r--r--lora_pkt_fwd/local_conf.json7
-rw-r--r--lora_pkt_fwd/readme.md328
-rw-r--r--lora_pkt_fwd/src/base64.c308
-rw-r--r--lora_pkt_fwd/src/jitqueue.c465
-rw-r--r--lora_pkt_fwd/src/lora_pkt_fwd.c2889
-rw-r--r--lora_pkt_fwd/src/parson.c1765
-rw-r--r--lora_pkt_fwd/src/timersync.c146
-rwxr-xr-xlora_pkt_fwd/update_gwid.sh31
-rw-r--r--readme.md247
-rw-r--r--util_ack/Makefile33
-rw-r--r--util_ack/readme.md65
-rw-r--r--util_ack/src/util_ack.c193
-rw-r--r--util_sink/Makefile33
-rw-r--r--util_sink/readme.md62
-rw-r--r--util_sink/src/util_sink.c125
-rw-r--r--util_tx_test/Makefile36
-rw-r--r--util_tx_test/inc/base64.h62
-rw-r--r--util_tx_test/readme.md77
-rw-r--r--util_tx_test/src/base64.c308
-rw-r--r--util_tx_test/src/util_tx_test.c502
42 files changed, 10716 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea0baf2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.o
+*.swp
+*.bak
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e646fe9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,49 @@
+Copyright (C) 2013, 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.
+
+
+--- For the 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,
+ITNESS 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.
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..aee59c7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,22 @@
+### Environment constants
+
+LGW_PATH ?= ../../lora_gateway/libloragw
+ARCH ?=
+CROSS_COMPILE ?=
+export
+
+### general build targets
+
+all:
+ $(MAKE) all -e -C lora_pkt_fwd
+ $(MAKE) all -e -C util_ack
+ $(MAKE) all -e -C util_sink
+ $(MAKE) all -e -C util_tx_test
+
+clean:
+ $(MAKE) clean -e -C lora_pkt_fwd
+ $(MAKE) clean -e -C util_ack
+ $(MAKE) clean -e -C util_sink
+ $(MAKE) clean -e -C util_tx_test
+
+### EOF
diff --git a/PROTOCOL.TXT b/PROTOCOL.TXT
new file mode 100644
index 0000000..d479fbe
--- /dev/null
+++ b/PROTOCOL.TXT
@@ -0,0 +1,461 @@
+ ______ _
+ / _____) _ | |
+ ( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+ (______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Basic communication protocol between Lora gateway and 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)
+ 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 in dBm (signed integer, 1 dB precision)
+ lsnr | number | Lora SNR ratio in dB (signed float, 0.1 dB precision)
+ 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 & time)
+ tmst | number | Send packet on a certain timestamp value (will ignore time)
+ 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"
+}}
+```
+
+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 | Indication about success or type of failure that occured for downlink request.
+
+The possible values of "error" field are:
+
+ Value | Definition
+:-----------------:|---------------------------------------------------------------------
+ NONE | Packet has been programmed for downlink
+ 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
+ TX_POWER | Rejected because requested power is not supported by gateway
+ GPS_UNLOCKED | Rejected because GPS is unlocked, so GPS timestamp cannot be used
+
+Examples (white-spaces, indentation and newlines added for readability):
+
+``` json
+{"txpk_ack":{
+ "error":"COLLISION_PACKET"
+}}
+```
+
+7. Revisions
+-------------
+
+### 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.
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..1454f6e
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+4.0.1
diff --git a/compile.sh b/compile.sh
new file mode 100755
index 0000000..43c8b4f
--- /dev/null
+++ b/compile.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+
+LORA_GATEWAY_DRIVER_PATH=../lora_gateway
+
+TARGET_IP_ADDRESS=192.168.0.1
+TARGET_PATH=/home/pi/lora-net
+TARGET_USER=pi
+
+clean_all() {
+ make clean -C $LORA_GATEWAY_DRIVER_PATH
+ if [ $? != 0 ]
+ then
+ echo "ERROR: Failed to clean $LORA_GATEWAY_DRIVER_PATH"
+ exit 1
+ fi
+ make clean
+ if [ $? != 0 ]
+ then
+ exit 1
+ fi
+}
+
+build_all() {
+ make all -C $LORA_GATEWAY_DRIVER_PATH
+ if [ $? != 0 ]
+ then
+ echo "ERROR: Failed to compile $LORA_GATEWAY_DRIVER_PATH"
+ exit 1
+ fi
+ make all
+ if [ $? != 0 ]
+ then
+ exit 1
+ fi
+}
+
+install() {
+ scp ./lora_pkt_fwd/lora_pkt_fwd $TARGET_USER@$TARGET_IP_ADDRESS:$TARGET_PATH
+ if [ $? != 0 ]
+ then
+ echo "ERROR: Failed to install the packet forwarder"
+ echo " target info: $TARGET_IP_ADDRESS, $TARGET_USER, $TARGET_PATH"
+ exit 1
+ fi
+}
+
+case "$1" in
+ install)
+ install
+ ;;
+
+ clean)
+ clean_all
+ ;;
+
+ cleanall)
+ # clean and rebuild
+ clean_all
+ build_all
+ ;;
+
+ -h)
+ echo "Compile the complete gateway software (driver & packet forwarder)"
+ echo "Usage: $0 [clean/cleanall/install]"
+ exit 1
+ ;;
+
+ *)
+ # rebuild
+ build_all
+ ;;
+esac
+
+exit 0
+
diff --git a/lora_pkt_fwd/Makefile b/lora_pkt_fwd/Makefile
new file mode 100644
index 0000000..1330d62
--- /dev/null
+++ b/lora_pkt_fwd/Makefile
@@ -0,0 +1,67 @@
+### Application-specific constants
+
+APP_NAME := lora_pkt_fwd
+
+### Environment constants
+
+LGW_PATH ?= ../../lora_gateway/libloragw
+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.
+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
+LGW_INC += $(LGW_PATH)/inc/loragw_gps.h
+
+### Linking options
+
+LIBS := -lloragw -lrt -lpthread -lm
+
+### General build targets
+
+all: $(APP_NAME)
+
+clean:
+ rm -f $(OBJDIR)/*.o
+ rm -f $(APP_NAME)
+
+### 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)/parson.o $(OBJDIR)/base64.o $(OBJDIR)/jitqueue.o $(OBJDIR)/timersync.o
+ $(CC) -L$(LGW_PATH) $< $(OBJDIR)/parson.o $(OBJDIR)/base64.o $(OBJDIR)/jitqueue.o $(OBJDIR)/timersync.o -o $@ $(LIBS)
+
+### EOF
diff --git a/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic
new file mode 100644
index 0000000..7119eb1
--- /dev/null
+++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.basic
@@ -0,0 +1,188 @@
+{
+ "SX1301_conf": {
+ "lorawan_public": true,
+ "clksrc": 1, /* radio_1 provides clock to concentrator */
+ "antenna_gain": 0, /* antenna gain, in dBi */
+ "radio_0": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 867500000,
+ "rssi_offset": -166.0,
+ "tx_enable": true,
+ "tx_freq_min": 863000000,
+ "tx_freq_max": 870000000
+ },
+ "radio_1": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 868500000,
+ "rssi_offset": -166.0,
+ "tx_enable": false
+ },
+ "chan_multiSF_0": {
+ /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -400000
+ },
+ "chan_multiSF_1": {
+ /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000
+ },
+ "chan_multiSF_2": {
+ /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 0
+ },
+ "chan_multiSF_3": {
+ /* Lora MAC channel, 125kHz, all SF, 867.1 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -400000
+ },
+ "chan_multiSF_4": {
+ /* Lora MAC channel, 125kHz, all SF, 867.3 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -200000
+ },
+ "chan_multiSF_5": {
+ /* Lora MAC channel, 125kHz, all SF, 867.5 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 0
+ },
+ "chan_multiSF_6": {
+ /* Lora MAC channel, 125kHz, all SF, 867.7 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 200000
+ },
+ "chan_multiSF_7": {
+ /* Lora MAC channel, 125kHz, all SF, 867.9 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 400000
+ },
+ "chan_Lora_std": {
+ /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000,
+ "bandwidth": 250000,
+ "spread_factor": 7
+ },
+ "chan_FSK": {
+ /* FSK 50kbps channel, 868.8 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 300000,
+ "bandwidth": 125000,
+ "datarate": 50000
+ },
+ "tx_lut_0": {
+ /* TX gain table, index 0 */
+ "pa_gain": 0,
+ "mix_gain": 8,
+ "rf_power": -6,
+ "dig_gain": 0
+ },
+ "tx_lut_1": {
+ /* TX gain table, index 1 */
+ "pa_gain": 0,
+ "mix_gain": 10,
+ "rf_power": -3,
+ "dig_gain": 0
+ },
+ "tx_lut_2": {
+ /* TX gain table, index 2 */
+ "pa_gain": 0,
+ "mix_gain": 12,
+ "rf_power": 0,
+ "dig_gain": 0
+ },
+ "tx_lut_3": {
+ /* TX gain table, index 3 */
+ "pa_gain": 1,
+ "mix_gain": 8,
+ "rf_power": 3,
+ "dig_gain": 0
+ },
+ "tx_lut_4": {
+ /* TX gain table, index 4 */
+ "pa_gain": 1,
+ "mix_gain": 10,
+ "rf_power": 6,
+ "dig_gain": 0
+ },
+ "tx_lut_5": {
+ /* TX gain table, index 5 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 10,
+ "dig_gain": 0
+ },
+ "tx_lut_6": {
+ /* TX gain table, index 6 */
+ "pa_gain": 1,
+ "mix_gain": 13,
+ "rf_power": 11,
+ "dig_gain": 0
+ },
+ "tx_lut_7": {
+ /* TX gain table, index 7 */
+ "pa_gain": 2,
+ "mix_gain": 9,
+ "rf_power": 12,
+ "dig_gain": 0
+ },
+ "tx_lut_8": {
+ /* TX gain table, index 8 */
+ "pa_gain": 1,
+ "mix_gain": 15,
+ "rf_power": 13,
+ "dig_gain": 0
+ },
+ "tx_lut_9": {
+ /* TX gain table, index 9 */
+ "pa_gain": 2,
+ "mix_gain": 10,
+ "rf_power": 14,
+ "dig_gain": 0
+ },
+ "tx_lut_10": {
+ /* TX gain table, index 10 */
+ "pa_gain": 2,
+ "mix_gain": 11,
+ "rf_power": 16,
+ "dig_gain": 0
+ },
+ "tx_lut_11": {
+ /* TX gain table, index 11 */
+ "pa_gain": 3,
+ "mix_gain": 9,
+ "rf_power": 20,
+ "dig_gain": 0
+ }
+ },
+
+ "gateway_conf": {
+ "gateway_ID": "AA555A0000000000",
+ /* change with default server address/ports, or overwrite in local_conf.json */
+ "server_address": "localhost",
+ "serv_port_up": 1680,
+ "serv_port_down": 1680,
+ /* 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
+ }
+}
+
diff --git a/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon
new file mode 100644
index 0000000..c5796b5
--- /dev/null
+++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.beacon
@@ -0,0 +1,201 @@
+{
+ "SX1301_conf": {
+ "lorawan_public": true,
+ "clksrc": 1, /* radio_1 provides clock to concentrator */
+ "antenna_gain": 0, /* antenna gain, in dBi */
+ "radio_0": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 867500000,
+ "rssi_offset": -166.0,
+ "tx_enable": true,
+ "tx_freq_min": 863000000,
+ "tx_freq_max": 870000000
+ },
+ "radio_1": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 868500000,
+ "rssi_offset": -166.0,
+ "tx_enable": false
+ },
+ "chan_multiSF_0": {
+ /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -400000
+ },
+ "chan_multiSF_1": {
+ /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000
+ },
+ "chan_multiSF_2": {
+ /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 0
+ },
+ "chan_multiSF_3": {
+ /* Lora MAC channel, 125kHz, all SF, 867.1 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -400000
+ },
+ "chan_multiSF_4": {
+ /* Lora MAC channel, 125kHz, all SF, 867.3 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -200000
+ },
+ "chan_multiSF_5": {
+ /* Lora MAC channel, 125kHz, all SF, 867.5 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 0
+ },
+ "chan_multiSF_6": {
+ /* Lora MAC channel, 125kHz, all SF, 867.7 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 200000
+ },
+ "chan_multiSF_7": {
+ /* Lora MAC channel, 125kHz, all SF, 867.9 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 400000
+ },
+ "chan_Lora_std": {
+ /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000,
+ "bandwidth": 250000,
+ "spread_factor": 7
+ },
+ "chan_FSK": {
+ /* FSK 50kbps channel, 868.8 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 300000,
+ "bandwidth": 125000,
+ "datarate": 50000
+ },
+ "tx_lut_0": {
+ /* TX gain table, index 0 */
+ "pa_gain": 0,
+ "mix_gain": 8,
+ "rf_power": -6,
+ "dig_gain": 0
+ },
+ "tx_lut_1": {
+ /* TX gain table, index 1 */
+ "pa_gain": 0,
+ "mix_gain": 10,
+ "rf_power": -3,
+ "dig_gain": 0
+ },
+ "tx_lut_2": {
+ /* TX gain table, index 2 */
+ "pa_gain": 0,
+ "mix_gain": 12,
+ "rf_power": 0,
+ "dig_gain": 0
+ },
+ "tx_lut_3": {
+ /* TX gain table, index 3 */
+ "pa_gain": 1,
+ "mix_gain": 8,
+ "rf_power": 3,
+ "dig_gain": 0
+ },
+ "tx_lut_4": {
+ /* TX gain table, index 4 */
+ "pa_gain": 1,
+ "mix_gain": 10,
+ "rf_power": 6,
+ "dig_gain": 0
+ },
+ "tx_lut_5": {
+ /* TX gain table, index 5 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 10,
+ "dig_gain": 0
+ },
+ "tx_lut_6": {
+ /* TX gain table, index 6 */
+ "pa_gain": 1,
+ "mix_gain": 13,
+ "rf_power": 11,
+ "dig_gain": 0
+ },
+ "tx_lut_7": {
+ /* TX gain table, index 7 */
+ "pa_gain": 2,
+ "mix_gain": 9,
+ "rf_power": 12,
+ "dig_gain": 0
+ },
+ "tx_lut_8": {
+ /* TX gain table, index 8 */
+ "pa_gain": 1,
+ "mix_gain": 15,
+ "rf_power": 13,
+ "dig_gain": 0
+ },
+ "tx_lut_9": {
+ /* TX gain table, index 9 */
+ "pa_gain": 2,
+ "mix_gain": 10,
+ "rf_power": 14,
+ "dig_gain": 0
+ },
+ "tx_lut_10": {
+ /* TX gain table, index 10 */
+ "pa_gain": 2,
+ "mix_gain": 11,
+ "rf_power": 16,
+ "dig_gain": 0
+ },
+ "tx_lut_11": {
+ /* TX gain table, index 11 */
+ "pa_gain": 3,
+ "mix_gain": 9,
+ "rf_power": 20,
+ "dig_gain": 0
+ }
+ },
+
+ "gateway_conf": {
+ "gateway_ID": "AA555A0000000000",
+ /* change with default server address/ports, or overwrite in local_conf.json */
+ "server_address": "localhost",
+ "serv_port_up": 1680,
+ "serv_port_down": 1680,
+ /* 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/ttyAMA0",
+ /* 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
+ }
+}
+
diff --git a/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps
new file mode 100644
index 0000000..da76f8d
--- /dev/null
+++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E286.EU868.gps
@@ -0,0 +1,194 @@
+{
+ "SX1301_conf": {
+ "lorawan_public": true,
+ "clksrc": 1, /* radio_1 provides clock to concentrator */
+ "antenna_gain": 0, /* antenna gain, in dBi */
+ "radio_0": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 867500000,
+ "rssi_offset": -166.0,
+ "tx_enable": true,
+ "tx_freq_min": 863000000,
+ "tx_freq_max": 870000000
+ },
+ "radio_1": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 868500000,
+ "rssi_offset": -166.0,
+ "tx_enable": false
+ },
+ "chan_multiSF_0": {
+ /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -400000
+ },
+ "chan_multiSF_1": {
+ /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000
+ },
+ "chan_multiSF_2": {
+ /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 0
+ },
+ "chan_multiSF_3": {
+ /* Lora MAC channel, 125kHz, all SF, 867.1 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -400000
+ },
+ "chan_multiSF_4": {
+ /* Lora MAC channel, 125kHz, all SF, 867.3 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -200000
+ },
+ "chan_multiSF_5": {
+ /* Lora MAC channel, 125kHz, all SF, 867.5 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 0
+ },
+ "chan_multiSF_6": {
+ /* Lora MAC channel, 125kHz, all SF, 867.7 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 200000
+ },
+ "chan_multiSF_7": {
+ /* Lora MAC channel, 125kHz, all SF, 867.9 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 400000
+ },
+ "chan_Lora_std": {
+ /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000,
+ "bandwidth": 250000,
+ "spread_factor": 7
+ },
+ "chan_FSK": {
+ /* FSK 50kbps channel, 868.8 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 300000,
+ "bandwidth": 125000,
+ "datarate": 50000
+ },
+ "tx_lut_0": {
+ /* TX gain table, index 0 */
+ "pa_gain": 0,
+ "mix_gain": 8,
+ "rf_power": -6,
+ "dig_gain": 0
+ },
+ "tx_lut_1": {
+ /* TX gain table, index 1 */
+ "pa_gain": 0,
+ "mix_gain": 10,
+ "rf_power": -3,
+ "dig_gain": 0
+ },
+ "tx_lut_2": {
+ /* TX gain table, index 2 */
+ "pa_gain": 0,
+ "mix_gain": 12,
+ "rf_power": 0,
+ "dig_gain": 0
+ },
+ "tx_lut_3": {
+ /* TX gain table, index 3 */
+ "pa_gain": 1,
+ "mix_gain": 8,
+ "rf_power": 3,
+ "dig_gain": 0
+ },
+ "tx_lut_4": {
+ /* TX gain table, index 4 */
+ "pa_gain": 1,
+ "mix_gain": 10,
+ "rf_power": 6,
+ "dig_gain": 0
+ },
+ "tx_lut_5": {
+ /* TX gain table, index 5 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 10,
+ "dig_gain": 0
+ },
+ "tx_lut_6": {
+ /* TX gain table, index 6 */
+ "pa_gain": 1,
+ "mix_gain": 13,
+ "rf_power": 11,
+ "dig_gain": 0
+ },
+ "tx_lut_7": {
+ /* TX gain table, index 7 */
+ "pa_gain": 2,
+ "mix_gain": 9,
+ "rf_power": 12,
+ "dig_gain": 0
+ },
+ "tx_lut_8": {
+ /* TX gain table, index 8 */
+ "pa_gain": 1,
+ "mix_gain": 15,
+ "rf_power": 13,
+ "dig_gain": 0
+ },
+ "tx_lut_9": {
+ /* TX gain table, index 9 */
+ "pa_gain": 2,
+ "mix_gain": 10,
+ "rf_power": 14,
+ "dig_gain": 0
+ },
+ "tx_lut_10": {
+ /* TX gain table, index 10 */
+ "pa_gain": 2,
+ "mix_gain": 11,
+ "rf_power": 16,
+ "dig_gain": 0
+ },
+ "tx_lut_11": {
+ /* TX gain table, index 11 */
+ "pa_gain": 3,
+ "mix_gain": 9,
+ "rf_power": 20,
+ "dig_gain": 0
+ }
+ },
+
+ "gateway_conf": {
+ "gateway_ID": "AA555A0000000000",
+ /* change with default server address/ports, or overwrite in local_conf.json */
+ "server_address": "localhost",
+ "serv_port_up": 1680,
+ "serv_port_down": 1680,
+ /* 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/ttyAMA0",
+ /* GPS reference coordinates */
+ "ref_latitude": 0.0,
+ "ref_longitude": 0.0,
+ "ref_altitude": 0
+ }
+}
+
diff --git a/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic
new file mode 100644
index 0000000..51f0f5a
--- /dev/null
+++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.basic
@@ -0,0 +1,228 @@
+{
+ "SX1301_conf": {
+ "lorawan_public": true,
+ "clksrc": 1, /* radio_1 provides clock to concentrator */
+ "lbt_cfg": {
+ "enable": false,
+ "rssi_target": -80, /* dBm */
+ "chan_cfg":[ /* 8 channels maximum */
+ { "freq_hz": 867100000, "scan_time_us": 128 },
+ { "freq_hz": 867300000, "scan_time_us": 5000 },
+ { "freq_hz": 867500000, "scan_time_us": 128 },
+ { "freq_hz": 869525000, "scan_time_us": 128 }
+ ],
+ "sx127x_rssi_offset": -4 /* dB */
+ },
+ "antenna_gain": 0, /* antenna gain, in dBi */
+ "radio_0": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 867500000,
+ "rssi_offset": -165.0,
+ "tx_enable": true,
+ "tx_notch_freq": 129000, /* [126..250] KHz */
+ "tx_freq_min": 863000000,
+ "tx_freq_max": 870000000
+ },
+ "radio_1": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 868500000,
+ "rssi_offset": -165.0,
+ "tx_enable": false
+ },
+ "chan_multiSF_0": {
+ /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -400000
+ },
+ "chan_multiSF_1": {
+ /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000
+ },
+ "chan_multiSF_2": {
+ /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 0
+ },
+ "chan_multiSF_3": {
+ /* Lora MAC channel, 125kHz, all SF, 867.1 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -400000
+ },
+ "chan_multiSF_4": {
+ /* Lora MAC channel, 125kHz, all SF, 867.3 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -200000
+ },
+ "chan_multiSF_5": {
+ /* Lora MAC channel, 125kHz, all SF, 867.5 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 0
+ },
+ "chan_multiSF_6": {
+ /* Lora MAC channel, 125kHz, all SF, 867.7 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 200000
+ },
+ "chan_multiSF_7": {
+ /* Lora MAC channel, 125kHz, all SF, 867.9 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 400000
+ },
+ "chan_Lora_std": {
+ /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000,
+ "bandwidth": 250000,
+ "spread_factor": 7
+ },
+ "chan_FSK": {
+ /* FSK 50kbps channel, 868.8 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 300000,
+ "bandwidth": 125000,
+ "datarate": 50000
+ },
+ "tx_lut_0": {
+ /* TX gain table, index 0 */
+ "pa_gain": 0,
+ "mix_gain": 8,
+ "rf_power": -6,
+ "dig_gain": 3
+ },
+ "tx_lut_1": {
+ /* TX gain table, index 1 */
+ "pa_gain": 0,
+ "mix_gain": 10,
+ "rf_power": -3,
+ "dig_gain": 3
+ },
+ "tx_lut_2": {
+ /* TX gain table, index 2 */
+ "pa_gain": 0,
+ "mix_gain": 10,
+ "rf_power": 0,
+ "dig_gain": 1
+ },
+ "tx_lut_3": {
+ /* TX gain table, index 3 */
+ "pa_gain": 0,
+ "mix_gain": 14,
+ "rf_power": 3,
+ "dig_gain": 2
+ },
+ "tx_lut_4": {
+ /* TX gain table, index 4 */
+ "pa_gain": 1,
+ "mix_gain": 10,
+ "rf_power": 6,
+ "dig_gain": 3
+ },
+ "tx_lut_5": {
+ /* TX gain table, index 5 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 10,
+ "dig_gain": 2
+ },
+ "tx_lut_6": {
+ /* TX gain table, index 6 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 11,
+ "dig_gain": 1
+ },
+ "tx_lut_7": {
+ /* TX gain table, index 7 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 12,
+ "dig_gain": 0
+ },
+ "tx_lut_8": {
+ /* TX gain table, index 8 */
+ "pa_gain": 1,
+ "mix_gain": 14,
+ "rf_power": 13,
+ "dig_gain": 2
+ },
+ "tx_lut_9": {
+ /* TX gain table, index 9 */
+ "pa_gain": 1,
+ "mix_gain": 13,
+ "rf_power": 14,
+ "dig_gain": 0
+ },
+ "tx_lut_10": {
+ /* TX gain table, index 10 */
+ "pa_gain": 2,
+ "mix_gain": 9,
+ "rf_power": 16,
+ "dig_gain": 2
+ },
+ "tx_lut_11": {
+ /* TX gain table, index 11 */
+ "pa_gain": 2,
+ "mix_gain": 11,
+ "rf_power": 20,
+ "dig_gain": 1
+ },
+ "tx_lut_12": {
+ /* TX gain table, index 12 */
+ "pa_gain": 2,
+ "mix_gain": 13,
+ "rf_power": 23,
+ "dig_gain": 1
+ },
+ "tx_lut_13": {
+ /* TX gain table, index 13 */
+ "pa_gain": 2,
+ "mix_gain": 15,
+ "rf_power": 25,
+ "dig_gain": 2
+ },
+ "tx_lut_14": {
+ /* TX gain table, index 14 */
+ "pa_gain": 3,
+ "mix_gain": 10,
+ "rf_power": 26,
+ "dig_gain": 2
+ },
+ "tx_lut_15": {
+ /* TX gain table, index 15 */
+ "pa_gain": 3,
+ "mix_gain": 10,
+ "rf_power": 27,
+ "dig_gain": 1
+ }
+ },
+
+ "gateway_conf": {
+ "gateway_ID": "AA555A0000000000",
+ /* change with default server address/ports, or overwrite in local_conf.json */
+ "server_address": "localhost",
+ "serv_port_up": 1680,
+ "serv_port_down": 1680,
+ /* 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
+ }
+}
+
diff --git a/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon
new file mode 100644
index 0000000..bcc7b72
--- /dev/null
+++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.beacon
@@ -0,0 +1,241 @@
+{
+ "SX1301_conf": {
+ "lorawan_public": true,
+ "clksrc": 1, /* radio_1 provides clock to concentrator */
+ "lbt_cfg": {
+ "enable": false,
+ "rssi_target": -80, /* dBm */
+ "chan_cfg":[ /* 8 channels maximum */
+ { "freq_hz": 867100000, "scan_time_us": 128 },
+ { "freq_hz": 867300000, "scan_time_us": 5000 },
+ { "freq_hz": 867500000, "scan_time_us": 128 },
+ { "freq_hz": 869525000, "scan_time_us": 128 }
+ ],
+ "sx127x_rssi_offset": -4 /* dB */
+ },
+ "antenna_gain": 0, /* antenna gain, in dBi */
+ "radio_0": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 867500000,
+ "rssi_offset": -165.0,
+ "tx_enable": true,
+ "tx_notch_freq": 129000, /* [126..250] KHz */
+ "tx_freq_min": 863000000,
+ "tx_freq_max": 870000000
+ },
+ "radio_1": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 868500000,
+ "rssi_offset": -165.0,
+ "tx_enable": false
+ },
+ "chan_multiSF_0": {
+ /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -400000
+ },
+ "chan_multiSF_1": {
+ /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000
+ },
+ "chan_multiSF_2": {
+ /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 0
+ },
+ "chan_multiSF_3": {
+ /* Lora MAC channel, 125kHz, all SF, 867.1 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -400000
+ },
+ "chan_multiSF_4": {
+ /* Lora MAC channel, 125kHz, all SF, 867.3 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -200000
+ },
+ "chan_multiSF_5": {
+ /* Lora MAC channel, 125kHz, all SF, 867.5 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 0
+ },
+ "chan_multiSF_6": {
+ /* Lora MAC channel, 125kHz, all SF, 867.7 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 200000
+ },
+ "chan_multiSF_7": {
+ /* Lora MAC channel, 125kHz, all SF, 867.9 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 400000
+ },
+ "chan_Lora_std": {
+ /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000,
+ "bandwidth": 250000,
+ "spread_factor": 7
+ },
+ "chan_FSK": {
+ /* FSK 50kbps channel, 868.8 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 300000,
+ "bandwidth": 125000,
+ "datarate": 50000
+ },
+ "tx_lut_0": {
+ /* TX gain table, index 0 */
+ "pa_gain": 0,
+ "mix_gain": 8,
+ "rf_power": -6,
+ "dig_gain": 3
+ },
+ "tx_lut_1": {
+ /* TX gain table, index 1 */
+ "pa_gain": 0,
+ "mix_gain": 10,
+ "rf_power": -3,
+ "dig_gain": 3
+ },
+ "tx_lut_2": {
+ /* TX gain table, index 2 */
+ "pa_gain": 0,
+ "mix_gain": 10,
+ "rf_power": 0,
+ "dig_gain": 1
+ },
+ "tx_lut_3": {
+ /* TX gain table, index 3 */
+ "pa_gain": 0,
+ "mix_gain": 14,
+ "rf_power": 3,
+ "dig_gain": 2
+ },
+ "tx_lut_4": {
+ /* TX gain table, index 4 */
+ "pa_gain": 1,
+ "mix_gain": 10,
+ "rf_power": 6,
+ "dig_gain": 3
+ },
+ "tx_lut_5": {
+ /* TX gain table, index 5 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 10,
+ "dig_gain": 2
+ },
+ "tx_lut_6": {
+ /* TX gain table, index 6 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 11,
+ "dig_gain": 1
+ },
+ "tx_lut_7": {
+ /* TX gain table, index 7 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 12,
+ "dig_gain": 0
+ },
+ "tx_lut_8": {
+ /* TX gain table, index 8 */
+ "pa_gain": 1,
+ "mix_gain": 14,
+ "rf_power": 13,
+ "dig_gain": 2
+ },
+ "tx_lut_9": {
+ /* TX gain table, index 9 */
+ "pa_gain": 1,
+ "mix_gain": 13,
+ "rf_power": 14,
+ "dig_gain": 0
+ },
+ "tx_lut_10": {
+ /* TX gain table, index 10 */
+ "pa_gain": 2,
+ "mix_gain": 9,
+ "rf_power": 16,
+ "dig_gain": 2
+ },
+ "tx_lut_11": {
+ /* TX gain table, index 11 */
+ "pa_gain": 2,
+ "mix_gain": 11,
+ "rf_power": 20,
+ "dig_gain": 1
+ },
+ "tx_lut_12": {
+ /* TX gain table, index 12 */
+ "pa_gain": 2,
+ "mix_gain": 13,
+ "rf_power": 23,
+ "dig_gain": 1
+ },
+ "tx_lut_13": {
+ /* TX gain table, index 13 */
+ "pa_gain": 2,
+ "mix_gain": 15,
+ "rf_power": 25,
+ "dig_gain": 2
+ },
+ "tx_lut_14": {
+ /* TX gain table, index 14 */
+ "pa_gain": 3,
+ "mix_gain": 10,
+ "rf_power": 26,
+ "dig_gain": 2
+ },
+ "tx_lut_15": {
+ /* TX gain table, index 15 */
+ "pa_gain": 3,
+ "mix_gain": 10,
+ "rf_power": 27,
+ "dig_gain": 1
+ }
+ },
+
+ "gateway_conf": {
+ "gateway_ID": "AA555A0000000000",
+ /* change with default server address/ports, or overwrite in local_conf.json */
+ "server_address": "localhost",
+ "serv_port_up": 1680,
+ "serv_port_down": 1680,
+ /* 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/ttyAMA0",
+ /* 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
+ }
+}
+
diff --git a/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps
new file mode 100644
index 0000000..f25bb7a
--- /dev/null
+++ b/lora_pkt_fwd/cfg/global_conf.json.PCB_E336.EU868.gps
@@ -0,0 +1,234 @@
+{
+ "SX1301_conf": {
+ "lorawan_public": true,
+ "clksrc": 1, /* radio_1 provides clock to concentrator */
+ "lbt_cfg": {
+ "enable": false,
+ "rssi_target": -80, /* dBm */
+ "chan_cfg":[ /* 8 channels maximum */
+ { "freq_hz": 867100000, "scan_time_us": 128 },
+ { "freq_hz": 867300000, "scan_time_us": 5000 },
+ { "freq_hz": 867500000, "scan_time_us": 128 },
+ { "freq_hz": 869525000, "scan_time_us": 128 }
+ ],
+ "sx127x_rssi_offset": -4 /* dB */
+ },
+ "antenna_gain": 0, /* antenna gain, in dBi */
+ "radio_0": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 867500000,
+ "rssi_offset": -165.0,
+ "tx_enable": true,
+ "tx_notch_freq": 129000, /* [126..250] KHz */
+ "tx_freq_min": 863000000,
+ "tx_freq_max": 870000000
+ },
+ "radio_1": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 868500000,
+ "rssi_offset": -165.0,
+ "tx_enable": false
+ },
+ "chan_multiSF_0": {
+ /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -400000
+ },
+ "chan_multiSF_1": {
+ /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000
+ },
+ "chan_multiSF_2": {
+ /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 0
+ },
+ "chan_multiSF_3": {
+ /* Lora MAC channel, 125kHz, all SF, 867.1 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -400000
+ },
+ "chan_multiSF_4": {
+ /* Lora MAC channel, 125kHz, all SF, 867.3 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -200000
+ },
+ "chan_multiSF_5": {
+ /* Lora MAC channel, 125kHz, all SF, 867.5 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 0
+ },
+ "chan_multiSF_6": {
+ /* Lora MAC channel, 125kHz, all SF, 867.7 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 200000
+ },
+ "chan_multiSF_7": {
+ /* Lora MAC channel, 125kHz, all SF, 867.9 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 400000
+ },
+ "chan_Lora_std": {
+ /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000,
+ "bandwidth": 250000,
+ "spread_factor": 7
+ },
+ "chan_FSK": {
+ /* FSK 50kbps channel, 868.8 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 300000,
+ "bandwidth": 125000,
+ "datarate": 50000
+ },
+ "tx_lut_0": {
+ /* TX gain table, index 0 */
+ "pa_gain": 0,
+ "mix_gain": 8,
+ "rf_power": -6,
+ "dig_gain": 3
+ },
+ "tx_lut_1": {
+ /* TX gain table, index 1 */
+ "pa_gain": 0,
+ "mix_gain": 10,
+ "rf_power": -3,
+ "dig_gain": 3
+ },
+ "tx_lut_2": {
+ /* TX gain table, index 2 */
+ "pa_gain": 0,
+ "mix_gain": 10,
+ "rf_power": 0,
+ "dig_gain": 1
+ },
+ "tx_lut_3": {
+ /* TX gain table, index 3 */
+ "pa_gain": 0,
+ "mix_gain": 14,
+ "rf_power": 3,
+ "dig_gain": 2
+ },
+ "tx_lut_4": {
+ /* TX gain table, index 4 */
+ "pa_gain": 1,
+ "mix_gain": 10,
+ "rf_power": 6,
+ "dig_gain": 3
+ },
+ "tx_lut_5": {
+ /* TX gain table, index 5 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 10,
+ "dig_gain": 2
+ },
+ "tx_lut_6": {
+ /* TX gain table, index 6 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 11,
+ "dig_gain": 1
+ },
+ "tx_lut_7": {
+ /* TX gain table, index 7 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 12,
+ "dig_gain": 0
+ },
+ "tx_lut_8": {
+ /* TX gain table, index 8 */
+ "pa_gain": 1,
+ "mix_gain": 14,
+ "rf_power": 13,
+ "dig_gain": 2
+ },
+ "tx_lut_9": {
+ /* TX gain table, index 9 */
+ "pa_gain": 1,
+ "mix_gain": 13,
+ "rf_power": 14,
+ "dig_gain": 0
+ },
+ "tx_lut_10": {
+ /* TX gain table, index 10 */
+ "pa_gain": 2,
+ "mix_gain": 9,
+ "rf_power": 16,
+ "dig_gain": 2
+ },
+ "tx_lut_11": {
+ /* TX gain table, index 11 */
+ "pa_gain": 2,
+ "mix_gain": 11,
+ "rf_power": 20,
+ "dig_gain": 1
+ },
+ "tx_lut_12": {
+ /* TX gain table, index 12 */
+ "pa_gain": 2,
+ "mix_gain": 13,
+ "rf_power": 23,
+ "dig_gain": 1
+ },
+ "tx_lut_13": {
+ /* TX gain table, index 13 */
+ "pa_gain": 2,
+ "mix_gain": 15,
+ "rf_power": 25,
+ "dig_gain": 2
+ },
+ "tx_lut_14": {
+ /* TX gain table, index 14 */
+ "pa_gain": 3,
+ "mix_gain": 10,
+ "rf_power": 26,
+ "dig_gain": 2
+ },
+ "tx_lut_15": {
+ /* TX gain table, index 15 */
+ "pa_gain": 3,
+ "mix_gain": 10,
+ "rf_power": 27,
+ "dig_gain": 1
+ }
+ },
+
+ "gateway_conf": {
+ "gateway_ID": "AA555A0000000000",
+ /* change with default server address/ports, or overwrite in local_conf.json */
+ "server_address": "localhost",
+ "serv_port_up": 1680,
+ "serv_port_down": 1680,
+ /* 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/ttyAMA0",
+ /* GPS reference coordinates */
+ "ref_latitude": 0.0,
+ "ref_longitude": 0.0,
+ "ref_altitude": 0
+ }
+}
+
diff --git a/lora_pkt_fwd/cfg/global_conf.json.US902.basic b/lora_pkt_fwd/cfg/global_conf.json.US902.basic
new file mode 100644
index 0000000..3914956
--- /dev/null
+++ b/lora_pkt_fwd/cfg/global_conf.json.US902.basic
@@ -0,0 +1,104 @@
+{
+ "SX1301_conf": {
+ "lorawan_public": true,
+ "clksrc": 1, /* radio_1 provides clock to concentrator */
+ "antenna_gain": 0, /* antenna gain, in dBi */
+ "radio_0": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 902700000,
+ "rssi_offset": -166.0,
+ "tx_enable": true,
+ "tx_freq_min": 902000000,
+ "tx_freq_max": 928000000
+ },
+ "radio_1": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 903400000,
+ "rssi_offset": -166.0,
+ "tx_enable": false
+ },
+ "chan_multiSF_0": {
+ /* Lora MAC channel, 125kHz, all SF, 902.3 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -400000
+ },
+ "chan_multiSF_1": {
+ /* Lora MAC channel, 125kHz, all SF, 902.5 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -200000
+ },
+ "chan_multiSF_2": {
+ /* Lora MAC channel, 125kHz, all SF, 902.7 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 0
+ },
+ "chan_multiSF_3": {
+ /* Lora MAC channel, 125kHz, all SF, 902.9 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 200000
+ },
+ "chan_multiSF_4": {
+ /* Lora MAC channel, 125kHz, all SF, 903.1 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -300000
+ },
+ "chan_multiSF_5": {
+ /* Lora MAC channel, 125kHz, all SF, 903.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -100000
+ },
+ "chan_multiSF_6": {
+ /* Lora MAC channel, 125kHz, all SF, 903.5 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 100000
+ },
+ "chan_multiSF_7": {
+ /* Lora MAC channel, 125kHz, all SF, 903.7 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 300000
+ },
+ "chan_Lora_std": {
+ /* Lora MAC channel, 500kHz, SF8, 903.0 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 300000,
+ "bandwidth": 500000,
+ "spread_factor": 8
+ },
+ "chan_FSK": {
+ /* FSK 100kbps channel, 903.0 MHz */
+ "enable": false,
+ "radio": 0,
+ "if": 300000,
+ "bandwidth": 250000,
+ "datarate": 100000
+ }
+ },
+
+ "gateway_conf": {
+ "gateway_ID": "AA555A0000000000",
+ /* change with default server address/ports, or overwrite in local_conf.json */
+ "server_address": "localhost",
+ "serv_port_up": 1680,
+ "serv_port_down": 1680,
+ /* 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
+ }
+}
+
diff --git a/lora_pkt_fwd/cfg/global_conf.json.US902.beacon b/lora_pkt_fwd/cfg/global_conf.json.US902.beacon
new file mode 100644
index 0000000..3592d40
--- /dev/null
+++ b/lora_pkt_fwd/cfg/global_conf.json.US902.beacon
@@ -0,0 +1,119 @@
+{
+ "SX1301_conf": {
+ "lorawan_public": true,
+ "clksrc": 1, /* radio_1 provides clock to concentrator */
+ "antenna_gain": 0, /* antenna gain, in dBi */
+ "radio_0": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 902700000,
+ "rssi_offset": -166.0,
+ "tx_enable": true,
+ "tx_freq_min": 902000000,
+ "tx_freq_max": 928000000
+ },
+ "radio_1": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 903400000,
+ "rssi_offset": -166.0,
+ "tx_enable": false
+ },
+ "chan_multiSF_0": {
+ /* Lora MAC channel, 125kHz, all SF, 902.3 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -400000
+ },
+ "chan_multiSF_1": {
+ /* Lora MAC channel, 125kHz, all SF, 902.5 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -200000
+ },
+ "chan_multiSF_2": {
+ /* Lora MAC channel, 125kHz, all SF, 902.7 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 0
+ },
+ "chan_multiSF_3": {
+ /* Lora MAC channel, 125kHz, all SF, 902.9 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 200000
+ },
+ "chan_multiSF_4": {
+ /* Lora MAC channel, 125kHz, all SF, 903.1 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -300000
+ },
+ "chan_multiSF_5": {
+ /* Lora MAC channel, 125kHz, all SF, 903.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -100000
+ },
+ "chan_multiSF_6": {
+ /* Lora MAC channel, 125kHz, all SF, 903.5 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 100000
+ },
+ "chan_multiSF_7": {
+ /* Lora MAC channel, 125kHz, all SF, 903.7 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 300000
+ },
+ "chan_Lora_std": {
+ /* Lora MAC channel, 500kHz, SF8, 903.0 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 300000,
+ "bandwidth": 500000,
+ "spread_factor": 8
+ },
+ "chan_FSK": {
+ /* FSK 100kbps channel, 903.0 MHz */
+ "enable": false,
+ "radio": 0,
+ "if": 300000,
+ "bandwidth": 250000,
+ "datarate": 100000
+ }
+ },
+
+ "gateway_conf": {
+ "gateway_ID": "AA555A0000000000",
+ /* change with default server address/ports, or overwrite in local_conf.json */
+ "server_address": "localhost",
+ "serv_port_up": 1680,
+ "serv_port_down": 1680,
+ /* 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/ttyAMA0",
+ /* GPS reference coordinates */
+ "ref_latitude": 0.0,
+ "ref_longitude": 0.0,
+ "ref_altitude": 0,
+ /* Beaconing parameters */
+ "beacon_period": 128,
+ "beacon_freq_hz": 923300000,
+ "beacon_freq_nb": 8,
+ "beacon_freq_step": 600000,
+ "beacon_datarate": 12,
+ "beacon_bw_hz": 500000,
+ "beacon_power": 14,
+ "beacon_infodesc": 0
+ }
+}
+
diff --git a/lora_pkt_fwd/cfg/global_conf.json.US902.gps b/lora_pkt_fwd/cfg/global_conf.json.US902.gps
new file mode 100644
index 0000000..ddd0928
--- /dev/null
+++ b/lora_pkt_fwd/cfg/global_conf.json.US902.gps
@@ -0,0 +1,110 @@
+{
+ "SX1301_conf": {
+ "lorawan_public": true,
+ "clksrc": 1, /* radio_1 provides clock to concentrator */
+ "antenna_gain": 0, /* antenna gain, in dBi */
+ "radio_0": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 902700000,
+ "rssi_offset": -166.0,
+ "tx_enable": true,
+ "tx_freq_min": 902000000,
+ "tx_freq_max": 928000000
+ },
+ "radio_1": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 903400000,
+ "rssi_offset": -166.0,
+ "tx_enable": false
+ },
+ "chan_multiSF_0": {
+ /* Lora MAC channel, 125kHz, all SF, 902.3 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -400000
+ },
+ "chan_multiSF_1": {
+ /* Lora MAC channel, 125kHz, all SF, 902.5 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -200000
+ },
+ "chan_multiSF_2": {
+ /* Lora MAC channel, 125kHz, all SF, 902.7 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 0
+ },
+ "chan_multiSF_3": {
+ /* Lora MAC channel, 125kHz, all SF, 902.9 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 200000
+ },
+ "chan_multiSF_4": {
+ /* Lora MAC channel, 125kHz, all SF, 903.1 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -300000
+ },
+ "chan_multiSF_5": {
+ /* Lora MAC channel, 125kHz, all SF, 903.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -100000
+ },
+ "chan_multiSF_6": {
+ /* Lora MAC channel, 125kHz, all SF, 903.5 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 100000
+ },
+ "chan_multiSF_7": {
+ /* Lora MAC channel, 125kHz, all SF, 903.7 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 300000
+ },
+ "chan_Lora_std": {
+ /* Lora MAC channel, 500kHz, SF8, 903.0 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 300000,
+ "bandwidth": 500000,
+ "spread_factor": 8
+ },
+ "chan_FSK": {
+ /* FSK 100kbps channel, 903.0 MHz */
+ "enable": false,
+ "radio": 0,
+ "if": 300000,
+ "bandwidth": 250000,
+ "datarate": 100000
+ }
+ },
+
+ "gateway_conf": {
+ "gateway_ID": "AA555A0000000000",
+ /* change with default server address/ports, or overwrite in local_conf.json */
+ "server_address": "localhost",
+ "serv_port_up": 1680,
+ "serv_port_down": 1680,
+ /* 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/ttyAMA0",
+ /* GPS reference coordinates */
+ "ref_latitude": 0.0,
+ "ref_longitude": 0.0,
+ "ref_altitude": 0
+ }
+}
+
diff --git a/lora_pkt_fwd/global_conf.json b/lora_pkt_fwd/global_conf.json
new file mode 100644
index 0000000..2d0948c
--- /dev/null
+++ b/lora_pkt_fwd/global_conf.json
@@ -0,0 +1,228 @@
+{
+ "SX1301_conf": {
+ "lorawan_public": true,
+ "clksrc": 1, /* radio_1 provides clock to concentrator */
+ "lbt_cfg": {
+ "enable": false,
+ "rssi_target": -80, /* dBm */
+ "chan_cfg":[ /* 8 channels maximum */
+ { "freq_hz": 867100000, "scan_time_us": 128 },
+ { "freq_hz": 867300000, "scan_time_us": 5000 },
+ { "freq_hz": 867500000, "scan_time_us": 128 },
+ { "freq_hz": 869525000, "scan_time_us": 128 }
+ ],
+ "sx127x_rssi_offset": -4 /* dB */
+ },
+ "antenna_gain": 0, /* antenna gain, in dBi */
+ "radio_0": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 867500000,
+ "rssi_offset": -166.0,
+ "tx_enable": true,
+ "tx_notch_freq": 129000, /* [126..250] KHz */
+ "tx_freq_min": 863000000,
+ "tx_freq_max": 870000000
+ },
+ "radio_1": {
+ "enable": true,
+ "type": "SX1257",
+ "freq": 868500000,
+ "rssi_offset": -166.0,
+ "tx_enable": false
+ },
+ "chan_multiSF_0": {
+ /* Lora MAC channel, 125kHz, all SF, 868.1 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -400000
+ },
+ "chan_multiSF_1": {
+ /* Lora MAC channel, 125kHz, all SF, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000
+ },
+ "chan_multiSF_2": {
+ /* Lora MAC channel, 125kHz, all SF, 868.5 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 0
+ },
+ "chan_multiSF_3": {
+ /* Lora MAC channel, 125kHz, all SF, 867.1 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -400000
+ },
+ "chan_multiSF_4": {
+ /* Lora MAC channel, 125kHz, all SF, 867.3 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": -200000
+ },
+ "chan_multiSF_5": {
+ /* Lora MAC channel, 125kHz, all SF, 867.5 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 0
+ },
+ "chan_multiSF_6": {
+ /* Lora MAC channel, 125kHz, all SF, 867.7 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 200000
+ },
+ "chan_multiSF_7": {
+ /* Lora MAC channel, 125kHz, all SF, 867.9 MHz */
+ "enable": true,
+ "radio": 0,
+ "if": 400000
+ },
+ "chan_Lora_std": {
+ /* Lora MAC channel, 250kHz, SF7, 868.3 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": -200000,
+ "bandwidth": 250000,
+ "spread_factor": 7
+ },
+ "chan_FSK": {
+ /* FSK 50kbps channel, 868.8 MHz */
+ "enable": true,
+ "radio": 1,
+ "if": 300000,
+ "bandwidth": 125000,
+ "datarate": 50000
+ },
+ "tx_lut_0": {
+ /* TX gain table, index 0 */
+ "pa_gain": 0,
+ "mix_gain": 8,
+ "rf_power": -6,
+ "dig_gain": 0
+ },
+ "tx_lut_1": {
+ /* TX gain table, index 1 */
+ "pa_gain": 0,
+ "mix_gain": 10,
+ "rf_power": -3,
+ "dig_gain": 0
+ },
+ "tx_lut_2": {
+ /* TX gain table, index 2 */
+ "pa_gain": 0,
+ "mix_gain": 12,
+ "rf_power": 0,
+ "dig_gain": 0
+ },
+ "tx_lut_3": {
+ /* TX gain table, index 3 */
+ "pa_gain": 1,
+ "mix_gain": 8,
+ "rf_power": 3,
+ "dig_gain": 0
+ },
+ "tx_lut_4": {
+ /* TX gain table, index 4 */
+ "pa_gain": 1,
+ "mix_gain": 10,
+ "rf_power": 6,
+ "dig_gain": 0
+ },
+ "tx_lut_5": {
+ /* TX gain table, index 5 */
+ "pa_gain": 1,
+ "mix_gain": 12,
+ "rf_power": 10,
+ "dig_gain": 0
+ },
+ "tx_lut_6": {
+ /* TX gain table, index 6 */
+ "pa_gain": 1,
+ "mix_gain": 13,
+ "rf_power": 11,
+ "dig_gain": 0
+ },
+ "tx_lut_7": {
+ /* TX gain table, index 7 */
+ "pa_gain": 2,
+ "mix_gain": 9,
+ "rf_power": 12,
+ "dig_gain": 0
+ },
+ "tx_lut_8": {
+ /* TX gain table, index 8 */
+ "pa_gain": 1,
+ "mix_gain": 15,
+ "rf_power": 13,
+ "dig_gain": 0
+ },
+ "tx_lut_9": {
+ /* TX gain table, index 9 */
+ "pa_gain": 2,
+ "mix_gain": 10,
+ "rf_power": 14,
+ "dig_gain": 0
+ },
+ "tx_lut_10": {
+ /* TX gain table, index 10 */
+ "pa_gain": 2,
+ "mix_gain": 11,
+ "rf_power": 16,
+ "dig_gain": 0
+ },
+ "tx_lut_11": {
+ /* TX gain table, index 11 */
+ "pa_gain": 3,
+ "mix_gain": 9,
+ "rf_power": 20,
+ "dig_gain": 0
+ },
+ "tx_lut_12": {
+ /* TX gain table, index 12 */
+ "pa_gain": 3,
+ "mix_gain": 10,
+ "rf_power": 23,
+ "dig_gain": 0
+ },
+ "tx_lut_13": {
+ /* TX gain table, index 13 */
+ "pa_gain": 3,
+ "mix_gain": 11,
+ "rf_power": 25,
+ "dig_gain": 0
+ },
+ "tx_lut_14": {
+ /* TX gain table, index 14 */
+ "pa_gain": 3,
+ "mix_gain": 12,
+ "rf_power": 26,
+ "dig_gain": 0
+ },
+ "tx_lut_15": {
+ /* TX gain table, index 15 */
+ "pa_gain": 3,
+ "mix_gain": 14,
+ "rf_power": 27,
+ "dig_gain": 0
+ }
+ },
+
+ "gateway_conf": {
+ "gateway_ID": "AA555A0000000000",
+ /* change with default server address/ports, or overwrite in local_conf.json */
+ "server_address": "localhost",
+ "serv_port_up": 1680,
+ "serv_port_down": 1680,
+ /* 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
+ }
+}
+
diff --git a/lora_pkt_fwd/inc/base64.h b/lora_pkt_fwd/inc/base64.h
new file mode 100644
index 0000000..e57eb47
--- /dev/null
+++ b/lora_pkt_fwd/inc/base64.h
@@ -0,0 +1,62 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Base64 encoding & decoding library
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+#ifndef _BASE64_H
+#define _BASE64_H
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+/**
+@brief Encode binary data in Base64 string (no padding)
+@param in pointer to a table of binary data
+@param size number of bytes to be encoded to base64
+@param out pointer to a string where the function will output encoded data
+@param max_len max length of the out string (including null char)
+@return >=0 length of the resulting string (w/o null char), -1 for error
+*/
+int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len);
+
+/**
+@brief Decode Base64 string to binary data (no padding)
+@param in string containing only base64 valid characters
+@param size number of characters to be decoded from base64 (w/o null char)
+@param out pointer to a data buffer where the function will output decoded data
+@param out_max_len usable size of the output data buffer
+@return >=0 number of bytes written to the data buffer, -1 for error
+*/
+int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len);
+
+/* === derivative functions === */
+
+/**
+@brief Encode binary data in Base64 string (with added padding)
+*/
+int bin_to_b64(const uint8_t * in, int size, char * out, int max_len);
+
+/**
+@brief Decode Base64 string to binary data (remove padding if necessary)
+*/
+int b64_to_bin(const char * in, int size, uint8_t * out, int max_len);
+
+#endif
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/lora_pkt_fwd/inc/jitqueue.h b/lora_pkt_fwd/inc/jitqueue.h
new file mode 100644
index 0000000..b674f75
--- /dev/null
+++ b/lora_pkt_fwd/inc/jitqueue.h
@@ -0,0 +1,156 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ LoRa concentrator : Just In Time TX scheduling queue
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+
+#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"
+#include "loragw_gps.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[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, struct timeval *time, 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[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, struct timeval *time, 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 ------------------------------------------------------------------ */
diff --git a/lora_pkt_fwd/inc/parson.h b/lora_pkt_fwd/inc/parson.h
new file mode 100644
index 0000000..2669a18
--- /dev/null
+++ b/lora_pkt_fwd/inc/parson.h
@@ -0,0 +1,222 @@
+/*
+ Parson ( http://kgabis.github.com/parson/ )
+ Copyright (c) 2012 - 2016 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.
+*/
+
+#ifndef parson_parson_h
+#define parson_parson_h
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stddef.h> /* size_t */
+
+/* Types and enums */
+typedef struct json_object_t JSON_Object;
+typedef struct json_array_t JSON_Array;
+typedef struct json_value_t JSON_Value;
+
+enum json_value_type {
+ JSONError = -1,
+ JSONNull = 1,
+ JSONString = 2,
+ JSONNumber = 3,
+ JSONObject = 4,
+ JSONArray = 5,
+ JSONBoolean = 6
+};
+typedef int JSON_Value_Type;
+
+enum json_result_t {
+ JSONSuccess = 0,
+ JSONFailure = -1
+};
+typedef int JSON_Status;
+
+typedef void * (*JSON_Malloc_Function)(size_t);
+typedef void (*JSON_Free_Function)(void *);
+
+/* Call only once, before calling any other function from parson API. If not called, malloc and free
+ from stdlib will be used for all allocations */
+void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun);
+
+/* Parses first JSON value in a file, returns NULL in case of error */
+JSON_Value * json_parse_file(const char *filename);
+
+/* Parses first JSON value in a file and ignores comments (/ * * / and //),
+ returns NULL in case of error */
+JSON_Value * json_parse_file_with_comments(const char *filename);
+
+/* Parses first JSON value in a string, returns NULL in case of error */
+JSON_Value * json_parse_string(const char *string);
+
+/* Parses first JSON value in a string and ignores comments (/ * * / and //),
+ returns NULL in case of error */
+JSON_Value * json_parse_string_with_comments(const char *string);
+
+/* Serialization */
+size_t json_serialization_size(const JSON_Value *value); /* returns 0 on fail */
+JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes);
+JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename);
+char * json_serialize_to_string(const JSON_Value *value);
+
+/* Pretty serialization */
+size_t json_serialization_size_pretty(const JSON_Value *value); /* returns 0 on fail */
+JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes);
+JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename);
+char * json_serialize_to_string_pretty(const JSON_Value *value);
+
+void json_free_serialized_string(char *string); /* frees string from json_serialize_to_string and json_serialize_to_string_pretty */
+
+/* Comparing */
+int json_value_equals(const JSON_Value *a, const JSON_Value *b);
+
+/* Validation
+ This is *NOT* JSON Schema. It validates json by checking if object have identically
+ named fields with matching types.
+ For example schema {"name":"", "age":0} will validate
+ {"name":"Joe", "age":25} and {"name":"Joe", "age":25, "gender":"m"},
+ but not {"name":"Joe"} or {"name":"Joe", "age":"Cucumber"}.
+ In case of arrays, only first value in schema is checked against all values in tested array.
+ Empty objects ({}) validate all objects, empty arrays ([]) validate all arrays,
+ null validates values of every type.
+ */
+JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value);
+
+/*
+ * JSON Object
+ */
+JSON_Value * json_object_get_value (const JSON_Object *object, const char *name);
+const char * json_object_get_string (const JSON_Object *object, const char *name);
+JSON_Object * json_object_get_object (const JSON_Object *object, const char *name);
+JSON_Array * json_object_get_array (const JSON_Object *object, const char *name);
+double json_object_get_number (const JSON_Object *object, const char *name); /* returns 0 on fail */
+int json_object_get_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */
+
+/* dotget functions enable addressing values with dot notation in nested objects,
+ just like in structs or c++/java/c# objects (e.g. objectA.objectB.value).
+ Because valid names in JSON can contain dots, some values may be inaccessible
+ this way. */
+JSON_Value * json_object_dotget_value (const JSON_Object *object, const char *name);
+const char * json_object_dotget_string (const JSON_Object *object, const char *name);
+JSON_Object * json_object_dotget_object (const JSON_Object *object, const char *name);
+JSON_Array * json_object_dotget_array (const JSON_Object *object, const char *name);
+double json_object_dotget_number (const JSON_Object *object, const char *name); /* returns 0 on fail */
+int json_object_dotget_boolean(const JSON_Object *object, const char *name); /* returns -1 on fail */
+
+/* Functions to get available names */
+size_t json_object_get_count(const JSON_Object *object);
+const char * json_object_get_name (const JSON_Object *object, size_t index);
+
+/* Creates new name-value pair or frees and replaces old value with a new one.
+ * json_object_set_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value);
+JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string);
+JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number);
+JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean);
+JSON_Status json_object_set_null(JSON_Object *object, const char *name);
+
+/* Works like dotget functions, but creates whole hierarchy if necessary.
+ * json_object_dotset_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value);
+JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string);
+JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number);
+JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean);
+JSON_Status json_object_dotset_null(JSON_Object *object, const char *name);
+
+/* Frees and removes name-value pair */
+JSON_Status json_object_remove(JSON_Object *object, const char *name);
+
+/* Works like dotget function, but removes name-value pair only on exact match. */
+JSON_Status json_object_dotremove(JSON_Object *object, const char *key);
+
+/* Removes all name-value pairs in object */
+JSON_Status json_object_clear(JSON_Object *object);
+
+/*
+ *JSON Array
+ */
+JSON_Value * json_array_get_value (const JSON_Array *array, size_t index);
+const char * json_array_get_string (const JSON_Array *array, size_t index);
+JSON_Object * json_array_get_object (const JSON_Array *array, size_t index);
+JSON_Array * json_array_get_array (const JSON_Array *array, size_t index);
+double json_array_get_number (const JSON_Array *array, size_t index); /* returns 0 on fail */
+int json_array_get_boolean(const JSON_Array *array, size_t index); /* returns -1 on fail */
+size_t json_array_get_count (const JSON_Array *array);
+
+/* Frees and removes value at given index, does nothing and returns JSONFailure if index doesn't exist.
+ * Order of values in array may change during execution. */
+JSON_Status json_array_remove(JSON_Array *array, size_t i);
+
+/* Frees and removes from array value at given index and replaces it with given one.
+ * Does nothing and returns JSONFailure if index doesn't exist.
+ * json_array_replace_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_array_replace_value(JSON_Array *array, size_t i, JSON_Value *value);
+JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string);
+JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number);
+JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean);
+JSON_Status json_array_replace_null(JSON_Array *array, size_t i);
+
+/* Frees and removes all values from array */
+JSON_Status json_array_clear(JSON_Array *array);
+
+/* Appends new value at the end of array.
+ * json_array_append_value does not copy passed value so it shouldn't be freed afterwards. */
+JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value);
+JSON_Status json_array_append_string(JSON_Array *array, const char *string);
+JSON_Status json_array_append_number(JSON_Array *array, double number);
+JSON_Status json_array_append_boolean(JSON_Array *array, int boolean);
+JSON_Status json_array_append_null(JSON_Array *array);
+
+/*
+ *JSON Value
+ */
+JSON_Value * json_value_init_object (void);
+JSON_Value * json_value_init_array (void);
+JSON_Value * json_value_init_string (const char *string); /* copies passed string */
+JSON_Value * json_value_init_number (double number);
+JSON_Value * json_value_init_boolean(int boolean);
+JSON_Value * json_value_init_null (void);
+JSON_Value * json_value_deep_copy (const JSON_Value *value);
+void json_value_free (JSON_Value *value);
+
+JSON_Value_Type json_value_get_type (const JSON_Value *value);
+JSON_Object * json_value_get_object (const JSON_Value *value);
+JSON_Array * json_value_get_array (const JSON_Value *value);
+const char * json_value_get_string (const JSON_Value *value);
+double json_value_get_number (const JSON_Value *value);
+int json_value_get_boolean(const JSON_Value *value);
+
+/* Same as above, but shorter */
+JSON_Value_Type json_type (const JSON_Value *value);
+JSON_Object * json_object (const JSON_Value *value);
+JSON_Array * json_array (const JSON_Value *value);
+const char * json_string (const JSON_Value *value);
+double json_number (const JSON_Value *value);
+int json_boolean(const JSON_Value *value);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lora_pkt_fwd/inc/timersync.h b/lora_pkt_fwd/inc/timersync.h
new file mode 100644
index 0000000..497e538
--- /dev/null
+++ b/lora_pkt_fwd/inc/timersync.h
@@ -0,0 +1,32 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ LoRa concentrator : Just In Time TX scheduling queue
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+
+#ifndef _LORA_PKTFWD_TIMERSYNC_H
+#define _LORA_PKTFWD_TIMERSYNC_H
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <sys/time.h> /* timeval */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+int get_concentrator_time(struct timeval *concent_time, struct timeval unix_time);
+
+void thread_timersync(void);
+
+#endif
diff --git a/lora_pkt_fwd/inc/trace.h b/lora_pkt_fwd/inc/trace.h
new file mode 100644
index 0000000..a067851
--- /dev/null
+++ b/lora_pkt_fwd/inc/trace.h
@@ -0,0 +1,37 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ LoRa concentrator : Packet Forwarder trace helpers
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+
+#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)
+
+
+
+#endif
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/lora_pkt_fwd/local_conf.json b/lora_pkt_fwd/local_conf.json
new file mode 100644
index 0000000..cf6ff56
--- /dev/null
+++ b/lora_pkt_fwd/local_conf.json
@@ -0,0 +1,7 @@
+{
+/* Put there parameters that are different for each gateway (eg. pointing one gateway to a test server while the others stay in production) */
+/* Settings defined in global_conf will be overwritten by those in local_conf */
+ "gateway_conf": {
+ "gateway_ID": "AA555A0000000101" /* you must pick a unique 64b number for each gateway (represented by an hex string) */
+ }
+}
diff --git a/lora_pkt_fwd/readme.md b/lora_pkt_fwd/readme.md
new file mode 100644
index 0000000..d934154
--- /dev/null
+++ b/lora_pkt_fwd/readme.md
@@ -0,0 +1,328 @@
+ / _____) _ | |
+ ( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+ (______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Lora Gateway 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 from cfg/ directory that fit with your
+platform, region and feature need.
+* Update the JSON configuration (global and local) files, as explained below.
+* For IoT Starter Kit only, run:
+ ./reset_lgw.sh stop
+ ./reset_lgw.sh start
+* Run:
+ ./update_gwid.sh local_conf.json (OPTIONAL)
+ ./lora_pkt_fwd
+
+To stop the application, press Ctrl+C.
+Unless it is manually stopped or encounter a critical error, the program will
+run forever.
+
+There are no command line launch options.
+
+The way the program takes configuration files into account is the following:
+ * if there is a debug_conf.json parse it, others are ignored
+ * if there is a global_conf.json parse it, look for the next file
+ * if there is a local_conf.json parse it
+If some parameters are defined in both global and local configuration files,
+the local definition overwrites the global definition.
+
+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).
+
+As some of the parameters (like 'rssi_offset', 'tx_lut_*') are board dependant,
+several flavours of the global_conf.json file are provided in the cfg/
+directory.
+* global_conf.json.PCB_E286.EU868.*: to be used for Semtech reference design
+ board with PCB name PCB_E286 (also called Gateway Board v1.0 (no FPGA)).
+ Configured for Europe 868MHz channels.
+* global_conf.json.PCB_E336.EU868.*:to be used for Semtech reference design
+ board with PCB name PCB_E336 (also called Gateway Board v1.5 (with FPGA)).
+ Configured for Europe 868MHz channels.
+* global_conf.json.US902.*: to be used for Semtech reference design v1.0 or
+ v1.5. (No calibration done for RSSI offset and TX gains yet).
+ Configured for US 902MHz channels.
+
+Beside board related flavours, there are "features" flavours named "basic",
+"gps", "beacon".
+* global_conf.json.*.basic: to be used for basic packet forwarder usage, with
+no GPS.
+* global_conf.json.*.gps: to be used when the platform has a GPS receiver.
+* global_conf.json.*.beacon: to be used when the platform has a GPS receiver
+and we want the packet forwarder to emit beacons for synchronized networks.
+
+Rename the one you need to global_conf.json before launching the packet
+forwarder.
+
+The local configuration file should contain parameters that are specific to
+each gateway (eg. MAC address, frequency for backhaul radio channels).
+
+In each configuration file, the program looks for a JSON object named
+"SX1301_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.
+- A Timer synchronization thread to keep the concentrator clock and Unix clock
+synchronized so that host processor can determine if a packet with a given
+timestamp can be programmed in the concentrator or not.
+
+5.1. Concentrator vs Unix time synchronization
+
+In order for the host to know if an incoming downlink packet can or cannot be
+queued in JiT queue for later transmission, it has to check if the timestamp of
+the packet designates a time later than the current concentrator counter or if
+it is already too late to be passed to the concentrator.
+In order to get current concentrator time, we can use the lgw_get_trigcnt() HAL
+function. The problem is that the sample register used to read this value can be
+configured in 2 different ways:
+ - Real time mode: when GPS is disabled, the value read in sample register is
+ the actual concentrator counter value.
+ - PPS mode: when GPS is enabled, the value read in sample register is the
+ value that the concentrator counter had when last GPS’s PPS occurred. So
+ this changes every second only.
+As in our case GPS is enabled (LGW_GPS_EN==1), we need to have a way to get the
+actual concentrator current time, at any time.
+For this, a new thread has been added to the packet forwarder (thread_timersync)
+which will regularly:
+ - Disable GPS mode of SX1301 counter sampler
+ - Get current Unix time
+ - Get current SX1301 counter
+ - Compute the offset between Unix and SX1301 clocks and store it
+ - Re-enable GPS mode of SX1301 counter sampler
+Then a new function has been added to estimate the current concentrator counter
+at any time based on the current Unix time and offset computed by the timersync
+thread.
+
+In addition to this, the Concentrator vs Unix time synchronization is used by
+the JiT thread to determine if a packet in the JiT queue has to be sent to the
+concentrator for transmission.
+So basically it is used for queueing and dequeuing packets to/from the JiT queue.
+
+5.2. 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 SX1301 counter value to GPS UTC time when we receive
+an uplink, in order to fill the “time” field of JSON “rxpk” structure.
+
+5.3. 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.4. 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) 2013, 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*
diff --git a/lora_pkt_fwd/src/base64.c b/lora_pkt_fwd/src/base64.c
new file mode 100644
index 0000000..8ba908e
--- /dev/null
+++ b/lora_pkt_fwd/src/base64.c
@@ -0,0 +1,308 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Base64 encoding & decoding library
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "base64.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#define CRIT(a) fprintf(stderr, "\nCRITICAL file:%s line:%u msg:%s\n", __FILE__, __LINE__,a);exit(EXIT_FAILURE)
+
+//#define DEBUG(args...) fprintf(stderr,"debug: " args) /* diagnostic message that is destined to the user */
+#define DEBUG(args...)
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MODULE-WIDE VARIABLES ---------------------------------------- */
+
+static char code_62 = '+'; /* RFC 1421 standard character for code 62 */
+static char code_63 = '/'; /* RFC 1421 standard character for code 63 */
+static char code_pad = '='; /* RFC 1421 padding character if padding */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
+
+/**
+@brief Convert a code in the range 0-63 to an ASCII character
+*/
+char code_to_char(uint8_t x);
+
+/**
+@brief Convert an ASCII character to a code in the range 0-63
+*/
+uint8_t char_to_code(char x);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+char code_to_char(uint8_t x) {
+ if (x <= 25) {
+ return 'A' + x;
+ } else if ((x >= 26) && (x <= 51)) {
+ return 'a' + (x-26);
+ } else if ((x >= 52) && (x <= 61)) {
+ return '0' + (x-52);
+ } else if (x == 62) {
+ return code_62;
+ } else if (x == 63) {
+ return code_63;
+ } else {
+ DEBUG("ERROR: %i IS OUT OF RANGE 0-63 FOR BASE64 ENCODING\n", x);
+ exit(EXIT_FAILURE);
+ } //TODO: improve error management
+}
+
+uint8_t char_to_code(char x) {
+ if ((x >= 'A') && (x <= 'Z')) {
+ return (uint8_t)x - (uint8_t)'A';
+ } else if ((x >= 'a') && (x <= 'z')) {
+ return (uint8_t)x - (uint8_t)'a' + 26;
+ } else if ((x >= '0') && (x <= '9')) {
+ return (uint8_t)x - (uint8_t)'0' + 52;
+ } else if (x == code_62) {
+ return 62;
+ } else if (x == code_63) {
+ return 63;
+ } else {
+ DEBUG("ERROR: %c (0x%x) IS INVALID CHARACTER FOR BASE64 DECODING\n", x, x);
+ exit(EXIT_FAILURE);
+ } //TODO: improve error management
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
+
+int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len) {
+ int i;
+ int result_len; /* size of the result */
+ int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */
+ int last_bytes; /* number of unsigned chars <3 in the last block */
+ int last_chars; /* number of characters <4 in the last block */
+ uint32_t b;
+
+ /* check input values */
+ if ((out == NULL) || (in == NULL)) {
+ DEBUG("ERROR: NULL POINTER AS OUTPUT IN BIN_TO_B64\n");
+ return -1;
+ }
+ if (size == 0) {
+ *out = 0; /* null string */
+ return 0;
+ }
+
+ /* calculate the number of base64 'blocks' */
+ full_blocks = size / 3;
+ last_bytes = size % 3;
+ switch (last_bytes) {
+ case 0: /* no byte left to encode */
+ last_chars = 0;
+ break;
+ case 1: /* 1 byte left to encode -> +2 chars */
+ last_chars = 2;
+ break;
+ case 2: /* 2 bytes left to encode -> +3 chars */
+ last_chars = 3;
+ break;
+ default:
+ CRIT("switch default that should not be possible");
+ }
+
+ /* check if output buffer is big enough */
+ result_len = (4*full_blocks) + last_chars;
+ if (max_len < (result_len + 1)) { /* 1 char added for string terminator */
+ DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN BIN_TO_B64\n");
+ return -1;
+ }
+
+ /* process all the full blocks */
+ for (i=0; i < full_blocks; ++i) {
+ b = (0xFF & in[3*i] ) << 16;
+ b |= (0xFF & in[3*i + 1]) << 8;
+ b |= 0xFF & in[3*i + 2];
+ out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
+ out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
+ out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F);
+ out[4*i + 3] = code_to_char( b & 0x3F);
+ }
+
+ /* process the last 'partial' block and terminate string */
+ i = full_blocks;
+ if (last_chars == 0) {
+ out[4*i] = 0; /* null character to terminate string */
+ } else if (last_chars == 2) {
+ b = (0xFF & in[3*i] ) << 16;
+ out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
+ out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
+ out[4*i + 2] = 0; /* null character to terminate string */
+ } else if (last_chars == 3) {
+ b = (0xFF & in[3*i] ) << 16;
+ b |= (0xFF & in[3*i + 1]) << 8;
+ out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
+ out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
+ out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F);
+ out[4*i + 3] = 0; /* null character to terminate string */
+ }
+
+ return result_len;
+}
+
+int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len) {
+ int i;
+ int result_len; /* size of the result */
+ int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */
+ int last_chars; /* number of characters <4 in the last block */
+ int last_bytes; /* number of unsigned chars <3 in the last block */
+ uint32_t b;
+ ;
+
+ /* check input values */
+ if ((out == NULL) || (in == NULL)) {
+ DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n");
+ return -1;
+ }
+ if (size == 0) {
+ return 0;
+ }
+
+ /* calculate the number of base64 'blocks' */
+ full_blocks = size / 4;
+ last_chars = size % 4;
+ switch (last_chars) {
+ case 0: /* no char left to decode */
+ last_bytes = 0;
+ break;
+ case 1: /* only 1 char left is an error */
+ DEBUG("ERROR: ONLY ONE CHAR LEFT IN B64_TO_BIN\n");
+ return -1;
+ case 2: /* 2 chars left to decode -> +1 byte */
+ last_bytes = 1;
+ break;
+ case 3: /* 3 chars left to decode -> +2 bytes */
+ last_bytes = 2;
+ break;
+ default:
+ CRIT("switch default that should not be possible");
+ }
+
+ /* check if output buffer is big enough */
+ result_len = (3*full_blocks) + last_bytes;
+ if (max_len < result_len) {
+ DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN B64_TO_BIN\n");
+ return -1;
+ }
+
+ /* process all the full blocks */
+ for (i=0; i < full_blocks; ++i) {
+ b = (0x3F & char_to_code(in[4*i] )) << 18;
+ b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
+ b |= (0x3F & char_to_code(in[4*i + 2])) << 6;
+ b |= 0x3F & char_to_code(in[4*i + 3]);
+ out[3*i + 0] = (b >> 16) & 0xFF;
+ out[3*i + 1] = (b >> 8 ) & 0xFF;
+ out[3*i + 2] = b & 0xFF;
+ }
+
+ /* process the last 'partial' block */
+ i = full_blocks;
+ if (last_bytes == 1) {
+ b = (0x3F & char_to_code(in[4*i] )) << 18;
+ b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
+ out[3*i + 0] = (b >> 16) & 0xFF;
+ if (((b >> 12) & 0x0F) != 0) {
+ DEBUG("WARNING: last character contains unusable bits\n");
+ }
+ } else if (last_bytes == 2) {
+ b = (0x3F & char_to_code(in[4*i] )) << 18;
+ b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
+ b |= (0x3F & char_to_code(in[4*i + 2])) << 6;
+ out[3*i + 0] = (b >> 16) & 0xFF;
+ out[3*i + 1] = (b >> 8 ) & 0xFF;
+ if (((b >> 6) & 0x03) != 0) {
+ DEBUG("WARNING: last character contains unusable bits\n");
+ }
+ }
+
+ return result_len;
+}
+
+int bin_to_b64(const uint8_t * in, int size, char * out, int max_len) {
+ int ret;
+
+ ret = bin_to_b64_nopad(in, size, out, max_len);
+
+ if (ret == -1) {
+ return -1;
+ }
+ switch (ret%4) {
+ case 0: /* nothing to do */
+ return ret;
+ case 1:
+ DEBUG("ERROR: INVALID UNPADDED BASE64 STRING\n");
+ return -1;
+ case 2: /* 2 chars in last block, must add 2 padding char */
+ if (max_len >= (ret + 2 + 1)) {
+ out[ret] = code_pad;
+ out[ret+1] = code_pad;
+ out[ret+2] = 0;
+ return ret+2;
+ } else {
+ DEBUG("ERROR: not enough room to add padding in bin_to_b64\n");
+ return -1;
+ }
+ case 3: /* 3 chars in last block, must add 1 padding char */
+ if (max_len >= (ret + 1 + 1)) {
+ out[ret] = code_pad;
+ out[ret+1] = 0;
+ return ret+1;
+ } else {
+ DEBUG("ERROR: not enough room to add padding in bin_to_b64\n");
+ return -1;
+ }
+ default:
+ CRIT("switch default that should not be possible");
+ }
+}
+
+int b64_to_bin(const char * in, int size, uint8_t * out, int max_len) {
+ if (in == NULL) {
+ DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n");
+ return -1;
+ }
+ if ((size%4 == 0) && (size >= 4)) { /* potentially padded Base64 */
+ if (in[size-2] == code_pad) { /* 2 padding char to ignore */
+ return b64_to_bin_nopad(in, size-2, out, max_len);
+ } else if (in[size-1] == code_pad) { /* 1 padding char to ignore */
+ return b64_to_bin_nopad(in, size-1, out, max_len);
+ } else { /* no padding to ignore */
+ return b64_to_bin_nopad(in, size, out, max_len);
+ }
+ } else { /* treat as unpadded Base64 */
+ return b64_to_bin_nopad(in, size, out, max_len);
+ }
+}
+
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/lora_pkt_fwd/src/jitqueue.c b/lora_pkt_fwd/src/jitqueue.c
new file mode 100644
index 0000000..dbde8d2
--- /dev/null
+++ b/lora_pkt_fwd/src/jitqueue.c
@@ -0,0 +1,465 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ LoRa concentrator : Just In Time TX scheduling queue
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+/* -------------------------------------------------------------------------- */
+/* --- 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, struct timeval *time, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e pkt_type) {
+ int i = 0;
+ uint32_t time_us = time->tv_sec * 1000000UL + time->tv_usec; /* convert time in µs */
+ 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, struct timeval *time, int *pkt_idx) {
+ /* Return index of node containing a packet inline with given time */
+ int i = 0;
+ int idx_highest_priority = -1;
+ uint32_t time_us;
+
+ if ((time == NULL) || (pkt_idx == NULL)) {
+ MSG("ERROR: invalid parameter\n");
+ return JIT_ERROR_INVALID;
+ }
+
+ if (jit_queue_is_empty(queue)) {
+ return JIT_ERROR_EMPTY;
+ }
+
+ time_us = time->tv_sec * 1000000UL + time->tv_usec;
+
+ 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);
+ }
+}
+
diff --git a/lora_pkt_fwd/src/lora_pkt_fwd.c b/lora_pkt_fwd/src/lora_pkt_fwd.c
new file mode 100644
index 0000000..801f28d
--- /dev/null
+++ b/lora_pkt_fwd/src/lora_pkt_fwd.c
@@ -0,0 +1,2889 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Configure Lora concentrator and forward packets to a server
+ Use GPS for packet timestamping.
+ Send a becon at a regular interval without server intervention
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+/* fix an issue between POSIX and C99 */
+#if __STDC_VERSION__ >= 199901L
+ #define _XOPEN_SOURCE 600
+#else
+ #define _XOPEN_SOURCE 500
+#endif
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+#include <stdio.h> /* printf, fprintf, snprintf, fopen, fputs */
+
+#include <string.h> /* memset */
+#include <signal.h> /* sigaction */
+#include <time.h> /* time, clock_gettime, strftime, gmtime */
+#include <sys/time.h> /* timeval */
+#include <unistd.h> /* getopt, access */
+#include <stdlib.h> /* atoi, exit */
+#include <errno.h> /* error messages */
+#include <math.h> /* modf */
+#include <assert.h>
+
+#include <sys/socket.h> /* socket specific definitions */
+#include <netinet/in.h> /* INET constants and stuff */
+#include <arpa/inet.h> /* IP address conversion stuff */
+#include <netdb.h> /* gai_strerror */
+
+#include <pthread.h>
+
+#include "trace.h"
+#include "jitqueue.h"
+#include "timersync.h"
+#include "parson.h"
+#include "base64.h"
+#include "loragw_hal.h"
+#include "loragw_gps.h"
+#include "loragw_aux.h"
+#include "loragw_reg.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#define STRINGIFY(x) #x
+#define STR(x) STRINGIFY(x)
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#ifndef VERSION_STRING
+ #define VERSION_STRING "undefined"
+#endif
+
+#define DEFAULT_SERVER 127.0.0.1 /* hostname also supported */
+#define DEFAULT_PORT_UP 1780
+#define DEFAULT_PORT_DW 1782
+#define DEFAULT_KEEPALIVE 5 /* default time interval for downstream keep-alive packet */
+#define DEFAULT_STAT 30 /* default time interval for statistics */
+#define PUSH_TIMEOUT_MS 100
+#define PULL_TIMEOUT_MS 200
+#define GPS_REF_MAX_AGE 30 /* maximum admitted delay in seconds of GPS loss before considering latest GPS sync unusable */
+#define FETCH_SLEEP_MS 10 /* nb of ms waited when a fetch return no packets */
+#define BEACON_POLL_MS 50 /* time in ms between polling of beacon TX status */
+
+#define PROTOCOL_VERSION 2 /* v1.3 */
+
+#define XERR_INIT_AVG 128 /* nb of measurements the XTAL correction is averaged on as initial value */
+#define XERR_FILT_COEF 256 /* coefficient for low-pass XTAL error tracking */
+
+#define PKT_PUSH_DATA 0
+#define PKT_PUSH_ACK 1
+#define PKT_PULL_DATA 2
+#define PKT_PULL_RESP 3
+#define PKT_PULL_ACK 4
+#define PKT_TX_ACK 5
+
+#define NB_PKT_MAX 8 /* max number of packets per fetch/send cycle */
+
+#define MIN_LORA_PREAMB 6 /* minimum Lora preamble length for this application */
+#define STD_LORA_PREAMB 8
+#define MIN_FSK_PREAMB 3 /* minimum FSK preamble length for this application */
+#define STD_FSK_PREAMB 5
+
+#define STATUS_SIZE 200
+#define TX_BUFF_SIZE ((540 * NB_PKT_MAX) + 30 + STATUS_SIZE)
+
+#define UNIX_GPS_EPOCH_OFFSET 315964800 /* Number of seconds ellapsed between 01.Jan.1970 00:00:00
+ and 06.Jan.1980 00:00:00 */
+
+#define DEFAULT_BEACON_FREQ_HZ 869525000
+#define DEFAULT_BEACON_FREQ_NB 1
+#define DEFAULT_BEACON_FREQ_STEP 0
+#define DEFAULT_BEACON_DATARATE 9
+#define DEFAULT_BEACON_BW_HZ 125000
+#define DEFAULT_BEACON_POWER 14
+#define DEFAULT_BEACON_INFODESC 0
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */
+
+/* signal handling variables */
+volatile bool exit_sig = false; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */
+volatile bool quit_sig = false; /* 1 -> application terminates without shutting down the hardware */
+
+/* packets filtering configuration variables */
+static bool fwd_valid_pkt = true; /* packets with PAYLOAD CRC OK are forwarded */
+static bool fwd_error_pkt = false; /* packets with PAYLOAD CRC ERROR are NOT forwarded */
+static bool fwd_nocrc_pkt = false; /* packets with NO PAYLOAD CRC are NOT forwarded */
+
+/* network configuration variables */
+static uint64_t lgwm = 0; /* Lora gateway MAC address */
+static char serv_addr[64] = STR(DEFAULT_SERVER); /* address of the server (host name or IPv4/IPv6) */
+static char serv_port_up[8] = STR(DEFAULT_PORT_UP); /* server port for upstream traffic */
+static char serv_port_down[8] = STR(DEFAULT_PORT_DW); /* server port for downstream traffic */
+static int keepalive_time = DEFAULT_KEEPALIVE; /* send a PULL_DATA request every X seconds, negative = disabled */
+
+/* statistics collection configuration variables */
+static unsigned stat_interval = DEFAULT_STAT; /* time interval (in sec) at which statistics are collected and displayed */
+
+/* gateway <-> MAC protocol variables */
+static uint32_t net_mac_h; /* Most Significant Nibble, network order */
+static uint32_t net_mac_l; /* Least Significant Nibble, network order */
+
+/* network sockets */
+static int sock_up; /* socket for upstream traffic */
+static int sock_down; /* socket for downstream traffic */
+
+/* network protocol variables */
+static struct timeval push_timeout_half = {0, (PUSH_TIMEOUT_MS * 500)}; /* cut in half, critical for throughput */
+static struct timeval pull_timeout = {0, (PULL_TIMEOUT_MS * 1000)}; /* non critical for throughput */
+
+/* hardware access control and correction */
+pthread_mutex_t mx_concent = PTHREAD_MUTEX_INITIALIZER; /* control access to the concentrator */
+static pthread_mutex_t mx_xcorr = PTHREAD_MUTEX_INITIALIZER; /* control access to the XTAL correction */
+static bool xtal_correct_ok = false; /* set true when XTAL correction is stable enough */
+static double xtal_correct = 1.0;
+
+/* GPS configuration and synchronization */
+static char gps_tty_path[64] = "\0"; /* path of the TTY port GPS is connected on */
+static int gps_tty_fd = -1; /* file descriptor of the GPS TTY port */
+static bool gps_enabled = false; /* is GPS enabled on that gateway ? */
+
+/* GPS time reference */
+static pthread_mutex_t mx_timeref = PTHREAD_MUTEX_INITIALIZER; /* control access to GPS time reference */
+static bool gps_ref_valid; /* is GPS reference acceptable (ie. not too old) */
+static struct tref time_reference_gps; /* time reference used for GPS <-> timestamp conversion */
+
+/* Reference coordinates, for broadcasting (beacon) */
+static struct coord_s reference_coord;
+
+/* Enable faking the GPS coordinates of the gateway */
+static bool gps_fake_enable; /* enable the feature */
+
+/* measurements to establish statistics */
+static pthread_mutex_t mx_meas_up = PTHREAD_MUTEX_INITIALIZER; /* control access to the upstream measurements */
+static uint32_t meas_nb_rx_rcv = 0; /* count packets received */
+static uint32_t meas_nb_rx_ok = 0; /* count packets received with PAYLOAD CRC OK */
+static uint32_t meas_nb_rx_bad = 0; /* count packets received with PAYLOAD CRC ERROR */
+static uint32_t meas_nb_rx_nocrc = 0; /* count packets received with NO PAYLOAD CRC */
+static uint32_t meas_up_pkt_fwd = 0; /* number of radio packet forwarded to the server */
+static uint32_t meas_up_network_byte = 0; /* sum of UDP bytes sent for upstream traffic */
+static uint32_t meas_up_payload_byte = 0; /* sum of radio payload bytes sent for upstream traffic */
+static uint32_t meas_up_dgram_sent = 0; /* number of datagrams sent for upstream traffic */
+static uint32_t meas_up_ack_rcv = 0; /* number of datagrams acknowledged for upstream traffic */
+
+static pthread_mutex_t mx_meas_dw = PTHREAD_MUTEX_INITIALIZER; /* control access to the downstream measurements */
+static uint32_t meas_dw_pull_sent = 0; /* number of PULL requests sent for downstream traffic */
+static uint32_t meas_dw_ack_rcv = 0; /* number of PULL requests acknowledged for downstream traffic */
+static uint32_t meas_dw_dgram_rcv = 0; /* count PULL response packets received for downstream traffic */
+static uint32_t meas_dw_network_byte = 0; /* sum of UDP bytes sent for upstream traffic */
+static uint32_t meas_dw_payload_byte = 0; /* sum of radio payload bytes sent for upstream traffic */
+static uint32_t meas_nb_tx_ok = 0; /* count packets emitted successfully */
+static uint32_t meas_nb_tx_fail = 0; /* count packets were TX failed for other reasons */
+static uint32_t meas_nb_tx_requested = 0; /* count TX request from server (downlinks) */
+static uint32_t meas_nb_tx_rejected_collision_packet = 0; /* count packets were TX request were rejected due to collision with another packet already programmed */
+static uint32_t meas_nb_tx_rejected_collision_beacon = 0; /* count packets were TX request were rejected due to collision with a beacon already programmed */
+static uint32_t meas_nb_tx_rejected_too_late = 0; /* count packets were TX request were rejected because it is too late to program it */
+static uint32_t meas_nb_tx_rejected_too_early = 0; /* count packets were TX request were rejected because timestamp is too much in advance */
+static uint32_t meas_nb_beacon_queued = 0; /* count beacon inserted in jit queue */
+static uint32_t meas_nb_beacon_sent = 0; /* count beacon actually sent to concentrator */
+static uint32_t meas_nb_beacon_rejected = 0; /* count beacon rejected for queuing */
+
+static pthread_mutex_t mx_meas_gps = PTHREAD_MUTEX_INITIALIZER; /* control access to the GPS statistics */
+static bool gps_coord_valid; /* could we get valid GPS coordinates ? */
+static struct coord_s meas_gps_coord; /* GPS position of the gateway */
+static struct coord_s meas_gps_err; /* GPS position of the gateway */
+
+static pthread_mutex_t mx_stat_rep = PTHREAD_MUTEX_INITIALIZER; /* control access to the status report */
+static bool report_ready = false; /* true when there is a new report to send to the server */
+static char status_report[STATUS_SIZE]; /* status report as a JSON object */
+
+/* beacon parameters */
+static uint32_t beacon_period = 0; /* set beaconing period, must be a sub-multiple of 86400, the nb of sec in a day */
+static uint32_t beacon_freq_hz = DEFAULT_BEACON_FREQ_HZ; /* set beacon TX frequency, in Hz */
+static uint8_t beacon_freq_nb = DEFAULT_BEACON_FREQ_NB; /* set number of beaconing channels beacon */
+static uint32_t beacon_freq_step = DEFAULT_BEACON_FREQ_STEP; /* set frequency step between beacon channels, in Hz */
+static uint8_t beacon_datarate = DEFAULT_BEACON_DATARATE; /* set beacon datarate (SF) */
+static uint32_t beacon_bw_hz = DEFAULT_BEACON_BW_HZ; /* set beacon bandwidth, in Hz */
+static int8_t beacon_power = DEFAULT_BEACON_POWER; /* set beacon TX power, in dBm */
+static uint8_t beacon_infodesc = DEFAULT_BEACON_INFODESC; /* set beacon information descriptor */
+
+/* auto-quit function */
+static uint32_t autoquit_threshold = 0; /* enable auto-quit after a number of non-acknowledged PULL_DATA (0 = disabled)*/
+
+/* Just In Time TX scheduling */
+static struct jit_queue_s jit_queue;
+
+/* Gateway specificities */
+static int8_t antenna_gain = 0;
+
+/* TX capabilities */
+static struct lgw_tx_gain_lut_s txlut; /* TX gain table */
+static uint32_t tx_freq_min[LGW_RF_CHAIN_NB]; /* lowest frequency supported by TX chain */
+static uint32_t tx_freq_max[LGW_RF_CHAIN_NB]; /* highest frequency supported by TX chain */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
+
+static void sig_handler(int sigio);
+
+static int parse_SX1301_configuration(const char * conf_file);
+
+static int parse_gateway_configuration(const char * conf_file);
+
+static uint16_t crc16(const uint8_t * data, unsigned size);
+
+static double difftimespec(struct timespec end, struct timespec beginning);
+
+static void gps_process_sync(void);
+
+static void gps_process_coords(void);
+
+/* threads */
+void thread_up(void);
+void thread_down(void);
+void thread_gps(void);
+void thread_valid(void);
+void thread_jit(void);
+void thread_timersync(void);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+static void sig_handler(int sigio) {
+ if (sigio == SIGQUIT) {
+ quit_sig = true;
+ } else if ((sigio == SIGINT) || (sigio == SIGTERM)) {
+ exit_sig = true;
+ }
+ return;
+}
+
+static int parse_SX1301_configuration(const char * conf_file) {
+ int i;
+ char param_name[32]; /* used to generate variable parameter names */
+ const char *str; /* used to store string value from JSON object */
+ const char conf_obj_name[] = "SX1301_conf";
+ JSON_Value *root_val = NULL;
+ JSON_Object *conf_obj = NULL;
+ JSON_Object *conf_lbt_obj = NULL;
+ JSON_Object *conf_lbtchan_obj = NULL;
+ JSON_Value *val = NULL;
+ JSON_Array *conf_array = NULL;
+ struct lgw_conf_board_s boardconf;
+ struct lgw_conf_lbt_s lbtconf;
+ struct lgw_conf_rxrf_s rfconf;
+ struct lgw_conf_rxif_s ifconf;
+ uint32_t sf, bw, fdev;
+
+ /* try to parse JSON */
+ root_val = json_parse_file_with_comments(conf_file);
+ if (root_val == NULL) {
+ MSG("ERROR: %s is not a valid JSON file\n", conf_file);
+ exit(EXIT_FAILURE);
+ }
+
+ /* point to the gateway configuration object */
+ conf_obj = json_object_get_object(json_value_get_object(root_val), conf_obj_name);
+ if (conf_obj == NULL) {
+ MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj_name);
+ return -1;
+ } else {
+ MSG("INFO: %s does contain a JSON object named %s, parsing SX1301 parameters\n", conf_file, conf_obj_name);
+ }
+
+ /* set board configuration */
+ memset(&boardconf, 0, sizeof boardconf); /* initialize configuration structure */
+ val = json_object_get_value(conf_obj, "lorawan_public"); /* fetch value (if possible) */
+ if (json_value_get_type(val) == JSONBoolean) {
+ boardconf.lorawan_public = (bool)json_value_get_boolean(val);
+ } else {
+ MSG("WARNING: Data type for lorawan_public seems wrong, please check\n");
+ boardconf.lorawan_public = false;
+ }
+ val = json_object_get_value(conf_obj, "clksrc"); /* fetch value (if possible) */
+ if (json_value_get_type(val) == JSONNumber) {
+ boardconf.clksrc = (uint8_t)json_value_get_number(val);
+ } else {
+ MSG("WARNING: Data type for clksrc seems wrong, please check\n");
+ boardconf.clksrc = 0;
+ }
+ MSG("INFO: lorawan_public %d, clksrc %d\n", boardconf.lorawan_public, boardconf.clksrc);
+ /* all parameters parsed, submitting configuration to the HAL */
+ if (lgw_board_setconf(boardconf) != LGW_HAL_SUCCESS) {
+ MSG("ERROR: Failed to configure board\n");
+ return -1;
+ }
+
+ /* set LBT configuration */
+ memset(&lbtconf, 0, sizeof lbtconf); /* initialize configuration structure */
+ conf_lbt_obj = json_object_get_object(conf_obj, "lbt_cfg"); /* fetch value (if possible) */
+ if (conf_lbt_obj == NULL) {
+ MSG("INFO: no configuration for LBT\n");
+ } else {
+ val = json_object_get_value(conf_lbt_obj, "enable"); /* fetch value (if possible) */
+ if (json_value_get_type(val) == JSONBoolean) {
+ lbtconf.enable = (bool)json_value_get_boolean(val);
+ } else {
+ MSG("WARNING: Data type for lbt_cfg.enable seems wrong, please check\n");
+ lbtconf.enable = false;
+ }
+ if (lbtconf.enable == true) {
+ val = json_object_get_value(conf_lbt_obj, "rssi_target"); /* fetch value (if possible) */
+ if (json_value_get_type(val) == JSONNumber) {
+ lbtconf.rssi_target = (int8_t)json_value_get_number(val);
+ } else {
+ MSG("WARNING: Data type for lbt_cfg.rssi_target seems wrong, please check\n");
+ lbtconf.rssi_target = 0;
+ }
+ val = json_object_get_value(conf_lbt_obj, "sx127x_rssi_offset"); /* fetch value (if possible) */
+ if (json_value_get_type(val) == JSONNumber) {
+ lbtconf.rssi_offset = (int8_t)json_value_get_number(val);
+ } else {
+ MSG("WARNING: Data type for lbt_cfg.sx127x_rssi_offset seems wrong, please check\n");
+ lbtconf.rssi_offset = 0;
+ }
+ /* set LBT channels configuration */
+ conf_array = json_object_get_array(conf_lbt_obj, "chan_cfg");
+ if (conf_array != NULL) {
+ lbtconf.nb_channel = json_array_get_count( conf_array );
+ MSG("INFO: %u LBT channels configured\n", lbtconf.nb_channel);
+ }
+ for (i = 0; i < (int)lbtconf.nb_channel; i++) {
+ /* Sanity check */
+ if (i >= LBT_CHANNEL_FREQ_NB)
+ {
+ MSG("ERROR: LBT channel %d not supported, skip it\n", i );
+ break;
+ }
+ /* Get LBT channel configuration object from array */
+ conf_lbtchan_obj = json_array_get_object(conf_array, i);
+
+ /* Channel frequency */
+ val = json_object_dotget_value(conf_lbtchan_obj, "freq_hz"); /* fetch value (if possible) */
+ if (json_value_get_type(val) == JSONNumber) {
+ lbtconf.channels[i].freq_hz = (uint32_t)json_value_get_number(val);
+ } else {
+ MSG("WARNING: Data type for lbt_cfg.channels[%d].freq_hz seems wrong, please check\n", i);
+ lbtconf.channels[i].freq_hz = 0;
+ }
+
+ /* Channel scan time */
+ val = json_object_dotget_value(conf_lbtchan_obj, "scan_time_us"); /* fetch value (if possible) */
+ if (json_value_get_type(val) == JSONNumber) {
+ lbtconf.channels[i].scan_time_us = (uint16_t)json_value_get_number(val);
+ } else {
+ MSG("WARNING: Data type for lbt_cfg.channels[%d].scan_time_us seems wrong, please check\n", i);
+ lbtconf.channels[i].scan_time_us = 0;
+ }
+ }
+
+ /* all parameters parsed, submitting configuration to the HAL */
+ if (lgw_lbt_setconf(lbtconf) != LGW_HAL_SUCCESS) {
+ MSG("ERROR: Failed to configure LBT\n");
+ return -1;
+ }
+ } else {
+ MSG("INFO: LBT is disabled\n");
+ }
+ }
+
+ /* set antenna gain configuration */
+ val = json_object_get_value(conf_obj, "antenna_gain"); /* fetch value (if possible) */
+ if (val != NULL) {
+ if (json_value_get_type(val) == JSONNumber) {
+ antenna_gain = (int8_t)json_value_get_number(val);
+ } else {
+ MSG("WARNING: Data type for antenna_gain seems wrong, please check\n");
+ antenna_gain = 0;
+ }
+ }
+ MSG("INFO: antenna_gain %d dBi\n", antenna_gain);
+
+ /* set configuration for tx gains */
+ memset(&txlut, 0, sizeof txlut); /* initialize configuration structure */
+ for (i = 0; i < TX_GAIN_LUT_SIZE_MAX; i++) {
+ snprintf(param_name, sizeof param_name, "tx_lut_%i", i); /* compose parameter path inside JSON structure */
+ val = json_object_get_value(conf_obj, param_name); /* fetch value (if possible) */
+ if (json_value_get_type(val) != JSONObject) {
+ MSG("INFO: no configuration for tx gain lut %i\n", i);
+ continue;
+ }
+ txlut.size++; /* update TX LUT size based on JSON object found in configuration file */
+ /* there is an object to configure that TX gain index, let's parse it */
+ snprintf(param_name, sizeof param_name, "tx_lut_%i.pa_gain", i);
+ val = json_object_dotget_value(conf_obj, param_name);
+ if (json_value_get_type(val) == JSONNumber) {
+ txlut.lut[i].pa_gain = (uint8_t)json_value_get_number(val);
+ } else {
+ MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i);
+ txlut.lut[i].pa_gain = 0;
+ }
+ snprintf(param_name, sizeof param_name, "tx_lut_%i.dac_gain", i);
+ val = json_object_dotget_value(conf_obj, param_name);
+ if (json_value_get_type(val) == JSONNumber) {
+ txlut.lut[i].dac_gain = (uint8_t)json_value_get_number(val);
+ } else {
+ txlut.lut[i].dac_gain = 3; /* This is the only dac_gain supported for now */
+ }
+ snprintf(param_name, sizeof param_name, "tx_lut_%i.dig_gain", i);
+ val = json_object_dotget_value(conf_obj, param_name);
+ if (json_value_get_type(val) == JSONNumber) {
+ txlut.lut[i].dig_gain = (uint8_t)json_value_get_number(val);
+ } else {
+ MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i);
+ txlut.lut[i].dig_gain = 0;
+ }
+ snprintf(param_name, sizeof param_name, "tx_lut_%i.mix_gain", i);
+ val = json_object_dotget_value(conf_obj, param_name);
+ if (json_value_get_type(val) == JSONNumber) {
+ txlut.lut[i].mix_gain = (uint8_t)json_value_get_number(val);
+ } else {
+ MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i);
+ txlut.lut[i].mix_gain = 0;
+ }
+ snprintf(param_name, sizeof param_name, "tx_lut_%i.rf_power", i);
+ val = json_object_dotget_value(conf_obj, param_name);
+ if (json_value_get_type(val) == JSONNumber) {
+ txlut.lut[i].rf_power = (int8_t)json_value_get_number(val);
+ } else {
+ MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i);
+ txlut.lut[i].rf_power = 0;
+ }
+ }
+ /* all parameters parsed, submitting configuration to the HAL */
+ if (txlut.size > 0) {
+ MSG("INFO: Configuring TX LUT with %u indexes\n", txlut.size);
+ if (lgw_txgain_setconf(&txlut) != LGW_HAL_SUCCESS) {
+ MSG("ERROR: Failed to configure concentrator TX Gain LUT\n");
+ return -1;
+ }
+ } else {
+ MSG("WARNING: No TX gain LUT defined\n");
+ }
+
+ /* set configuration for RF chains */
+ for (i = 0; i < LGW_RF_CHAIN_NB; ++i) {
+ memset(&rfconf, 0, sizeof rfconf); /* initialize configuration structure */
+ snprintf(param_name, sizeof param_name, "radio_%i", i); /* compose parameter path inside JSON structure */
+ val = json_object_get_value(conf_obj, param_name); /* fetch value (if possible) */
+ if (json_value_get_type(val) != JSONObject) {
+ MSG("INFO: no configuration for radio %i\n", i);
+ continue;
+ }
+ /* there is an object to configure that radio, let's parse it */
+ snprintf(param_name, sizeof param_name, "radio_%i.enable", i);
+ val = json_object_dotget_value(conf_obj, param_name);
+ if (json_value_get_type(val) == JSONBoolean) {
+ rfconf.enable = (bool)json_value_get_boolean(val);
+ } else {
+ rfconf.enable = false;
+ }
+ if (rfconf.enable == false) { /* radio disabled, nothing else to parse */
+ MSG("INFO: radio %i disabled\n", i);
+ } else { /* radio enabled, will parse the other parameters */
+ snprintf(param_name, sizeof param_name, "radio_%i.freq", i);
+ rfconf.freq_hz = (uint32_t)json_object_dotget_number(conf_obj, param_name);
+ snprintf(param_name, sizeof param_name, "radio_%i.rssi_offset", i);
+ rfconf.rssi_offset = (float)json_object_dotget_number(conf_obj, param_name);
+ snprintf(param_name, sizeof param_name, "radio_%i.type", i);
+ str = json_object_dotget_string(conf_obj, param_name);
+ if (!strncmp(str, "SX1255", 6)) {
+ rfconf.type = LGW_RADIO_TYPE_SX1255;
+ } else if (!strncmp(str, "SX1257", 6)) {
+ rfconf.type = LGW_RADIO_TYPE_SX1257;
+ } else {
+ MSG("WARNING: invalid radio type: %s (should be SX1255 or SX1257)\n", str);
+ }
+ snprintf(param_name, sizeof param_name, "radio_%i.tx_enable", i);
+ val = json_object_dotget_value(conf_obj, param_name);
+ if (json_value_get_type(val) == JSONBoolean) {
+ rfconf.tx_enable = (bool)json_value_get_boolean(val);
+ if (rfconf.tx_enable == true) {
+ /* tx is enabled on this rf chain, we need its frequency range */
+ snprintf(param_name, sizeof param_name, "radio_%i.tx_freq_min", i);
+ tx_freq_min[i] = (uint32_t)json_object_dotget_number(conf_obj, param_name);
+ snprintf(param_name, sizeof param_name, "radio_%i.tx_freq_max", i);
+ tx_freq_max[i] = (uint32_t)json_object_dotget_number(conf_obj, param_name);
+ if ((tx_freq_min[i] == 0) || (tx_freq_max[i] == 0)) {
+ MSG("WARNING: no frequency range specified for TX rf chain %d\n", i);
+ }
+ /* ... and the notch filter frequency to be set */
+ snprintf(param_name, sizeof param_name, "radio_%i.tx_notch_freq", i);
+ rfconf.tx_notch_freq = (uint32_t)json_object_dotget_number(conf_obj, param_name);
+ }
+ } else {
+ rfconf.tx_enable = false;
+ }
+ MSG("INFO: radio %i enabled (type %s), center frequency %u, RSSI offset %f, tx enabled %d, tx_notch_freq %u\n", i, str, rfconf.freq_hz, rfconf.rssi_offset, rfconf.tx_enable, rfconf.tx_notch_freq);
+ }
+ /* all parameters parsed, submitting configuration to the HAL */
+ if (lgw_rxrf_setconf(i, rfconf) != LGW_HAL_SUCCESS) {
+ MSG("ERROR: invalid configuration for radio %i\n", i);
+ return -1;
+ }
+ }
+
+ /* set configuration for Lora multi-SF channels (bandwidth cannot be set) */
+ for (i = 0; i < LGW_MULTI_NB; ++i) {
+ memset(&ifconf, 0, sizeof ifconf); /* initialize configuration structure */
+ snprintf(param_name, sizeof param_name, "chan_multiSF_%i", i); /* compose parameter path inside JSON structure */
+ val = json_object_get_value(conf_obj, param_name); /* fetch value (if possible) */
+ if (json_value_get_type(val) != JSONObject) {
+ MSG("INFO: no configuration for Lora multi-SF channel %i\n", i);
+ continue;
+ }
+ /* there is an object to configure that Lora multi-SF channel, let's parse it */
+ snprintf(param_name, sizeof param_name, "chan_multiSF_%i.enable", i);
+ val = json_object_dotget_value(conf_obj, param_name);
+ if (json_value_get_type(val) == JSONBoolean) {
+ ifconf.enable = (bool)json_value_get_boolean(val);
+ } else {
+ ifconf.enable = false;
+ }
+ if (ifconf.enable == false) { /* Lora multi-SF channel disabled, nothing else to parse */
+ MSG("INFO: Lora multi-SF channel %i disabled\n", i);
+ } else { /* Lora multi-SF channel enabled, will parse the other parameters */
+ snprintf(param_name, sizeof param_name, "chan_multiSF_%i.radio", i);
+ ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf_obj, param_name);
+ snprintf(param_name, sizeof param_name, "chan_multiSF_%i.if", i);
+ ifconf.freq_hz = (int32_t)json_object_dotget_number(conf_obj, param_name);
+ // TODO: handle individual SF enabling and disabling (spread_factor)
+ MSG("INFO: Lora multi-SF channel %i> radio %i, IF %i Hz, 125 kHz bw, SF 7 to 12\n", i, ifconf.rf_chain, ifconf.freq_hz);
+ }
+ /* all parameters parsed, submitting configuration to the HAL */
+ if (lgw_rxif_setconf(i, ifconf) != LGW_HAL_SUCCESS) {
+ MSG("ERROR: invalid configuration for Lora multi-SF channel %i\n", i);
+ return -1;
+ }
+ }
+
+ /* set configuration for Lora standard channel */
+ memset(&ifconf, 0, sizeof ifconf); /* initialize configuration structure */
+ val = json_object_get_value(conf_obj, "chan_Lora_std"); /* fetch value (if possible) */
+ if (json_value_get_type(val) != JSONObject) {
+ MSG("INFO: no configuration for Lora standard channel\n");
+ } else {
+ val = json_object_dotget_value(conf_obj, "chan_Lora_std.enable");
+ if (json_value_get_type(val) == JSONBoolean) {
+ ifconf.enable = (bool)json_value_get_boolean(val);
+ } else {
+ ifconf.enable = false;
+ }
+ if (ifconf.enable == false) {
+ MSG("INFO: Lora standard channel %i disabled\n", i);
+ } else {
+ ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.radio");
+ ifconf.freq_hz = (int32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.if");
+ bw = (uint32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.bandwidth");
+ switch(bw) {
+ case 500000: ifconf.bandwidth = BW_500KHZ; break;
+ case 250000: ifconf.bandwidth = BW_250KHZ; break;
+ case 125000: ifconf.bandwidth = BW_125KHZ; break;
+ default: ifconf.bandwidth = BW_UNDEFINED;
+ }
+ sf = (uint32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.spread_factor");
+ switch(sf) {
+ case 7: ifconf.datarate = DR_LORA_SF7; break;
+ case 8: ifconf.datarate = DR_LORA_SF8; break;
+ case 9: ifconf.datarate = DR_LORA_SF9; break;
+ case 10: ifconf.datarate = DR_LORA_SF10; break;
+ case 11: ifconf.datarate = DR_LORA_SF11; break;
+ case 12: ifconf.datarate = DR_LORA_SF12; break;
+ default: ifconf.datarate = DR_UNDEFINED;
+ }
+ MSG("INFO: Lora std channel> radio %i, IF %i Hz, %u Hz bw, SF %u\n", ifconf.rf_chain, ifconf.freq_hz, bw, sf);
+ }
+ if (lgw_rxif_setconf(8, ifconf) != LGW_HAL_SUCCESS) {
+ MSG("ERROR: invalid configuration for Lora standard channel\n");
+ return -1;
+ }
+ }
+
+ /* set configuration for FSK channel */
+ memset(&ifconf, 0, sizeof ifconf); /* initialize configuration structure */
+ val = json_object_get_value(conf_obj, "chan_FSK"); /* fetch value (if possible) */
+ if (json_value_get_type(val) != JSONObject) {
+ MSG("INFO: no configuration for FSK channel\n");
+ } else {
+ val = json_object_dotget_value(conf_obj, "chan_FSK.enable");
+ if (json_value_get_type(val) == JSONBoolean) {
+ ifconf.enable = (bool)json_value_get_boolean(val);
+ } else {
+ ifconf.enable = false;
+ }
+ if (ifconf.enable == false) {
+ MSG("INFO: FSK channel %i disabled\n", i);
+ } else {
+ ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.radio");
+ ifconf.freq_hz = (int32_t)json_object_dotget_number(conf_obj, "chan_FSK.if");
+ bw = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.bandwidth");
+ fdev = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.freq_deviation");
+ ifconf.datarate = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.datarate");
+
+ /* if chan_FSK.bandwidth is set, it has priority over chan_FSK.freq_deviation */
+ if ((bw == 0) && (fdev != 0)) {
+ bw = 2 * fdev + ifconf.datarate;
+ }
+ if (bw == 0) ifconf.bandwidth = BW_UNDEFINED;
+ else if (bw <= 7800) ifconf.bandwidth = BW_7K8HZ;
+ else if (bw <= 15600) ifconf.bandwidth = BW_15K6HZ;
+ else if (bw <= 31200) ifconf.bandwidth = BW_31K2HZ;
+ else if (bw <= 62500) ifconf.bandwidth = BW_62K5HZ;
+ else if (bw <= 125000) ifconf.bandwidth = BW_125KHZ;
+ else if (bw <= 250000) ifconf.bandwidth = BW_250KHZ;
+ else if (bw <= 500000) ifconf.bandwidth = BW_500KHZ;
+ else ifconf.bandwidth = BW_UNDEFINED;
+
+ MSG("INFO: FSK channel> radio %i, IF %i Hz, %u Hz bw, %u bps datarate\n", ifconf.rf_chain, ifconf.freq_hz, bw, ifconf.datarate);
+ }
+ if (lgw_rxif_setconf(9, ifconf) != LGW_HAL_SUCCESS) {
+ MSG("ERROR: invalid configuration for FSK channel\n");
+ return -1;
+ }
+ }
+ json_value_free(root_val);
+
+ return 0;
+}
+
+static int parse_gateway_configuration(const char * conf_file) {
+ const char conf_obj_name[] = "gateway_conf";
+ JSON_Value *root_val;
+ JSON_Object *conf_obj = NULL;
+ JSON_Value *val = NULL; /* needed to detect the absence of some fields */
+ const char *str; /* pointer to sub-strings in the JSON data */
+ unsigned long long ull = 0;
+
+ /* try to parse JSON */
+ root_val = json_parse_file_with_comments(conf_file);
+ if (root_val == NULL) {
+ MSG("ERROR: %s is not a valid JSON file\n", conf_file);
+ exit(EXIT_FAILURE);
+ }
+
+ /* point to the gateway configuration object */
+ conf_obj = json_object_get_object(json_value_get_object(root_val), conf_obj_name);
+ if (conf_obj == NULL) {
+ MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj_name);
+ return -1;
+ } else {
+ MSG("INFO: %s does contain a JSON object named %s, parsing gateway parameters\n", conf_file, conf_obj_name);
+ }
+
+ /* gateway unique identifier (aka MAC address) (optional) */
+ str = json_object_get_string(conf_obj, "gateway_ID");
+ if (str != NULL) {
+ sscanf(str, "%llx", &ull);
+ lgwm = ull;
+ MSG("INFO: gateway MAC address is configured to %016llX\n", ull);
+ }
+
+ /* server hostname or IP address (optional) */
+ str = json_object_get_string(conf_obj, "server_address");
+ if (str != NULL) {
+ strncpy(serv_addr, str, sizeof serv_addr);
+ MSG("INFO: server hostname or IP address is configured to \"%s\"\n", serv_addr);
+ }
+
+ /* get up and down ports (optional) */
+ val = json_object_get_value(conf_obj, "serv_port_up");
+ if (val != NULL) {
+ snprintf(serv_port_up, sizeof serv_port_up, "%u", (uint16_t)json_value_get_number(val));
+ MSG("INFO: upstream port is configured to \"%s\"\n", serv_port_up);
+ }
+ val = json_object_get_value(conf_obj, "serv_port_down");
+ if (val != NULL) {
+ snprintf(serv_port_down, sizeof serv_port_down, "%u", (uint16_t)json_value_get_number(val));
+ MSG("INFO: downstream port is configured to \"%s\"\n", serv_port_down);
+ }
+
+ /* get keep-alive interval (in seconds) for downstream (optional) */
+ val = json_object_get_value(conf_obj, "keepalive_interval");
+ if (val != NULL) {
+ keepalive_time = (int)json_value_get_number(val);
+ MSG("INFO: downstream keep-alive interval is configured to %u seconds\n", keepalive_time);
+ }
+
+ /* get interval (in seconds) for statistics display (optional) */
+ val = json_object_get_value(conf_obj, "stat_interval");
+ if (val != NULL) {
+ stat_interval = (unsigned)json_value_get_number(val);
+ MSG("INFO: statistics display interval is configured to %u seconds\n", stat_interval);
+ }
+
+ /* get time-out value (in ms) for upstream datagrams (optional) */
+ val = json_object_get_value(conf_obj, "push_timeout_ms");
+ if (val != NULL) {
+ push_timeout_half.tv_usec = 500 * (long int)json_value_get_number(val);
+ MSG("INFO: upstream PUSH_DATA time-out is configured to %u ms\n", (unsigned)(push_timeout_half.tv_usec / 500));
+ }
+
+ /* packet filtering parameters */
+ val = json_object_get_value(conf_obj, "forward_crc_valid");
+ if (json_value_get_type(val) == JSONBoolean) {
+ fwd_valid_pkt = (bool)json_value_get_boolean(val);
+ }
+ MSG("INFO: packets received with a valid CRC will%s be forwarded\n", (fwd_valid_pkt ? "" : " NOT"));
+ val = json_object_get_value(conf_obj, "forward_crc_error");
+ if (json_value_get_type(val) == JSONBoolean) {
+ fwd_error_pkt = (bool)json_value_get_boolean(val);
+ }
+ MSG("INFO: packets received with a CRC error will%s be forwarded\n", (fwd_error_pkt ? "" : " NOT"));
+ val = json_object_get_value(conf_obj, "forward_crc_disabled");
+ if (json_value_get_type(val) == JSONBoolean) {
+ fwd_nocrc_pkt = (bool)json_value_get_boolean(val);
+ }
+ MSG("INFO: packets received with no CRC will%s be forwarded\n", (fwd_nocrc_pkt ? "" : " NOT"));
+
+ /* GPS module TTY path (optional) */
+ str = json_object_get_string(conf_obj, "gps_tty_path");
+ if (str != NULL) {
+ strncpy(gps_tty_path, str, sizeof gps_tty_path);
+ MSG("INFO: GPS serial port path is configured to \"%s\"\n", gps_tty_path);
+ }
+
+ /* get reference coordinates */
+ val = json_object_get_value(conf_obj, "ref_latitude");
+ if (val != NULL) {
+ reference_coord.lat = (double)json_value_get_number(val);
+ MSG("INFO: Reference latitude is configured to %f deg\n", reference_coord.lat);
+ }
+ val = json_object_get_value(conf_obj, "ref_longitude");
+ if (val != NULL) {
+ reference_coord.lon = (double)json_value_get_number(val);
+ MSG("INFO: Reference longitude is configured to %f deg\n", reference_coord.lon);
+ }
+ val = json_object_get_value(conf_obj, "ref_altitude");
+ if (val != NULL) {
+ reference_coord.alt = (short)json_value_get_number(val);
+ MSG("INFO: Reference altitude is configured to %i meters\n", reference_coord.alt);
+ }
+
+ /* Gateway GPS coordinates hardcoding (aka. faking) option */
+ val = json_object_get_value(conf_obj, "fake_gps");
+ if (json_value_get_type(val) == JSONBoolean) {
+ gps_fake_enable = (bool)json_value_get_boolean(val);
+ if (gps_fake_enable == true) {
+ MSG("INFO: fake GPS is enabled\n");
+ } else {
+ MSG("INFO: fake GPS is disabled\n");
+ }
+ }
+
+ /* Beacon signal period (optional) */
+ val = json_object_get_value(conf_obj, "beacon_period");
+ if (val != NULL) {
+ beacon_period = (uint32_t)json_value_get_number(val);
+ if ((beacon_period > 0) && (beacon_period < 6)) {
+ MSG("ERROR: invalid configuration for Beacon period, must be >= 6s\n");
+ return -1;
+ } else {
+ MSG("INFO: Beaconing period is configured to %u seconds\n", beacon_period);
+ }
+ }
+
+ /* Beacon TX frequency (optional) */
+ val = json_object_get_value(conf_obj, "beacon_freq_hz");
+ if (val != NULL) {
+ beacon_freq_hz = (uint32_t)json_value_get_number(val);
+ MSG("INFO: Beaconing signal will be emitted at %u Hz\n", beacon_freq_hz);
+ }
+
+ /* Number of beacon channels (optional) */
+ val = json_object_get_value(conf_obj, "beacon_freq_nb");
+ if (val != NULL) {
+ beacon_freq_nb = (uint8_t)json_value_get_number(val);
+ MSG("INFO: Beaconing channel number is set to %u\n", beacon_freq_nb);
+ }
+
+ /* Frequency step between beacon channels (optional) */
+ val = json_object_get_value(conf_obj, "beacon_freq_step");
+ if (val != NULL) {
+ beacon_freq_step = (uint32_t)json_value_get_number(val);
+ MSG("INFO: Beaconing channel frequency step is set to %uHz\n", beacon_freq_step);
+ }
+
+ /* Beacon datarate (optional) */
+ val = json_object_get_value(conf_obj, "beacon_datarate");
+ if (val != NULL) {
+ beacon_datarate = (uint8_t)json_value_get_number(val);
+ MSG("INFO: Beaconing datarate is set to SF%d\n", beacon_datarate);
+ }
+
+ /* Beacon modulation bandwidth (optional) */
+ val = json_object_get_value(conf_obj, "beacon_bw_hz");
+ if (val != NULL) {
+ beacon_bw_hz = (uint32_t)json_value_get_number(val);
+ MSG("INFO: Beaconing modulation bandwidth is set to %dHz\n", beacon_bw_hz);
+ }
+
+ /* Beacon TX power (optional) */
+ val = json_object_get_value(conf_obj, "beacon_power");
+ if (val != NULL) {
+ beacon_power = (int8_t)json_value_get_number(val);
+ MSG("INFO: Beaconing TX power is set to %ddBm\n", beacon_power);
+ }
+
+ /* Beacon information descriptor (optional) */
+ val = json_object_get_value(conf_obj, "beacon_infodesc");
+ if (val != NULL) {
+ beacon_infodesc = (uint8_t)json_value_get_number(val);
+ MSG("INFO: Beaconing information descriptor is set to %u\n", beacon_infodesc);
+ }
+
+ /* Auto-quit threshold (optional) */
+ val = json_object_get_value(conf_obj, "autoquit_threshold");
+ if (val != NULL) {
+ autoquit_threshold = (uint32_t)json_value_get_number(val);
+ MSG("INFO: Auto-quit after %u non-acknowledged PULL_DATA\n", autoquit_threshold);
+ }
+
+ /* free JSON parsing data structure */
+ json_value_free(root_val);
+ return 0;
+}
+
+static uint16_t crc16(const uint8_t * data, unsigned size) {
+ const uint16_t crc_poly = 0x1021;
+ const uint16_t init_val = 0x0000;
+ uint16_t x = init_val;
+ unsigned i, j;
+
+ if (data == NULL) {
+ return 0;
+ }
+
+ for (i=0; i<size; ++i) {
+ x ^= (uint16_t)data[i] << 8;
+ for (j=0; j<8; ++j) {
+ x = (x & 0x8000) ? (x<<1) ^ crc_poly : (x<<1);
+ }
+ }
+
+ return x;
+}
+
+static double difftimespec(struct timespec end, struct timespec beginning) {
+ double x;
+
+ x = 1E-9 * (double)(end.tv_nsec - beginning.tv_nsec);
+ x += (double)(end.tv_sec - beginning.tv_sec);
+
+ return x;
+}
+
+static int send_tx_ack(uint8_t token_h, uint8_t token_l, enum jit_error_e error) {
+ uint8_t buff_ack[64]; /* buffer to give feedback to server */
+ int buff_index;
+
+ /* reset buffer */
+ memset(&buff_ack, 0, sizeof buff_ack);
+
+ /* Prepare downlink feedback to be sent to server */
+ buff_ack[0] = PROTOCOL_VERSION;
+ buff_ack[1] = token_h;
+ buff_ack[2] = token_l;
+ buff_ack[3] = PKT_TX_ACK;
+ *(uint32_t *)(buff_ack + 4) = net_mac_h;
+ *(uint32_t *)(buff_ack + 8) = net_mac_l;
+ buff_index = 12; /* 12-byte header */
+
+ /* Put no JSON string if there is nothing to report */
+ if (error != JIT_ERROR_OK) {
+ /* start of JSON structure */
+ memcpy((void *)(buff_ack + buff_index), (void *)"{\"txpk_ack\":{", 13);
+ buff_index += 13;
+ /* set downlink error status in JSON structure */
+ memcpy((void *)(buff_ack + buff_index), (void *)"\"error\":", 8);
+ buff_index += 8;
+ switch (error) {
+ case JIT_ERROR_FULL:
+ case JIT_ERROR_COLLISION_PACKET:
+ memcpy((void *)(buff_ack + buff_index), (void *)"\"COLLISION_PACKET\"", 18);
+ buff_index += 18;
+ /* update stats */
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_nb_tx_rejected_collision_packet += 1;
+ pthread_mutex_unlock(&mx_meas_dw);
+ break;
+ case JIT_ERROR_TOO_LATE:
+ memcpy((void *)(buff_ack + buff_index), (void *)"\"TOO_LATE\"", 10);
+ buff_index += 10;
+ /* update stats */
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_nb_tx_rejected_too_late += 1;
+ pthread_mutex_unlock(&mx_meas_dw);
+ break;
+ case JIT_ERROR_TOO_EARLY:
+ memcpy((void *)(buff_ack + buff_index), (void *)"\"TOO_EARLY\"", 11);
+ buff_index += 11;
+ /* update stats */
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_nb_tx_rejected_too_early += 1;
+ pthread_mutex_unlock(&mx_meas_dw);
+ break;
+ case JIT_ERROR_COLLISION_BEACON:
+ memcpy((void *)(buff_ack + buff_index), (void *)"\"COLLISION_BEACON\"", 18);
+ buff_index += 18;
+ /* update stats */
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_nb_tx_rejected_collision_beacon += 1;
+ pthread_mutex_unlock(&mx_meas_dw);
+ break;
+ case JIT_ERROR_TX_FREQ:
+ memcpy((void *)(buff_ack + buff_index), (void *)"\"TX_FREQ\"", 9);
+ buff_index += 9;
+ break;
+ case JIT_ERROR_TX_POWER:
+ memcpy((void *)(buff_ack + buff_index), (void *)"\"TX_POWER\"", 10);
+ buff_index += 10;
+ break;
+ case JIT_ERROR_GPS_UNLOCKED:
+ memcpy((void *)(buff_ack + buff_index), (void *)"\"GPS_UNLOCKED\"", 14);
+ buff_index += 14;
+ break;
+ default:
+ memcpy((void *)(buff_ack + buff_index), (void *)"\"UNKNOWN\"", 9);
+ buff_index += 9;
+ break;
+ }
+ /* end of JSON structure */
+ memcpy((void *)(buff_ack + buff_index), (void *)"}}", 2);
+ buff_index += 2;
+ }
+
+ buff_ack[buff_index] = 0; /* add string terminator, for safety */
+
+ /* send datagram to server */
+ return send(sock_down, (void *)buff_ack, buff_index, 0);
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- MAIN FUNCTION -------------------------------------------------------- */
+
+int main(void)
+{
+ struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */
+ int i; /* loop variable and temporary variable for return value */
+ int x;
+
+ /* configuration file related */
+ char *global_cfg_path= "global_conf.json"; /* contain global (typ. network-wide) configuration */
+ char *local_cfg_path = "local_conf.json"; /* contain node specific configuration, overwrite global parameters for parameters that are defined in both */
+ char *debug_cfg_path = "debug_conf.json"; /* if present, all other configuration files are ignored */
+
+ /* threads */
+ pthread_t thrid_up;
+ pthread_t thrid_down;
+ pthread_t thrid_gps;
+ pthread_t thrid_valid;
+ pthread_t thrid_jit;
+ pthread_t thrid_timersync;
+
+ /* network socket creation */
+ struct addrinfo hints;
+ struct addrinfo *result; /* store result of getaddrinfo */
+ struct addrinfo *q; /* pointer to move into *result data */
+ char host_name[64];
+ char port_name[64];
+
+ /* variables to get local copies of measurements */
+ uint32_t cp_nb_rx_rcv;
+ uint32_t cp_nb_rx_ok;
+ uint32_t cp_nb_rx_bad;
+ uint32_t cp_nb_rx_nocrc;
+ uint32_t cp_up_pkt_fwd;
+ uint32_t cp_up_network_byte;
+ uint32_t cp_up_payload_byte;
+ uint32_t cp_up_dgram_sent;
+ uint32_t cp_up_ack_rcv;
+ uint32_t cp_dw_pull_sent;
+ uint32_t cp_dw_ack_rcv;
+ uint32_t cp_dw_dgram_rcv;
+ uint32_t cp_dw_network_byte;
+ uint32_t cp_dw_payload_byte;
+ uint32_t cp_nb_tx_ok;
+ uint32_t cp_nb_tx_fail;
+ uint32_t cp_nb_tx_requested = 0;
+ uint32_t cp_nb_tx_rejected_collision_packet = 0;
+ uint32_t cp_nb_tx_rejected_collision_beacon = 0;
+ uint32_t cp_nb_tx_rejected_too_late = 0;
+ uint32_t cp_nb_tx_rejected_too_early = 0;
+ uint32_t cp_nb_beacon_queued = 0;
+ uint32_t cp_nb_beacon_sent = 0;
+ uint32_t cp_nb_beacon_rejected = 0;
+
+ /* GPS coordinates variables */
+ bool coord_ok = false;
+ struct coord_s cp_gps_coord = {0.0, 0.0, 0};
+
+ /* SX1301 data variables */
+ uint32_t trig_tstamp;
+
+ /* statistics variable */
+ time_t t;
+ char stat_timestamp[24];
+ float rx_ok_ratio;
+ float rx_bad_ratio;
+ float rx_nocrc_ratio;
+ float up_ack_ratio;
+ float dw_ack_ratio;
+
+ /* display version informations */
+ MSG("*** Beacon Packet Forwarder for Lora Gateway ***\nVersion: " VERSION_STRING "\n");
+ MSG("*** Lora concentrator HAL library version info ***\n%s\n***\n", lgw_version_info());
+
+ /* display host endianness */
+ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ MSG("INFO: Little endian host\n");
+ #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+ MSG("INFO: Big endian host\n");
+ #else
+ MSG("INFO: Host endianness unknown\n");
+ #endif
+
+ /* load configuration files */
+ if (access(debug_cfg_path, R_OK) == 0) { /* if there is a debug conf, parse only the debug conf */
+ MSG("INFO: found debug configuration file %s, parsing it\n", debug_cfg_path);
+ MSG("INFO: other configuration files will be ignored\n");
+ x = parse_SX1301_configuration(debug_cfg_path);
+ if (x != 0) {
+ exit(EXIT_FAILURE);
+ }
+ x = parse_gateway_configuration(debug_cfg_path);
+ if (x != 0) {
+ exit(EXIT_FAILURE);
+ }
+ } else if (access(global_cfg_path, R_OK) == 0) { /* if there is a global conf, parse it and then try to parse local conf */
+ MSG("INFO: found global configuration file %s, parsing it\n", global_cfg_path);
+ x = parse_SX1301_configuration(global_cfg_path);
+ if (x != 0) {
+ exit(EXIT_FAILURE);
+ }
+ x = parse_gateway_configuration(global_cfg_path);
+ if (x != 0) {
+ exit(EXIT_FAILURE);
+ }
+ if (access(local_cfg_path, R_OK) == 0) {
+ MSG("INFO: found local configuration file %s, parsing it\n", local_cfg_path);
+ MSG("INFO: redefined parameters will overwrite global parameters\n");
+ parse_SX1301_configuration(local_cfg_path);
+ parse_gateway_configuration(local_cfg_path);
+ }
+ } else if (access(local_cfg_path, R_OK) == 0) { /* if there is only a local conf, parse it and that's all */
+ MSG("INFO: found local configuration file %s, parsing it\n", local_cfg_path);
+ x = parse_SX1301_configuration(local_cfg_path);
+ if (x != 0) {
+ exit(EXIT_FAILURE);
+ }
+ x = parse_gateway_configuration(local_cfg_path);
+ if (x != 0) {
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ MSG("ERROR: [main] failed to find any configuration file named %s, %s OR %s\n", global_cfg_path, local_cfg_path, debug_cfg_path);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Start GPS a.s.a.p., to allow it to lock */
+ if (gps_tty_path[0] != '\0') { /* do not try to open GPS device if no path set */
+ i = lgw_gps_enable(gps_tty_path, "ubx7", 0, &gps_tty_fd); /* HAL only supports u-blox 7 for now */
+ if (i != LGW_GPS_SUCCESS) {
+ printf("WARNING: [main] impossible to open %s for GPS sync (check permissions)\n", gps_tty_path);
+ gps_enabled = false;
+ gps_ref_valid = false;
+ } else {
+ printf("INFO: [main] TTY port %s open for GPS synchronization\n", gps_tty_path);
+ gps_enabled = true;
+ gps_ref_valid = false;
+ }
+ }
+
+ /* get timezone info */
+ tzset();
+
+ /* sanity check on configuration variables */
+ // TODO
+
+ /* process some of the configuration variables */
+ net_mac_h = htonl((uint32_t)(0xFFFFFFFF & (lgwm>>32)));
+ net_mac_l = htonl((uint32_t)(0xFFFFFFFF & lgwm ));
+
+ /* prepare hints to open network sockets */
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_INET; /* WA: Forcing IPv4 as AF_UNSPEC makes connection on localhost to fail */
+ hints.ai_socktype = SOCK_DGRAM;
+
+ /* look for server address w/ upstream port */
+ i = getaddrinfo(serv_addr, serv_port_up, &hints, &result);
+ if (i != 0) {
+ MSG("ERROR: [up] getaddrinfo on address %s (PORT %s) returned %s\n", serv_addr, serv_port_up, gai_strerror(i));
+ exit(EXIT_FAILURE);
+ }
+
+ /* try to open socket for upstream traffic */
+ for (q=result; q!=NULL; q=q->ai_next) {
+ sock_up = socket(q->ai_family, q->ai_socktype,q->ai_protocol);
+ if (sock_up == -1) continue; /* try next field */
+ else break; /* success, get out of loop */
+ }
+ if (q == NULL) {
+ MSG("ERROR: [up] failed to open socket to any of server %s addresses (port %s)\n", serv_addr, serv_port_up);
+ i = 1;
+ for (q=result; q!=NULL; q=q->ai_next) {
+ getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
+ MSG("INFO: [up] result %i host:%s service:%s\n", i, host_name, port_name);
+ ++i;
+ }
+ exit(EXIT_FAILURE);
+ }
+
+ /* connect so we can send/receive packet with the server only */
+ i = connect(sock_up, q->ai_addr, q->ai_addrlen);
+ if (i != 0) {
+ MSG("ERROR: [up] connect returned %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ freeaddrinfo(result);
+
+ /* look for server address w/ downstream port */
+ i = getaddrinfo(serv_addr, serv_port_down, &hints, &result);
+ if (i != 0) {
+ MSG("ERROR: [down] getaddrinfo on address %s (port %s) returned %s\n", serv_addr, serv_port_up, gai_strerror(i));
+ exit(EXIT_FAILURE);
+ }
+
+ /* try to open socket for downstream traffic */
+ for (q=result; q!=NULL; q=q->ai_next) {
+ sock_down = socket(q->ai_family, q->ai_socktype,q->ai_protocol);
+ if (sock_down == -1) continue; /* try next field */
+ else break; /* success, get out of loop */
+ }
+ if (q == NULL) {
+ MSG("ERROR: [down] failed to open socket to any of server %s addresses (port %s)\n", serv_addr, serv_port_up);
+ i = 1;
+ for (q=result; q!=NULL; q=q->ai_next) {
+ getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
+ MSG("INFO: [down] result %i host:%s service:%s\n", i, host_name, port_name);
+ ++i;
+ }
+ exit(EXIT_FAILURE);
+ }
+
+ /* connect so we can send/receive packet with the server only */
+ i = connect(sock_down, q->ai_addr, q->ai_addrlen);
+ if (i != 0) {
+ MSG("ERROR: [down] connect returned %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ freeaddrinfo(result);
+
+ /* starting the concentrator */
+ i = lgw_start();
+ if (i == LGW_HAL_SUCCESS) {
+ MSG("INFO: [main] concentrator started, packet can now be received\n");
+ } else {
+ MSG("ERROR: [main] failed to start the concentrator\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* spawn threads to manage upstream and downstream */
+ i = pthread_create( &thrid_up, NULL, (void * (*)(void *))thread_up, NULL);
+ if (i != 0) {
+ MSG("ERROR: [main] impossible to create upstream thread\n");
+ exit(EXIT_FAILURE);
+ }
+ i = pthread_create( &thrid_down, NULL, (void * (*)(void *))thread_down, NULL);
+ if (i != 0) {
+ MSG("ERROR: [main] impossible to create downstream thread\n");
+ exit(EXIT_FAILURE);
+ }
+ i = pthread_create( &thrid_jit, NULL, (void * (*)(void *))thread_jit, NULL);
+ if (i != 0) {
+ MSG("ERROR: [main] impossible to create JIT thread\n");
+ exit(EXIT_FAILURE);
+ }
+ i = pthread_create( &thrid_timersync, NULL, (void * (*)(void *))thread_timersync, NULL);
+ if (i != 0) {
+ MSG("ERROR: [main] impossible to create Timer Sync thread\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* spawn thread to manage GPS */
+ if (gps_enabled == true) {
+ i = pthread_create( &thrid_gps, NULL, (void * (*)(void *))thread_gps, NULL);
+ if (i != 0) {
+ MSG("ERROR: [main] impossible to create GPS thread\n");
+ exit(EXIT_FAILURE);
+ }
+ i = pthread_create( &thrid_valid, NULL, (void * (*)(void *))thread_valid, NULL);
+ if (i != 0) {
+ MSG("ERROR: [main] impossible to create validation thread\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* configure signal handling */
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigact.sa_handler = sig_handler;
+ sigaction(SIGQUIT, &sigact, NULL); /* Ctrl-\ */
+ sigaction(SIGINT, &sigact, NULL); /* Ctrl-C */
+ sigaction(SIGTERM, &sigact, NULL); /* default "kill" command */
+
+ /* main loop task : statistics collection */
+ while (!exit_sig && !quit_sig) {
+ /* wait for next reporting interval */
+ wait_ms(1000 * stat_interval);
+
+ /* get timestamp for statistics */
+ t = time(NULL);
+ strftime(stat_timestamp, sizeof stat_timestamp, "%F %T %Z", gmtime(&t));
+
+ /* access upstream statistics, copy and reset them */
+ pthread_mutex_lock(&mx_meas_up);
+ cp_nb_rx_rcv = meas_nb_rx_rcv;
+ cp_nb_rx_ok = meas_nb_rx_ok;
+ cp_nb_rx_bad = meas_nb_rx_bad;
+ cp_nb_rx_nocrc = meas_nb_rx_nocrc;
+ cp_up_pkt_fwd = meas_up_pkt_fwd;
+ cp_up_network_byte = meas_up_network_byte;
+ cp_up_payload_byte = meas_up_payload_byte;
+ cp_up_dgram_sent = meas_up_dgram_sent;
+ cp_up_ack_rcv = meas_up_ack_rcv;
+ meas_nb_rx_rcv = 0;
+ meas_nb_rx_ok = 0;
+ meas_nb_rx_bad = 0;
+ meas_nb_rx_nocrc = 0;
+ meas_up_pkt_fwd = 0;
+ meas_up_network_byte = 0;
+ meas_up_payload_byte = 0;
+ meas_up_dgram_sent = 0;
+ meas_up_ack_rcv = 0;
+ pthread_mutex_unlock(&mx_meas_up);
+ if (cp_nb_rx_rcv > 0) {
+ rx_ok_ratio = (float)cp_nb_rx_ok / (float)cp_nb_rx_rcv;
+ rx_bad_ratio = (float)cp_nb_rx_bad / (float)cp_nb_rx_rcv;
+ rx_nocrc_ratio = (float)cp_nb_rx_nocrc / (float)cp_nb_rx_rcv;
+ } else {
+ rx_ok_ratio = 0.0;
+ rx_bad_ratio = 0.0;
+ rx_nocrc_ratio = 0.0;
+ }
+ if (cp_up_dgram_sent > 0) {
+ up_ack_ratio = (float)cp_up_ack_rcv / (float)cp_up_dgram_sent;
+ } else {
+ up_ack_ratio = 0.0;
+ }
+
+ /* access downstream statistics, copy and reset them */
+ pthread_mutex_lock(&mx_meas_dw);
+ cp_dw_pull_sent = meas_dw_pull_sent;
+ cp_dw_ack_rcv = meas_dw_ack_rcv;
+ cp_dw_dgram_rcv = meas_dw_dgram_rcv;
+ cp_dw_network_byte = meas_dw_network_byte;
+ cp_dw_payload_byte = meas_dw_payload_byte;
+ cp_nb_tx_ok = meas_nb_tx_ok;
+ cp_nb_tx_fail = meas_nb_tx_fail;
+ cp_nb_tx_requested += meas_nb_tx_requested;
+ cp_nb_tx_rejected_collision_packet += meas_nb_tx_rejected_collision_packet;
+ cp_nb_tx_rejected_collision_beacon += meas_nb_tx_rejected_collision_beacon;
+ cp_nb_tx_rejected_too_late += meas_nb_tx_rejected_too_late;
+ cp_nb_tx_rejected_too_early += meas_nb_tx_rejected_too_early;
+ cp_nb_beacon_queued += meas_nb_beacon_queued;
+ cp_nb_beacon_sent += meas_nb_beacon_sent;
+ cp_nb_beacon_rejected += meas_nb_beacon_rejected;
+ meas_dw_pull_sent = 0;
+ meas_dw_ack_rcv = 0;
+ meas_dw_dgram_rcv = 0;
+ meas_dw_network_byte = 0;
+ meas_dw_payload_byte = 0;
+ meas_nb_tx_ok = 0;
+ meas_nb_tx_fail = 0;
+ meas_nb_tx_requested = 0;
+ meas_nb_tx_rejected_collision_packet = 0;
+ meas_nb_tx_rejected_collision_beacon = 0;
+ meas_nb_tx_rejected_too_late = 0;
+ meas_nb_tx_rejected_too_early = 0;
+ meas_nb_beacon_queued = 0;
+ meas_nb_beacon_sent = 0;
+ meas_nb_beacon_rejected = 0;
+ pthread_mutex_unlock(&mx_meas_dw);
+ if (cp_dw_pull_sent > 0) {
+ dw_ack_ratio = (float)cp_dw_ack_rcv / (float)cp_dw_pull_sent;
+ } else {
+ dw_ack_ratio = 0.0;
+ }
+
+ /* access GPS statistics, copy them */
+ if (gps_enabled == true) {
+ pthread_mutex_lock(&mx_meas_gps);
+ coord_ok = gps_coord_valid;
+ cp_gps_coord = meas_gps_coord;
+ pthread_mutex_unlock(&mx_meas_gps);
+ }
+
+ /* overwrite with reference coordinates if function is enabled */
+ if (gps_fake_enable == true) {
+ cp_gps_coord = reference_coord;
+ }
+
+ /* display a report */
+ printf("\n##### %s #####\n", stat_timestamp);
+ printf("### [UPSTREAM] ###\n");
+ printf("# RF packets received by concentrator: %u\n", cp_nb_rx_rcv);
+ printf("# CRC_OK: %.2f%%, CRC_FAIL: %.2f%%, NO_CRC: %.2f%%\n", 100.0 * rx_ok_ratio, 100.0 * rx_bad_ratio, 100.0 * rx_nocrc_ratio);
+ printf("# RF packets forwarded: %u (%u bytes)\n", cp_up_pkt_fwd, cp_up_payload_byte);
+ printf("# PUSH_DATA datagrams sent: %u (%u bytes)\n", cp_up_dgram_sent, cp_up_network_byte);
+ printf("# PUSH_DATA acknowledged: %.2f%%\n", 100.0 * up_ack_ratio);
+ printf("### [DOWNSTREAM] ###\n");
+ printf("# PULL_DATA sent: %u (%.2f%% acknowledged)\n", cp_dw_pull_sent, 100.0 * dw_ack_ratio);
+ printf("# PULL_RESP(onse) datagrams received: %u (%u bytes)\n", cp_dw_dgram_rcv, cp_dw_network_byte);
+ printf("# RF packets sent to concentrator: %u (%u bytes)\n", (cp_nb_tx_ok+cp_nb_tx_fail), cp_dw_payload_byte);
+ printf("# TX errors: %u\n", cp_nb_tx_fail);
+ if (cp_nb_tx_requested != 0 ) {
+ printf("# TX rejected (collision packet): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_collision_packet / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_collision_packet);
+ printf("# TX rejected (collision beacon): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_collision_beacon / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_collision_beacon);
+ printf("# TX rejected (too late): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_too_late / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_too_late);
+ printf("# TX rejected (too early): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_too_early / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_too_early);
+ }
+ printf("# BEACON queued: %u\n", cp_nb_beacon_queued);
+ printf("# BEACON sent so far: %u\n", cp_nb_beacon_sent);
+ printf("# BEACON rejected: %u\n", cp_nb_beacon_rejected);
+ printf("### [JIT] ###\n");
+ /* get timestamp captured on PPM pulse */
+ pthread_mutex_lock(&mx_concent);
+ i = lgw_get_trigcnt(&trig_tstamp);
+ pthread_mutex_unlock(&mx_concent);
+ if (i != LGW_HAL_SUCCESS) {
+ printf("# SX1301 time (PPS): unknown\n");
+ } else {
+ printf("# SX1301 time (PPS): %u\n", trig_tstamp);
+ }
+ jit_print_queue (&jit_queue, false, DEBUG_LOG);
+ printf("### [GPS] ###\n");
+ if (gps_enabled == true) {
+ /* no need for mutex, display is not critical */
+ if (gps_ref_valid == true) {
+ printf("# Valid time reference (age: %li sec)\n", (long)difftime(time(NULL), time_reference_gps.systime));
+ } else {
+ printf("# Invalid time reference (age: %li sec)\n", (long)difftime(time(NULL), time_reference_gps.systime));
+ }
+ if (coord_ok == true) {
+ printf("# GPS coordinates: latitude %.5f, longitude %.5f, altitude %i m\n", cp_gps_coord.lat, cp_gps_coord.lon, cp_gps_coord.alt);
+ } else {
+ printf("# no valid GPS coordinates available yet\n");
+ }
+ } else if (gps_fake_enable == true) {
+ printf("# GPS *FAKE* coordinates: latitude %.5f, longitude %.5f, altitude %i m\n", cp_gps_coord.lat, cp_gps_coord.lon, cp_gps_coord.alt);
+ } else {
+ printf("# GPS sync is disabled\n");
+ }
+ printf("##### END #####\n");
+
+ /* generate a JSON report (will be sent to server by upstream thread) */
+ pthread_mutex_lock(&mx_stat_rep);
+ if (((gps_enabled == true) && (coord_ok == true)) || (gps_fake_enable == true)) {
+ snprintf(status_report, STATUS_SIZE, "\"stat\":{\"time\":\"%s\",\"lati\":%.5f,\"long\":%.5f,\"alti\":%i,\"rxnb\":%u,\"rxok\":%u,\"rxfw\":%u,\"ackr\":%.1f,\"dwnb\":%u,\"txnb\":%u}", stat_timestamp, cp_gps_coord.lat, cp_gps_coord.lon, cp_gps_coord.alt, cp_nb_rx_rcv, cp_nb_rx_ok, cp_up_pkt_fwd, 100.0 * up_ack_ratio, cp_dw_dgram_rcv, cp_nb_tx_ok);
+ } else {
+ snprintf(status_report, STATUS_SIZE, "\"stat\":{\"time\":\"%s\",\"rxnb\":%u,\"rxok\":%u,\"rxfw\":%u,\"ackr\":%.1f,\"dwnb\":%u,\"txnb\":%u}", stat_timestamp, cp_nb_rx_rcv, cp_nb_rx_ok, cp_up_pkt_fwd, 100.0 * up_ack_ratio, cp_dw_dgram_rcv, cp_nb_tx_ok);
+ }
+ report_ready = true;
+ pthread_mutex_unlock(&mx_stat_rep);
+ }
+
+ /* wait for upstream thread to finish (1 fetch cycle max) */
+ pthread_join(thrid_up, NULL);
+ pthread_cancel(thrid_down); /* don't wait for downstream thread */
+ pthread_cancel(thrid_jit); /* don't wait for jit thread */
+ pthread_cancel(thrid_timersync); /* don't wait for timer sync thread */
+ if (gps_enabled == true) {
+ pthread_cancel(thrid_gps); /* don't wait for GPS thread */
+ pthread_cancel(thrid_valid); /* don't wait for validation thread */
+
+ i = lgw_gps_disable(gps_tty_fd);
+ if (i == LGW_HAL_SUCCESS) {
+ MSG("INFO: GPS closed successfully\n");
+ } else {
+ MSG("WARNING: failed to close GPS successfully\n");
+ }
+ }
+
+ /* if an exit signal was received, try to quit properly */
+ if (exit_sig) {
+ /* shut down network sockets */
+ shutdown(sock_up, SHUT_RDWR);
+ shutdown(sock_down, SHUT_RDWR);
+ /* stop the hardware */
+ i = lgw_stop();
+ if (i == LGW_HAL_SUCCESS) {
+ MSG("INFO: concentrator stopped successfully\n");
+ } else {
+ MSG("WARNING: failed to stop concentrator successfully\n");
+ }
+ }
+
+ MSG("INFO: Exiting packet forwarder program\n");
+ exit(EXIT_SUCCESS);
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- THREAD 1: RECEIVING PACKETS AND FORWARDING THEM ---------------------- */
+
+void thread_up(void) {
+ int i, j; /* loop variables */
+ unsigned pkt_in_dgram; /* nb on Lora packet in the current datagram */
+
+ /* allocate memory for packet fetching and processing */
+ struct lgw_pkt_rx_s rxpkt[NB_PKT_MAX]; /* array containing inbound packets + metadata */
+ struct lgw_pkt_rx_s *p; /* pointer on a RX packet */
+ int nb_pkt;
+
+ /* local copy of GPS time reference */
+ bool ref_ok = false; /* determine if GPS time reference must be used or not */
+ struct tref local_ref; /* time reference used for UTC <-> timestamp conversion */
+
+ /* data buffers */
+ uint8_t buff_up[TX_BUFF_SIZE]; /* buffer to compose the upstream packet */
+ int buff_index;
+ uint8_t buff_ack[32]; /* buffer to receive acknowledges */
+
+ /* protocol variables */
+ uint8_t token_h; /* random token for acknowledgement matching */
+ uint8_t token_l; /* random token for acknowledgement matching */
+
+ /* ping measurement variables */
+ struct timespec send_time;
+ struct timespec recv_time;
+
+ /* GPS synchronization variables */
+ struct timespec pkt_utc_time;
+ struct tm * x; /* broken-up UTC time */
+ struct timespec pkt_gps_time;
+ uint64_t pkt_gps_time_ms;
+
+ /* report management variable */
+ bool send_report = false;
+
+ /* mote info variables */
+ uint32_t mote_addr = 0;
+ uint16_t mote_fcnt = 0;
+
+ /* set upstream socket RX timeout */
+ i = setsockopt(sock_up, SOL_SOCKET, SO_RCVTIMEO, (void *)&push_timeout_half, sizeof push_timeout_half);
+ if (i != 0) {
+ MSG("ERROR: [up] setsockopt returned %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ /* pre-fill the data buffer with fixed fields */
+ buff_up[0] = PROTOCOL_VERSION;
+ buff_up[3] = PKT_PUSH_DATA;
+ *(uint32_t *)(buff_up + 4) = net_mac_h;
+ *(uint32_t *)(buff_up + 8) = net_mac_l;
+
+ while (!exit_sig && !quit_sig) {
+
+ /* fetch packets */
+ pthread_mutex_lock(&mx_concent);
+ nb_pkt = lgw_receive(NB_PKT_MAX, rxpkt);
+ pthread_mutex_unlock(&mx_concent);
+ if (nb_pkt == LGW_HAL_ERROR) {
+ MSG("ERROR: [up] failed packet fetch, exiting\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* check if there are status report to send */
+ send_report = report_ready; /* copy the variable so it doesn't change mid-function */
+ /* no mutex, we're only reading */
+
+ /* wait a short time if no packets, nor status report */
+ if ((nb_pkt == 0) && (send_report == false)) {
+ wait_ms(FETCH_SLEEP_MS);
+ continue;
+ }
+
+ /* get a copy of GPS time reference (avoid 1 mutex per packet) */
+ if ((nb_pkt > 0) && (gps_enabled == true)) {
+ pthread_mutex_lock(&mx_timeref);
+ ref_ok = gps_ref_valid;
+ local_ref = time_reference_gps;
+ pthread_mutex_unlock(&mx_timeref);
+ } else {
+ ref_ok = false;
+ }
+
+ /* start composing datagram with the header */
+ token_h = (uint8_t)rand(); /* random token */
+ token_l = (uint8_t)rand(); /* random token */
+ buff_up[1] = token_h;
+ buff_up[2] = token_l;
+ buff_index = 12; /* 12-byte header */
+
+ /* start of JSON structure */
+ memcpy((void *)(buff_up + buff_index), (void *)"{\"rxpk\":[", 9);
+ buff_index += 9;
+
+ /* serialize Lora packets metadata and payload */
+ pkt_in_dgram = 0;
+ for (i=0; i < nb_pkt; ++i) {
+ p = &rxpkt[i];
+
+ /* Get mote information from current packet (addr, fcnt) */
+ /* FHDR - DevAddr */
+ mote_addr = p->payload[1];
+ mote_addr |= p->payload[2] << 8;
+ mote_addr |= p->payload[3] << 16;
+ mote_addr |= p->payload[4] << 24;
+ /* FHDR - FCnt */
+ mote_fcnt = p->payload[6];
+ mote_fcnt |= p->payload[7] << 8;
+
+ /* basic packet filtering */
+ pthread_mutex_lock(&mx_meas_up);
+ meas_nb_rx_rcv += 1;
+ switch(p->status) {
+ case STAT_CRC_OK:
+ meas_nb_rx_ok += 1;
+ printf( "\nINFO: Received pkt from mote: %08X (fcnt=%u)\n", mote_addr, mote_fcnt );
+ if (!fwd_valid_pkt) {
+ pthread_mutex_unlock(&mx_meas_up);
+ continue; /* skip that packet */
+ }
+ break;
+ case STAT_CRC_BAD:
+ meas_nb_rx_bad += 1;
+ if (!fwd_error_pkt) {
+ pthread_mutex_unlock(&mx_meas_up);
+ continue; /* skip that packet */
+ }
+ break;
+ case STAT_NO_CRC:
+ meas_nb_rx_nocrc += 1;
+ if (!fwd_nocrc_pkt) {
+ pthread_mutex_unlock(&mx_meas_up);
+ continue; /* skip that packet */
+ }
+ break;
+ default:
+ MSG("WARNING: [up] received packet with unknown status %u (size %u, modulation %u, BW %u, DR %u, RSSI %.1f)\n", p->status, p->size, p->modulation, p->bandwidth, p->datarate, p->rssi);
+ pthread_mutex_unlock(&mx_meas_up);
+ continue; /* skip that packet */
+ // exit(EXIT_FAILURE);
+ }
+ meas_up_pkt_fwd += 1;
+ meas_up_payload_byte += p->size;
+ pthread_mutex_unlock(&mx_meas_up);
+
+ /* Start of packet, add inter-packet separator if necessary */
+ if (pkt_in_dgram == 0) {
+ buff_up[buff_index] = '{';
+ ++buff_index;
+ } else {
+ buff_up[buff_index] = ',';
+ buff_up[buff_index+1] = '{';
+ buff_index += 2;
+ }
+
+ /* RAW timestamp, 8-17 useful chars */
+ j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, "\"tmst\":%u", p->count_us);
+ if (j > 0) {
+ buff_index += j;
+ } else {
+ MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Packet RX time (GPS based), 37 useful chars */
+ if (ref_ok == true) {
+ /* convert packet timestamp to UTC absolute time */
+ j = lgw_cnt2utc(local_ref, p->count_us, &pkt_utc_time);
+ if (j == LGW_GPS_SUCCESS) {
+ /* split the UNIX timestamp to its calendar components */
+ x = gmtime(&(pkt_utc_time.tv_sec));
+ j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"time\":\"%04i-%02i-%02iT%02i:%02i:%02i.%06liZ\"", (x->tm_year)+1900, (x->tm_mon)+1, x->tm_mday, x->tm_hour, x->tm_min, x->tm_sec, (pkt_utc_time.tv_nsec)/1000); /* ISO 8601 format */
+ if (j > 0) {
+ buff_index += j;
+ } else {
+ MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+ }
+ /* convert packet timestamp to GPS absolute time */
+ j = lgw_cnt2gps(local_ref, p->count_us, &pkt_gps_time);
+ if (j == LGW_GPS_SUCCESS) {
+ pkt_gps_time_ms = pkt_gps_time.tv_sec * 1E3 + pkt_gps_time.tv_nsec / 1E6;
+ j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"tmms\":%llu",
+ pkt_gps_time_ms); /* GPS time in milliseconds since 06.Jan.1980 */
+ if (j > 0) {
+ buff_index += j;
+ } else {
+ MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
+ /* Packet concentrator channel, RF chain & RX frequency, 34-36 useful chars */
+ j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"chan\":%1u,\"rfch\":%1u,\"freq\":%.6lf", p->if_chain, p->rf_chain, ((double)p->freq_hz / 1e6));
+ if (j > 0) {
+ buff_index += j;
+ } else {
+ MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Packet status, 9-10 useful chars */
+ switch (p->status) {
+ case STAT_CRC_OK:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":1", 9);
+ buff_index += 9;
+ break;
+ case STAT_CRC_BAD:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":-1", 10);
+ buff_index += 10;
+ break;
+ case STAT_NO_CRC:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":0", 9);
+ buff_index += 9;
+ break;
+ default:
+ MSG("ERROR: [up] received packet with unknown status\n");
+ memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":?", 9);
+ buff_index += 9;
+ exit(EXIT_FAILURE);
+ }
+
+ /* Packet modulation, 13-14 useful chars */
+ if (p->modulation == MOD_LORA) {
+ memcpy((void *)(buff_up + buff_index), (void *)",\"modu\":\"LORA\"", 14);
+ buff_index += 14;
+
+ /* Lora datarate & bandwidth, 16-19 useful chars */
+ switch (p->datarate) {
+ case DR_LORA_SF7:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF7", 12);
+ buff_index += 12;
+ break;
+ case DR_LORA_SF8:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF8", 12);
+ buff_index += 12;
+ break;
+ case DR_LORA_SF9:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF9", 12);
+ buff_index += 12;
+ break;
+ case DR_LORA_SF10:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF10", 13);
+ buff_index += 13;
+ break;
+ case DR_LORA_SF11:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF11", 13);
+ buff_index += 13;
+ break;
+ case DR_LORA_SF12:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF12", 13);
+ buff_index += 13;
+ break;
+ default:
+ MSG("ERROR: [up] lora packet with unknown datarate\n");
+ memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF?", 12);
+ buff_index += 12;
+ exit(EXIT_FAILURE);
+ }
+ switch (p->bandwidth) {
+ case BW_125KHZ:
+ memcpy((void *)(buff_up + buff_index), (void *)"BW125\"", 6);
+ buff_index += 6;
+ break;
+ case BW_250KHZ:
+ memcpy((void *)(buff_up + buff_index), (void *)"BW250\"", 6);
+ buff_index += 6;
+ break;
+ case BW_500KHZ:
+ memcpy((void *)(buff_up + buff_index), (void *)"BW500\"", 6);
+ buff_index += 6;
+ break;
+ default:
+ MSG("ERROR: [up] lora packet with unknown bandwidth\n");
+ memcpy((void *)(buff_up + buff_index), (void *)"BW?\"", 4);
+ buff_index += 4;
+ exit(EXIT_FAILURE);
+ }
+
+ /* Packet ECC coding rate, 11-13 useful chars */
+ switch (p->coderate) {
+ case CR_LORA_4_5:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/5\"", 13);
+ buff_index += 13;
+ break;
+ case CR_LORA_4_6:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/6\"", 13);
+ buff_index += 13;
+ break;
+ case CR_LORA_4_7:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/7\"", 13);
+ buff_index += 13;
+ break;
+ case CR_LORA_4_8:
+ memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/8\"", 13);
+ buff_index += 13;
+ break;
+ case 0: /* treat the CR0 case (mostly false sync) */
+ memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"OFF\"", 13);
+ buff_index += 13;
+ break;
+ default:
+ MSG("ERROR: [up] lora packet with unknown coderate\n");
+ memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"?\"", 11);
+ buff_index += 11;
+ exit(EXIT_FAILURE);
+ }
+
+ /* Lora SNR, 11-13 useful chars */
+ j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"lsnr\":%.1f", p->snr);
+ if (j > 0) {
+ buff_index += j;
+ } else {
+ MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+ } else if (p->modulation == MOD_FSK) {
+ memcpy((void *)(buff_up + buff_index), (void *)",\"modu\":\"FSK\"", 13);
+ buff_index += 13;
+
+ /* FSK datarate, 11-14 useful chars */
+ j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"datr\":%u", p->datarate);
+ if (j > 0) {
+ buff_index += j;
+ } else {
+ MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ MSG("ERROR: [up] received packet with unknown modulation\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Packet RSSI, payload size, 18-23 useful chars */
+ j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssi\":%.0f,\"size\":%u", p->rssi, p->size);
+ if (j > 0) {
+ buff_index += j;
+ } else {
+ MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Packet base64-encoded payload, 14-350 useful chars */
+ memcpy((void *)(buff_up + buff_index), (void *)",\"data\":\"", 9);
+ buff_index += 9;
+ j = bin_to_b64(p->payload, p->size, (char *)(buff_up + buff_index), 341); /* 255 bytes = 340 chars in b64 + null char */
+ if (j>=0) {
+ buff_index += j;
+ } else {
+ MSG("ERROR: [up] bin_to_b64 failed line %u\n", (__LINE__ - 5));
+ exit(EXIT_FAILURE);
+ }
+ buff_up[buff_index] = '"';
+ ++buff_index;
+
+ /* End of packet serialization */
+ buff_up[buff_index] = '}';
+ ++buff_index;
+ ++pkt_in_dgram;
+ }
+
+ /* restart fetch sequence without sending empty JSON if all packets have been filtered out */
+ if (pkt_in_dgram == 0) {
+ if (send_report == true) {
+ /* need to clean up the beginning of the payload */
+ buff_index -= 8; /* removes "rxpk":[ */
+ } else {
+ /* all packet have been filtered out and no report, restart loop */
+ continue;
+ }
+ } else {
+ /* end of packet array */
+ buff_up[buff_index] = ']';
+ ++buff_index;
+ /* add separator if needed */
+ if (send_report == true) {
+ buff_up[buff_index] = ',';
+ ++buff_index;
+ }
+ }
+
+ /* add status report if a new one is available */
+ if (send_report == true) {
+ pthread_mutex_lock(&mx_stat_rep);
+ report_ready = false;
+ j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, "%s", status_report);
+ pthread_mutex_unlock(&mx_stat_rep);
+ if (j > 0) {
+ buff_index += j;
+ } else {
+ MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 5));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* end of JSON datagram payload */
+ buff_up[buff_index] = '}';
+ ++buff_index;
+ buff_up[buff_index] = 0; /* add string terminator, for safety */
+
+ printf("\nJSON up: %s\n", (char *)(buff_up + 12)); /* DEBUG: display JSON payload */
+
+ /* send datagram to server */
+ send(sock_up, (void *)buff_up, buff_index, 0);
+ clock_gettime(CLOCK_MONOTONIC, &send_time);
+ pthread_mutex_lock(&mx_meas_up);
+ meas_up_dgram_sent += 1;
+ meas_up_network_byte += buff_index;
+
+ /* wait for acknowledge (in 2 times, to catch extra packets) */
+ for (i=0; i<2; ++i) {
+ j = recv(sock_up, (void *)buff_ack, sizeof buff_ack, 0);
+ clock_gettime(CLOCK_MONOTONIC, &recv_time);
+ if (j == -1) {
+ if (errno == EAGAIN) { /* timeout */
+ continue;
+ } else { /* server connection error */
+ break;
+ }
+ } else if ((j < 4) || (buff_ack[0] != PROTOCOL_VERSION) || (buff_ack[3] != PKT_PUSH_ACK)) {
+ //MSG("WARNING: [up] ignored invalid non-ACL packet\n");
+ continue;
+ } else if ((buff_ack[1] != token_h) || (buff_ack[2] != token_l)) {
+ //MSG("WARNING: [up] ignored out-of sync ACK packet\n");
+ continue;
+ } else {
+ MSG("INFO: [up] PUSH_ACK received in %i ms\n", (int)(1000 * difftimespec(recv_time, send_time)));
+ meas_up_ack_rcv += 1;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&mx_meas_up);
+ }
+ MSG("\nINFO: End of upstream thread\n");
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- THREAD 2: POLLING SERVER AND ENQUEUING PACKETS IN JIT QUEUE ---------- */
+
+void thread_down(void) {
+ int i; /* loop variables */
+
+ /* configuration and metadata for an outbound packet */
+ struct lgw_pkt_tx_s txpkt;
+ bool sent_immediate = false; /* option to sent the packet immediately */
+
+ /* local timekeeping variables */
+ struct timespec send_time; /* time of the pull request */
+ struct timespec recv_time; /* time of return from recv socket call */
+
+ /* data buffers */
+ uint8_t buff_down[1000]; /* buffer to receive downstream packets */
+ uint8_t buff_req[12]; /* buffer to compose pull requests */
+ int msg_len;
+
+ /* protocol variables */
+ uint8_t token_h; /* random token for acknowledgement matching */
+ uint8_t token_l; /* random token for acknowledgement matching */
+ bool req_ack = false; /* keep track of whether PULL_DATA was acknowledged or not */
+
+ /* JSON parsing variables */
+ JSON_Value *root_val = NULL;
+ JSON_Object *txpk_obj = NULL;
+ JSON_Value *val = NULL; /* needed to detect the absence of some fields */
+ const char *str; /* pointer to sub-strings in the JSON data */
+ short x0, x1;
+ uint64_t x2;
+ double x3, x4;
+
+ /* variables to send on GPS timestamp */
+ struct tref local_ref; /* time reference used for GPS <-> timestamp conversion */
+ struct timespec gps_tx; /* GPS time that needs to be converted to timestamp */
+
+ /* beacon variables */
+ struct lgw_pkt_tx_s beacon_pkt;
+ uint8_t beacon_chan;
+ uint8_t beacon_loop;
+ size_t beacon_RFU1_size = 0;
+ size_t beacon_RFU2_size = 0;
+ uint8_t beacon_pyld_idx = 0;
+ time_t diff_beacon_time;
+ struct timespec next_beacon_gps_time; /* gps time of next beacon packet */
+ struct timespec last_beacon_gps_time; /* gps time of last enqueued beacon packet */
+ int retry;
+
+ /* beacon data fields, byte 0 is Least Significant Byte */
+ int32_t field_latitude; /* 3 bytes, derived from reference latitude */
+ int32_t field_longitude; /* 3 bytes, derived from reference longitude */
+ uint16_t field_crc1, field_crc2;
+
+ /* auto-quit variable */
+ uint32_t autoquit_cnt = 0; /* count the number of PULL_DATA sent since the latest PULL_ACK */
+
+ /* Just In Time downlink */
+ struct timeval current_unix_time;
+ struct timeval current_concentrator_time;
+ enum jit_error_e jit_result = JIT_ERROR_OK;
+ enum jit_pkt_type_e downlink_type;
+
+ /* set downstream socket RX timeout */
+ i = setsockopt(sock_down, SOL_SOCKET, SO_RCVTIMEO, (void *)&pull_timeout, sizeof pull_timeout);
+ if (i != 0) {
+ MSG("ERROR: [down] setsockopt returned %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ /* pre-fill the pull request buffer with fixed fields */
+ buff_req[0] = PROTOCOL_VERSION;
+ buff_req[3] = PKT_PULL_DATA;
+ *(uint32_t *)(buff_req + 4) = net_mac_h;
+ *(uint32_t *)(buff_req + 8) = net_mac_l;
+
+ /* beacon variables initialization */
+ last_beacon_gps_time.tv_sec = 0;
+ last_beacon_gps_time.tv_nsec = 0;
+
+ /* beacon packet parameters */
+ beacon_pkt.tx_mode = ON_GPS; /* send on PPS pulse */
+ beacon_pkt.rf_chain = 0; /* antenna A */
+ beacon_pkt.rf_power = beacon_power;
+ beacon_pkt.modulation = MOD_LORA;
+ switch (beacon_bw_hz) {
+ case 125000:
+ beacon_pkt.bandwidth = BW_125KHZ;
+ break;
+ case 500000:
+ beacon_pkt.bandwidth = BW_500KHZ;
+ break;
+ default:
+ /* should not happen */
+ MSG("ERROR: unsupported bandwidth for beacon\n");
+ exit(EXIT_FAILURE);
+ }
+ switch (beacon_datarate) {
+ case 8:
+ beacon_pkt.datarate = DR_LORA_SF8;
+ beacon_RFU1_size = 1;
+ beacon_RFU2_size = 3;
+ break;
+ case 9:
+ beacon_pkt.datarate = DR_LORA_SF9;
+ beacon_RFU1_size = 2;
+ beacon_RFU2_size = 0;
+ break;
+ case 10:
+ beacon_pkt.datarate = DR_LORA_SF10;
+ beacon_RFU1_size = 3;
+ beacon_RFU2_size = 1;
+ break;
+ case 12:
+ beacon_pkt.datarate = DR_LORA_SF12;
+ beacon_RFU1_size = 5;
+ beacon_RFU2_size = 3;
+ break;
+ default:
+ /* should not happen */
+ MSG("ERROR: unsupported datarate for beacon\n");
+ exit(EXIT_FAILURE);
+ }
+ beacon_pkt.size = beacon_RFU1_size + 4 + 2 + 7 + beacon_RFU2_size + 2;
+ beacon_pkt.coderate = CR_LORA_4_5;
+ beacon_pkt.invert_pol = false;
+ beacon_pkt.preamble = 10;
+ beacon_pkt.no_crc = true;
+ beacon_pkt.no_header = true;
+
+ /* network common part beacon fields (little endian) */
+ for (i = 0; i < (int)beacon_RFU1_size; i++) {
+ beacon_pkt.payload[beacon_pyld_idx++] = 0x0;
+ }
+
+ /* network common part beacon fields (little endian) */
+ beacon_pyld_idx += 4; /* time (variable), filled later */
+ beacon_pyld_idx += 2; /* crc1 (variable), filled later */
+
+ /* calculate the latitude and longitude that must be publicly reported */
+ field_latitude = (int32_t)((reference_coord.lat / 90.0) * (double)(1<<23));
+ if (field_latitude > (int32_t)0x007FFFFF) {
+ field_latitude = (int32_t)0x007FFFFF; /* +90 N is represented as 89.99999 N */
+ } else if (field_latitude < (int32_t)0xFF800000) {
+ field_latitude = (int32_t)0xFF800000;
+ }
+ field_longitude = (int32_t)((reference_coord.lon / 180.0) * (double)(1<<23));
+ if (field_longitude > (int32_t)0x007FFFFF) {
+ field_longitude = (int32_t)0x007FFFFF; /* +180 E is represented as 179.99999 E */
+ } else if (field_longitude < (int32_t)0xFF800000) {
+ field_longitude = (int32_t)0xFF800000;
+ }
+
+ /* gateway specific beacon fields */
+ beacon_pkt.payload[beacon_pyld_idx++] = beacon_infodesc;
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_latitude;
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_latitude >> 8);
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_latitude >> 16);
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_longitude;
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_longitude >> 8);
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_longitude >> 16);
+
+ /* RFU */
+ for (i = 0; i < (int)beacon_RFU2_size; i++) {
+ beacon_pkt.payload[beacon_pyld_idx++] = 0x0;
+ }
+
+ /* CRC of the beacon gateway specific part fields */
+ field_crc2 = crc16((beacon_pkt.payload + 6 + beacon_RFU1_size), 7 + beacon_RFU2_size);
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_crc2;
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_crc2 >> 8);
+
+ /* JIT queue initialization */
+ jit_queue_init(&jit_queue);
+
+ while (!exit_sig && !quit_sig) {
+
+ /* auto-quit if the threshold is crossed */
+ if ((autoquit_threshold > 0) && (autoquit_cnt >= autoquit_threshold)) {
+ exit_sig = true;
+ MSG("INFO: [down] the last %u PULL_DATA were not ACKed, exiting application\n", autoquit_threshold);
+ break;
+ }
+
+ /* generate random token for request */
+ token_h = (uint8_t)rand(); /* random token */
+ token_l = (uint8_t)rand(); /* random token */
+ buff_req[1] = token_h;
+ buff_req[2] = token_l;
+
+ /* send PULL request and record time */
+ send(sock_down, (void *)buff_req, sizeof buff_req, 0);
+ clock_gettime(CLOCK_MONOTONIC, &send_time);
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_dw_pull_sent += 1;
+ pthread_mutex_unlock(&mx_meas_dw);
+ req_ack = false;
+ autoquit_cnt++;
+
+ /* listen to packets and process them until a new PULL request must be sent */
+ recv_time = send_time;
+ while ((int)difftimespec(recv_time, send_time) < keepalive_time) {
+
+ /* try to receive a datagram */
+ msg_len = recv(sock_down, (void *)buff_down, (sizeof buff_down)-1, 0);
+ clock_gettime(CLOCK_MONOTONIC, &recv_time);
+
+ /* Pre-allocate beacon slots in JiT queue, to check downlink collisions */
+ beacon_loop = JIT_NUM_BEACON_IN_QUEUE - jit_queue.num_beacon;
+ retry = 0;
+ while (beacon_loop && (beacon_period != 0)) {
+ pthread_mutex_lock(&mx_timeref);
+ /* Wait for GPS to be ready before inserting beacons in JiT queue */
+ if ((gps_ref_valid == true) && (xtal_correct_ok == true)) {
+
+ /* compute GPS time for next beacon to come */
+ /* LoRaWAN: T = k*beacon_period + TBeaconDelay */
+ /* with TBeaconDelay = [1.5ms +/- 1µs]*/
+ if (last_beacon_gps_time.tv_sec == 0) {
+ /* if no beacon has been queued, get next slot from current GPS time */
+ diff_beacon_time = time_reference_gps.gps.tv_sec % ((time_t)beacon_period);
+ next_beacon_gps_time.tv_sec = time_reference_gps.gps.tv_sec +
+ ((time_t)beacon_period - diff_beacon_time);
+ } else {
+ /* if there is already a beacon, take it as reference */
+ next_beacon_gps_time.tv_sec = last_beacon_gps_time.tv_sec + beacon_period;
+ }
+ /* now we can add a beacon_period to the reference to get next beacon GPS time */
+ next_beacon_gps_time.tv_sec += (retry * beacon_period);
+ next_beacon_gps_time.tv_nsec = 0;
+
+#if DEBUG_BEACON
+ {
+ time_t time_unix;
+
+ time_unix = time_reference_gps.gps.tv_sec + UNIX_GPS_EPOCH_OFFSET;
+ MSG_DEBUG(DEBUG_BEACON, "GPS-now : %s", ctime(&time_unix));
+ time_unix = last_beacon_gps_time.tv_sec + UNIX_GPS_EPOCH_OFFSET;
+ MSG_DEBUG(DEBUG_BEACON, "GPS-last: %s", ctime(&time_unix));
+ time_unix = next_beacon_gps_time.tv_sec + UNIX_GPS_EPOCH_OFFSET;
+ MSG_DEBUG(DEBUG_BEACON, "GPS-next: %s", ctime(&time_unix));
+ }
+#endif
+
+ /* convert GPS time to concentrator time, and set packet counter for JiT trigger */
+ lgw_gps2cnt(time_reference_gps, next_beacon_gps_time, &(beacon_pkt.count_us));
+ pthread_mutex_unlock(&mx_timeref);
+
+ /* apply frequency correction to beacon TX frequency */
+ if (beacon_freq_nb > 1) {
+ beacon_chan = (next_beacon_gps_time.tv_sec / beacon_period) % beacon_freq_nb; /* floor rounding */
+ } else {
+ beacon_chan = 0;
+ }
+ /* Compute beacon frequency */
+ beacon_pkt.freq_hz = beacon_freq_hz + (beacon_chan * beacon_freq_step);
+
+ /* load time in beacon payload */
+ beacon_pyld_idx = beacon_RFU1_size;
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & next_beacon_gps_time.tv_sec;
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 8);
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 16);
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 24);
+
+ /* calculate CRC */
+ field_crc1 = crc16(beacon_pkt.payload, 4 + beacon_RFU1_size); /* CRC for the network common part */
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_crc1;
+ beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_crc1 >> 8);
+
+ /* Insert beacon packet in JiT queue */
+ gettimeofday(&current_unix_time, NULL);
+ get_concentrator_time(&current_concentrator_time, current_unix_time);
+ jit_result = jit_enqueue(&jit_queue, &current_concentrator_time, &beacon_pkt, JIT_PKT_TYPE_BEACON);
+ if (jit_result == JIT_ERROR_OK) {
+ /* update stats */
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_nb_beacon_queued += 1;
+ pthread_mutex_unlock(&mx_meas_dw);
+
+ /* One more beacon in the queue */
+ beacon_loop--;
+ retry = 0;
+ last_beacon_gps_time.tv_sec = next_beacon_gps_time.tv_sec; /* keep this beacon time as reference for next one to be programmed */
+
+ /* display beacon payload */
+ MSG("INFO: Beacon queued (count_us=%u, freq_hz=%u, size=%u):\n", beacon_pkt.count_us, beacon_pkt.freq_hz, beacon_pkt.size);
+ printf( " => " );
+ for (i = 0; i < beacon_pkt.size; ++i) {
+ MSG("%02X ", beacon_pkt.payload[i]);
+ }
+ MSG("\n");
+ } else {
+ MSG_DEBUG(DEBUG_BEACON, "--> beacon queuing failed with %d\n", jit_result);
+ /* update stats */
+ pthread_mutex_lock(&mx_meas_dw);
+ if (jit_result != JIT_ERROR_COLLISION_BEACON) {
+ meas_nb_beacon_rejected += 1;
+ }
+ pthread_mutex_unlock(&mx_meas_dw);
+ /* In case previous enqueue failed, we retry one period later until it succeeds */
+ /* Note: In case the GPS has been unlocked for a while, there can be lots of retries */
+ /* to be done from last beacon time to a new valid one */
+ retry++;
+ MSG_DEBUG(DEBUG_BEACON, "--> beacon queuing retry=%d\n", retry);
+ }
+ } else {
+ pthread_mutex_unlock(&mx_timeref);
+ break;
+ }
+ }
+
+ /* if no network message was received, got back to listening sock_down socket */
+ if (msg_len == -1) {
+ //MSG("WARNING: [down] recv returned %s\n", strerror(errno)); /* too verbose */
+ continue;
+ }
+
+ /* if the datagram does not respect protocol, just ignore it */
+ if ((msg_len < 4) || (buff_down[0] != PROTOCOL_VERSION) || ((buff_down[3] != PKT_PULL_RESP) && (buff_down[3] != PKT_PULL_ACK))) {
+ MSG("WARNING: [down] ignoring invalid packet len=%d, protocol_version=%d, id=%d\n",
+ msg_len, buff_down[0], buff_down[3]);
+ continue;
+ }
+
+ /* if the datagram is an ACK, check token */
+ if (buff_down[3] == PKT_PULL_ACK) {
+ if ((buff_down[1] == token_h) && (buff_down[2] == token_l)) {
+ if (req_ack) {
+ MSG("INFO: [down] duplicate ACK received :)\n");
+ } else { /* if that packet was not already acknowledged */
+ req_ack = true;
+ autoquit_cnt = 0;
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_dw_ack_rcv += 1;
+ pthread_mutex_unlock(&mx_meas_dw);
+ MSG("INFO: [down] PULL_ACK received in %i ms\n", (int)(1000 * difftimespec(recv_time, send_time)));
+ }
+ } else { /* out-of-sync token */
+ MSG("INFO: [down] received out-of-sync ACK\n");
+ }
+ continue;
+ }
+
+ /* the datagram is a PULL_RESP */
+ buff_down[msg_len] = 0; /* add string terminator, just to be safe */
+ MSG("INFO: [down] PULL_RESP received - token[%d:%d] :)\n", buff_down[1], buff_down[2]); /* very verbose */
+ printf("\nJSON down: %s\n", (char *)(buff_down + 4)); /* DEBUG: display JSON payload */
+
+ /* initialize TX struct and try to parse JSON */
+ memset(&txpkt, 0, sizeof txpkt);
+ root_val = json_parse_string_with_comments((const char *)(buff_down + 4)); /* JSON offset */
+ if (root_val == NULL) {
+ MSG("WARNING: [down] invalid JSON, TX aborted\n");
+ continue;
+ }
+
+ /* look for JSON sub-object 'txpk' */
+ txpk_obj = json_object_get_object(json_value_get_object(root_val), "txpk");
+ if (txpk_obj == NULL) {
+ MSG("WARNING: [down] no \"txpk\" object in JSON, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+
+ /* Parse "immediate" tag, or target timestamp, or UTC time to be converted by GPS (mandatory) */
+ i = json_object_get_boolean(txpk_obj,"imme"); /* can be 1 if true, 0 if false, or -1 if not a JSON boolean */
+ if (i == 1) {
+ /* TX procedure: send immediately */
+ sent_immediate = true;
+ downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_C;
+ MSG("INFO: [down] a packet will be sent in \"immediate\" mode\n");
+ } else {
+ sent_immediate = false;
+ val = json_object_get_value(txpk_obj,"tmst");
+ if (val != NULL) {
+ /* TX procedure: send on timestamp value */
+ txpkt.count_us = (uint32_t)json_value_get_number(val);
+
+ /* Concentrator timestamp is given, we consider it is a Class A downlink */
+ downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_A;
+ } else {
+ /* TX procedure: send on GPS time (converted to timestamp value) */
+ val = json_object_get_value(txpk_obj, "tmms");
+ if (val == NULL) {
+ MSG("WARNING: [down] no mandatory \"txpk.tmst\" or \"txpk.tmms\" objects in JSON, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ if (gps_enabled == true) {
+ pthread_mutex_lock(&mx_timeref);
+ if (gps_ref_valid == true) {
+ local_ref = time_reference_gps;
+ pthread_mutex_unlock(&mx_timeref);
+ } else {
+ pthread_mutex_unlock(&mx_timeref);
+ MSG("WARNING: [down] no valid GPS time reference yet, impossible to send packet on specific GPS time, TX aborted\n");
+ json_value_free(root_val);
+
+ /* send acknoledge datagram to server */
+ send_tx_ack(buff_down[1], buff_down[2], JIT_ERROR_GPS_UNLOCKED);
+ continue;
+ }
+ } else {
+ MSG("WARNING: [down] GPS disabled, impossible to send packet on specific GPS time, TX aborted\n");
+ json_value_free(root_val);
+
+ /* send acknoledge datagram to server */
+ send_tx_ack(buff_down[1], buff_down[2], JIT_ERROR_GPS_UNLOCKED);
+ continue;
+ }
+
+ /* Get GPS time from JSON */
+ x2 = (uint64_t)json_value_get_number(val);
+
+ /* Convert GPS time from milliseconds to timespec */
+ x3 = modf((double)x2/1E3, &x4);
+ gps_tx.tv_sec = (time_t)x4; /* get seconds from integer part */
+ gps_tx.tv_nsec = (long)(x3 * 1E9); /* get nanoseconds from fractional part */
+
+ /* transform GPS time to timestamp */
+ i = lgw_gps2cnt(local_ref, gps_tx, &(txpkt.count_us));
+ if (i != LGW_GPS_SUCCESS) {
+ MSG("WARNING: [down] could not convert GPS time to timestamp, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ } else {
+ MSG("INFO: [down] a packet will be sent on timestamp value %u (calculated from GPS time)\n", txpkt.count_us);
+ }
+
+ /* GPS timestamp is given, we consider it is a Class B downlink */
+ downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_B;
+ }
+ }
+
+ /* Parse "No CRC" flag (optional field) */
+ val = json_object_get_value(txpk_obj,"ncrc");
+ if (val != NULL) {
+ txpkt.no_crc = (bool)json_value_get_boolean(val);
+ }
+
+ /* parse target frequency (mandatory) */
+ val = json_object_get_value(txpk_obj,"freq");
+ if (val == NULL) {
+ MSG("WARNING: [down] no mandatory \"txpk.freq\" object in JSON, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ txpkt.freq_hz = (uint32_t)((double)(1.0e6) * json_value_get_number(val));
+
+ /* parse RF chain used for TX (mandatory) */
+ val = json_object_get_value(txpk_obj,"rfch");
+ if (val == NULL) {
+ MSG("WARNING: [down] no mandatory \"txpk.rfch\" object in JSON, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ txpkt.rf_chain = (uint8_t)json_value_get_number(val);
+
+ /* parse TX power (optional field) */
+ val = json_object_get_value(txpk_obj,"powe");
+ if (val != NULL) {
+ txpkt.rf_power = (int8_t)json_value_get_number(val) - antenna_gain;
+ }
+
+ /* Parse modulation (mandatory) */
+ str = json_object_get_string(txpk_obj, "modu");
+ if (str == NULL) {
+ MSG("WARNING: [down] no mandatory \"txpk.modu\" object in JSON, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ if (strcmp(str, "LORA") == 0) {
+ /* Lora modulation */
+ txpkt.modulation = MOD_LORA;
+
+ /* Parse Lora spreading-factor and modulation bandwidth (mandatory) */
+ str = json_object_get_string(txpk_obj, "datr");
+ if (str == NULL) {
+ MSG("WARNING: [down] no mandatory \"txpk.datr\" object in JSON, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ i = sscanf(str, "SF%2hdBW%3hd", &x0, &x1);
+ if (i != 2) {
+ MSG("WARNING: [down] format error in \"txpk.datr\", TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ switch (x0) {
+ case 7: txpkt.datarate = DR_LORA_SF7; break;
+ case 8: txpkt.datarate = DR_LORA_SF8; break;
+ case 9: txpkt.datarate = DR_LORA_SF9; break;
+ case 10: txpkt.datarate = DR_LORA_SF10; break;
+ case 11: txpkt.datarate = DR_LORA_SF11; break;
+ case 12: txpkt.datarate = DR_LORA_SF12; break;
+ default:
+ MSG("WARNING: [down] format error in \"txpk.datr\", invalid SF, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ switch (x1) {
+ case 125: txpkt.bandwidth = BW_125KHZ; break;
+ case 250: txpkt.bandwidth = BW_250KHZ; break;
+ case 500: txpkt.bandwidth = BW_500KHZ; break;
+ default:
+ MSG("WARNING: [down] format error in \"txpk.datr\", invalid BW, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+
+ /* Parse ECC coding rate (optional field) */
+ str = json_object_get_string(txpk_obj, "codr");
+ if (str == NULL) {
+ MSG("WARNING: [down] no mandatory \"txpk.codr\" object in json, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ if (strcmp(str, "4/5") == 0) txpkt.coderate = CR_LORA_4_5;
+ else if (strcmp(str, "4/6") == 0) txpkt.coderate = CR_LORA_4_6;
+ else if (strcmp(str, "2/3") == 0) txpkt.coderate = CR_LORA_4_6;
+ else if (strcmp(str, "4/7") == 0) txpkt.coderate = CR_LORA_4_7;
+ else if (strcmp(str, "4/8") == 0) txpkt.coderate = CR_LORA_4_8;
+ else if (strcmp(str, "1/2") == 0) txpkt.coderate = CR_LORA_4_8;
+ else {
+ MSG("WARNING: [down] format error in \"txpk.codr\", TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+
+ /* Parse signal polarity switch (optional field) */
+ val = json_object_get_value(txpk_obj,"ipol");
+ if (val != NULL) {
+ txpkt.invert_pol = (bool)json_value_get_boolean(val);
+ }
+
+ /* parse Lora preamble length (optional field, optimum min value enforced) */
+ val = json_object_get_value(txpk_obj,"prea");
+ if (val != NULL) {
+ i = (int)json_value_get_number(val);
+ if (i >= MIN_LORA_PREAMB) {
+ txpkt.preamble = (uint16_t)i;
+ } else {
+ txpkt.preamble = (uint16_t)MIN_LORA_PREAMB;
+ }
+ } else {
+ txpkt.preamble = (uint16_t)STD_LORA_PREAMB;
+ }
+
+ } else if (strcmp(str, "FSK") == 0) {
+ /* FSK modulation */
+ txpkt.modulation = MOD_FSK;
+
+ /* parse FSK bitrate (mandatory) */
+ val = json_object_get_value(txpk_obj,"datr");
+ if (val == NULL) {
+ MSG("WARNING: [down] no mandatory \"txpk.datr\" object in JSON, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ txpkt.datarate = (uint32_t)(json_value_get_number(val));
+
+ /* parse frequency deviation (mandatory) */
+ val = json_object_get_value(txpk_obj,"fdev");
+ if (val == NULL) {
+ MSG("WARNING: [down] no mandatory \"txpk.fdev\" object in JSON, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ txpkt.f_dev = (uint8_t)(json_value_get_number(val) / 1000.0); /* JSON value in Hz, txpkt.f_dev in kHz */
+
+ /* parse FSK preamble length (optional field, optimum min value enforced) */
+ val = json_object_get_value(txpk_obj,"prea");
+ if (val != NULL) {
+ i = (int)json_value_get_number(val);
+ if (i >= MIN_FSK_PREAMB) {
+ txpkt.preamble = (uint16_t)i;
+ } else {
+ txpkt.preamble = (uint16_t)MIN_FSK_PREAMB;
+ }
+ } else {
+ txpkt.preamble = (uint16_t)STD_FSK_PREAMB;
+ }
+
+ } else {
+ MSG("WARNING: [down] invalid modulation in \"txpk.modu\", TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+
+ /* Parse payload length (mandatory) */
+ val = json_object_get_value(txpk_obj,"size");
+ if (val == NULL) {
+ MSG("WARNING: [down] no mandatory \"txpk.size\" object in JSON, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ txpkt.size = (uint16_t)json_value_get_number(val);
+
+ /* Parse payload data (mandatory) */
+ str = json_object_get_string(txpk_obj, "data");
+ if (str == NULL) {
+ MSG("WARNING: [down] no mandatory \"txpk.data\" object in JSON, TX aborted\n");
+ json_value_free(root_val);
+ continue;
+ }
+ i = b64_to_bin(str, strlen(str), txpkt.payload, sizeof txpkt.payload);
+ if (i != txpkt.size) {
+ MSG("WARNING: [down] mismatch between .size and .data size once converter to binary\n");
+ }
+
+ /* free the JSON parse tree from memory */
+ json_value_free(root_val);
+
+ /* select TX mode */
+ if (sent_immediate) {
+ txpkt.tx_mode = IMMEDIATE;
+ } else {
+ txpkt.tx_mode = TIMESTAMPED;
+ }
+
+ /* record measurement data */
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_dw_dgram_rcv += 1; /* count only datagrams with no JSON errors */
+ meas_dw_network_byte += msg_len; /* meas_dw_network_byte */
+ meas_dw_payload_byte += txpkt.size;
+ pthread_mutex_unlock(&mx_meas_dw);
+
+ /* check TX parameter before trying to queue packet */
+ jit_result = JIT_ERROR_OK;
+ if ((txpkt.freq_hz < tx_freq_min[txpkt.rf_chain]) || (txpkt.freq_hz > tx_freq_max[txpkt.rf_chain])) {
+ jit_result = JIT_ERROR_TX_FREQ;
+ MSG("ERROR: Packet REJECTED, unsupported frequency - %u (min:%u,max:%u)\n", txpkt.freq_hz, tx_freq_min[txpkt.rf_chain], tx_freq_max[txpkt.rf_chain]);
+ }
+ if (jit_result == JIT_ERROR_OK) {
+ for (i=0; i<txlut.size; i++) {
+ if (txlut.lut[i].rf_power == txpkt.rf_power) {
+ /* this RF power is supported, we can continue */
+ break;
+ }
+ }
+ if (i == txlut.size) {
+ /* this RF power is not supported */
+ jit_result = JIT_ERROR_TX_POWER;
+ MSG("ERROR: Packet REJECTED, unsupported RF power for TX - %d\n", txpkt.rf_power);
+ }
+ }
+
+ /* insert packet to be sent into JIT queue */
+ if (jit_result == JIT_ERROR_OK) {
+ gettimeofday(&current_unix_time, NULL);
+ get_concentrator_time(&current_concentrator_time, current_unix_time);
+ jit_result = jit_enqueue(&jit_queue, &current_concentrator_time, &txpkt, downlink_type);
+ if (jit_result != JIT_ERROR_OK) {
+ printf("ERROR: Packet REJECTED (jit error=%d)\n", jit_result);
+ }
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_nb_tx_requested += 1;
+ pthread_mutex_unlock(&mx_meas_dw);
+ }
+
+ /* Send acknoledge datagram to server */
+ send_tx_ack(buff_down[1], buff_down[2], jit_result);
+ }
+ }
+ MSG("\nINFO: End of downstream thread\n");
+}
+
+void print_tx_status(uint8_t tx_status) {
+ switch (tx_status) {
+ case TX_OFF:
+ MSG("INFO: [jit] lgw_status returned TX_OFF\n");
+ break;
+ case TX_FREE:
+ MSG("INFO: [jit] lgw_status returned TX_FREE\n");
+ break;
+ case TX_EMITTING:
+ MSG("INFO: [jit] lgw_status returned TX_EMITTING\n");
+ break;
+ case TX_SCHEDULED:
+ MSG("INFO: [jit] lgw_status returned TX_SCHEDULED\n");
+ break;
+ default:
+ MSG("INFO: [jit] lgw_status returned UNKNOWN (%d)\n", tx_status);
+ break;
+ }
+}
+
+
+/* -------------------------------------------------------------------------- */
+/* --- THREAD 3: CHECKING PACKETS TO BE SENT FROM JIT QUEUE AND SEND THEM --- */
+
+void thread_jit(void) {
+ int result = LGW_HAL_SUCCESS;
+ struct lgw_pkt_tx_s pkt;
+ int pkt_index = -1;
+ struct timeval current_unix_time;
+ struct timeval current_concentrator_time;
+ enum jit_error_e jit_result;
+ enum jit_pkt_type_e pkt_type;
+ uint8_t tx_status;
+
+ while (!exit_sig && !quit_sig) {
+ wait_ms(10);
+
+ /* transfer data and metadata to the concentrator, and schedule TX */
+ gettimeofday(&current_unix_time, NULL);
+ get_concentrator_time(&current_concentrator_time, current_unix_time);
+ jit_result = jit_peek(&jit_queue, &current_concentrator_time, &pkt_index);
+ if (jit_result == JIT_ERROR_OK) {
+ if (pkt_index > -1) {
+ jit_result = jit_dequeue(&jit_queue, pkt_index, &pkt, &pkt_type);
+ if (jit_result == JIT_ERROR_OK) {
+ /* update beacon stats */
+ if (pkt_type == JIT_PKT_TYPE_BEACON) {
+ /* Compensate breacon frequency with xtal error */
+ pthread_mutex_lock(&mx_xcorr);
+ pkt.freq_hz = (uint32_t)(xtal_correct * (double)pkt.freq_hz);
+ MSG_DEBUG(DEBUG_BEACON, "beacon_pkt.freq_hz=%u (xtal_correct=%.15lf)\n", pkt.freq_hz, xtal_correct);
+ pthread_mutex_unlock(&mx_xcorr);
+
+ /* Update statistics */
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_nb_beacon_sent += 1;
+ pthread_mutex_unlock(&mx_meas_dw);
+ MSG("INFO: Beacon dequeued (count_us=%u)\n", pkt.count_us);
+ }
+
+ /* check if concentrator is free for sending new packet */
+ pthread_mutex_lock(&mx_concent); /* may have to wait for a fetch to finish */
+ result = lgw_status(TX_STATUS, &tx_status);
+ pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */
+ if (result == LGW_HAL_ERROR) {
+ MSG("WARNING: [jit] lgw_status failed\n");
+ } else {
+ if (tx_status == TX_EMITTING) {
+ MSG("ERROR: concentrator is currently emitting\n");
+ print_tx_status(tx_status);
+ continue;
+ } else if (tx_status == TX_SCHEDULED) {
+ MSG("WARNING: a downlink was already scheduled, overwritting it...\n");
+ print_tx_status(tx_status);
+ } else {
+ /* Nothing to do */
+ }
+ }
+
+ /* send packet to concentrator */
+ pthread_mutex_lock(&mx_concent); /* may have to wait for a fetch to finish */
+ result = lgw_send(pkt);
+ pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */
+ if (result == LGW_HAL_ERROR) {
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_nb_tx_fail += 1;
+ pthread_mutex_unlock(&mx_meas_dw);
+ MSG("WARNING: [jit] lgw_send failed\n");
+ continue;
+ } else {
+ pthread_mutex_lock(&mx_meas_dw);
+ meas_nb_tx_ok += 1;
+ pthread_mutex_unlock(&mx_meas_dw);
+ MSG_DEBUG(DEBUG_PKT_FWD, "lgw_send done: count_us=%u\n", pkt.count_us);
+ }
+ } else {
+ MSG("ERROR: jit_dequeue failed with %d\n", jit_result);
+ }
+ }
+ } else if (jit_result == JIT_ERROR_EMPTY) {
+ /* Do nothing, it can happen */
+ } else {
+ MSG("ERROR: jit_peek failed with %d\n", jit_result);
+ }
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- THREAD 4: PARSE GPS MESSAGE AND KEEP GATEWAY IN SYNC ----------------- */
+
+static void gps_process_sync(void) {
+ struct timespec gps_time;
+ struct timespec utc;
+ uint32_t trig_tstamp; /* concentrator timestamp associated with PPM pulse */
+ int i = lgw_gps_get(&utc, &gps_time, NULL, NULL);
+
+ /* get GPS time for synchronization */
+ if (i != LGW_GPS_SUCCESS) {
+ MSG("WARNING: [gps] could not get GPS time from GPS\n");
+ return;
+ }
+
+ /* get timestamp captured on PPM pulse */
+ pthread_mutex_lock(&mx_concent);
+ i = lgw_get_trigcnt(&trig_tstamp);
+ pthread_mutex_unlock(&mx_concent);
+ if (i != LGW_HAL_SUCCESS) {
+ MSG("WARNING: [gps] failed to read concentrator timestamp\n");
+ return;
+ }
+
+ /* try to update time reference with the new GPS time & timestamp */
+ pthread_mutex_lock(&mx_timeref);
+ i = lgw_gps_sync(&time_reference_gps, trig_tstamp, utc, gps_time);
+ pthread_mutex_unlock(&mx_timeref);
+ if (i != LGW_GPS_SUCCESS) {
+ MSG("WARNING: [gps] GPS out of sync, keeping previous time reference\n");
+ }
+}
+
+static void gps_process_coords(void) {
+ /* position variable */
+ struct coord_s coord;
+ struct coord_s gpserr;
+ int i = lgw_gps_get(NULL, NULL, &coord, &gpserr);
+
+ /* update gateway coordinates */
+ pthread_mutex_lock(&mx_meas_gps);
+ if (i == LGW_GPS_SUCCESS) {
+ gps_coord_valid = true;
+ meas_gps_coord = coord;
+ meas_gps_err = gpserr;
+ // TODO: report other GPS statistics (typ. signal quality & integrity)
+ } else {
+ gps_coord_valid = false;
+ }
+ pthread_mutex_unlock(&mx_meas_gps);
+}
+
+void thread_gps(void) {
+ /* serial variables */
+ char serial_buff[128]; /* buffer to receive GPS data */
+ size_t wr_idx = 0; /* pointer to end of chars in buffer */
+
+ /* variables for PPM pulse GPS synchronization */
+ enum gps_msg latest_msg; /* keep track of latest NMEA message parsed */
+
+ /* initialize some variables before loop */
+ memset(serial_buff, 0, sizeof serial_buff);
+
+ while (!exit_sig && !quit_sig) {
+ size_t rd_idx = 0;
+ size_t frame_end_idx = 0;
+
+ /* blocking non-canonical read on serial port */
+ ssize_t nb_char = read(gps_tty_fd, serial_buff + wr_idx, LGW_GPS_MIN_MSG_SIZE);
+ if (nb_char <= 0) {
+ MSG("WARNING: [gps] read() returned value %d\n", nb_char);
+ continue;
+ }
+ wr_idx += (size_t)nb_char;
+
+ /*******************************************
+ * Scan buffer for UBX/NMEA sync chars and *
+ * attempt to decode frame if one is found *
+ *******************************************/
+ while(rd_idx < wr_idx) {
+ size_t frame_size = 0;
+
+ /* Scan buffer for UBX sync char */
+ if(serial_buff[rd_idx] == (char)LGW_GPS_UBX_SYNC_CHAR) {
+
+ /***********************
+ * Found UBX sync char *
+ ***********************/
+ latest_msg = lgw_parse_ubx(&serial_buff[rd_idx], (wr_idx - rd_idx), &frame_size);
+
+ if (frame_size > 0) {
+ if (latest_msg == INCOMPLETE) {
+ /* UBX header found but frame appears to be missing bytes */
+ frame_size = 0;
+ } else if (latest_msg == INVALID) {
+ /* message header received but message appears to be corrupted */
+ MSG("WARNING: [gps] could not get a valid message from GPS (no time)\n");
+ frame_size = 0;
+ } else if (latest_msg == UBX_NAV_TIMEGPS) {
+ gps_process_sync();
+ }
+ }
+ } else if(serial_buff[rd_idx] == LGW_GPS_NMEA_SYNC_CHAR) {
+ /************************
+ * Found NMEA sync char *
+ ************************/
+ /* scan for NMEA end marker (LF = 0x0a) */
+ char* nmea_end_ptr = memchr(&serial_buff[rd_idx],(int)0x0a, (wr_idx - rd_idx));
+
+ if(nmea_end_ptr) {
+ /* found end marker */
+ frame_size = nmea_end_ptr - &serial_buff[rd_idx] + 1;
+ latest_msg = lgw_parse_nmea(&serial_buff[rd_idx], frame_size);
+
+ if(latest_msg == INVALID || latest_msg == UNKNOWN) {
+ /* checksum failed */
+ frame_size = 0;
+ } else if (latest_msg == NMEA_RMC) { /* Get location from RMC frames */
+ gps_process_coords();
+ }
+ }
+ }
+
+ if(frame_size > 0) {
+ /* At this point message is a checksum verified frame
+ we're processed or ignored. Remove frame from buffer */
+ rd_idx += frame_size;
+ frame_end_idx = rd_idx;
+ } else {
+ rd_idx++;
+ }
+ } /* ...for(rd_idx = 0... */
+
+ if(frame_end_idx) {
+ /* Frames have been processed. Remove bytes to end of last processed frame */
+ memcpy(serial_buff, &serial_buff[frame_end_idx], wr_idx - frame_end_idx);
+ wr_idx -= frame_end_idx;
+ } /* ...for(rd_idx = 0... */
+
+ /* Prevent buffer overflow */
+ if((sizeof(serial_buff) - wr_idx) < LGW_GPS_MIN_MSG_SIZE) {
+ memcpy(serial_buff, &serial_buff[LGW_GPS_MIN_MSG_SIZE], wr_idx - LGW_GPS_MIN_MSG_SIZE);
+ wr_idx -= LGW_GPS_MIN_MSG_SIZE;
+ }
+ }
+ MSG("\nINFO: End of GPS thread\n");
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- THREAD 5: CHECK TIME REFERENCE AND CALCULATE XTAL CORRECTION --------- */
+
+void thread_valid(void) {
+
+ /* GPS reference validation variables */
+ long gps_ref_age = 0;
+ bool ref_valid_local = false;
+ double xtal_err_cpy;
+
+ /* variables for XTAL correction averaging */
+ unsigned init_cpt = 0;
+ double init_acc = 0.0;
+ double x;
+
+ /* correction debug */
+ // FILE * log_file = NULL;
+ // time_t now_time;
+ // char log_name[64];
+
+ /* initialization */
+ // time(&now_time);
+ // strftime(log_name,sizeof log_name,"xtal_err_%Y%m%dT%H%M%SZ.csv",localtime(&now_time));
+ // log_file = fopen(log_name, "w");
+ // setbuf(log_file, NULL);
+ // fprintf(log_file,"\"xtal_correct\",\"XERR_INIT_AVG %u XERR_FILT_COEF %u\"\n", XERR_INIT_AVG, XERR_FILT_COEF); // DEBUG
+
+ /* main loop task */
+ while (!exit_sig && !quit_sig) {
+ wait_ms(1000);
+
+ /* calculate when the time reference was last updated */
+ pthread_mutex_lock(&mx_timeref);
+ gps_ref_age = (long)difftime(time(NULL), time_reference_gps.systime);
+ if ((gps_ref_age >= 0) && (gps_ref_age <= GPS_REF_MAX_AGE)) {
+ /* time ref is ok, validate and */
+ gps_ref_valid = true;
+ ref_valid_local = true;
+ xtal_err_cpy = time_reference_gps.xtal_err;
+ //printf("XTAL err: %.15lf (1/XTAL_err:%.15lf)\n", xtal_err_cpy, 1/xtal_err_cpy); // DEBUG
+ } else {
+ /* time ref is too old, invalidate */
+ gps_ref_valid = false;
+ ref_valid_local = false;
+ }
+ pthread_mutex_unlock(&mx_timeref);
+
+ /* manage XTAL correction */
+ if (ref_valid_local == false) {
+ /* couldn't sync, or sync too old -> invalidate XTAL correction */
+ pthread_mutex_lock(&mx_xcorr);
+ xtal_correct_ok = false;
+ xtal_correct = 1.0;
+ pthread_mutex_unlock(&mx_xcorr);
+ init_cpt = 0;
+ init_acc = 0.0;
+ } else {
+ if (init_cpt < XERR_INIT_AVG) {
+ /* initial accumulation */
+ init_acc += xtal_err_cpy;
+ ++init_cpt;
+ } else if (init_cpt == XERR_INIT_AVG) {
+ /* initial average calculation */
+ pthread_mutex_lock(&mx_xcorr);
+ xtal_correct = (double)(XERR_INIT_AVG) / init_acc;
+ //printf("XERR_INIT_AVG=%d, init_acc=%.15lf\n", XERR_INIT_AVG, init_acc);
+ xtal_correct_ok = true;
+ pthread_mutex_unlock(&mx_xcorr);
+ ++init_cpt;
+ // fprintf(log_file,"%.18lf,\"average\"\n", xtal_correct); // DEBUG
+ } else {
+ /* tracking with low-pass filter */
+ x = 1 / xtal_err_cpy;
+ pthread_mutex_lock(&mx_xcorr);
+ xtal_correct = xtal_correct - xtal_correct/XERR_FILT_COEF + x/XERR_FILT_COEF;
+ pthread_mutex_unlock(&mx_xcorr);
+ // fprintf(log_file,"%.18lf,\"track\"\n", xtal_correct); // DEBUG
+ }
+ }
+ // printf("Time ref: %s, XTAL correct: %s (%.15lf)\n", ref_valid_local?"valid":"invalid", xtal_correct_ok?"valid":"invalid", xtal_correct); // DEBUG
+ }
+ MSG("\nINFO: End of validation thread\n");
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/lora_pkt_fwd/src/parson.c b/lora_pkt_fwd/src/parson.c
new file mode 100644
index 0000000..16bb158
--- /dev/null
+++ b/lora_pkt_fwd/src/parson.c
@@ -0,0 +1,1765 @@
+/*
+ Parson ( http://kgabis.github.com/parson/ )
+ Copyright (c) 2012 - 2016 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.
+*/
+#ifdef _MSC_VER
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#include "parson.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+
+#define STARTING_CAPACITY 15
+#define ARRAY_MAX_CAPACITY 122880 /* 15*(2^13) */
+#define OBJECT_MAX_CAPACITY 960 /* 15*(2^6) */
+#define MAX_NESTING 19
+#define DOUBLE_SERIALIZATION_FORMAT "%f"
+
+#define SIZEOF_TOKEN(a) (sizeof(a) - 1)
+#define SKIP_CHAR(str) ((*str)++)
+#define SKIP_WHITESPACES(str) while (isspace(**str)) { SKIP_CHAR(str); }
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+#undef malloc
+#undef free
+
+static JSON_Malloc_Function parson_malloc = malloc;
+static JSON_Free_Function parson_free = free;
+
+#define IS_CONT(b) (((unsigned char)(b) & 0xC0) == 0x80) /* is utf-8 continuation byte */
+
+/* Type definitions */
+typedef union json_value_value {
+ char *string;
+ double number;
+ JSON_Object *object;
+ JSON_Array *array;
+ int boolean;
+ int null;
+} JSON_Value_Value;
+
+struct json_value_t {
+ JSON_Value_Type type;
+ JSON_Value_Value value;
+};
+
+struct json_object_t {
+ char **names;
+ JSON_Value **values;
+ size_t count;
+ size_t capacity;
+};
+
+struct json_array_t {
+ JSON_Value **items;
+ size_t count;
+ size_t capacity;
+};
+
+/* Various */
+static char * read_file(const char *filename);
+static void remove_comments(char *string, const char *start_token, const char *end_token);
+static char * parson_strndup(const char *string, size_t n);
+static char * parson_strdup(const char *string);
+static int is_utf16_hex(const unsigned char *string);
+static int num_bytes_in_utf8_sequence(unsigned char c);
+static int verify_utf8_sequence(const unsigned char *string, int *len);
+static int is_valid_utf8(const char *string, size_t string_len);
+static int is_decimal(const char *string, size_t length);
+
+/* JSON Object */
+static JSON_Object * json_object_init(void);
+static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value);
+static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity);
+static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n);
+static void json_object_free(JSON_Object *object);
+
+/* JSON Array */
+static JSON_Array * json_array_init(void);
+static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value);
+static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity);
+static void json_array_free(JSON_Array *array);
+
+/* JSON Value */
+static JSON_Value * json_value_init_string_no_copy(char *string);
+
+/* Parser */
+static void skip_quotes(const char **string);
+static int parse_utf_16(const char **unprocessed, char **processed);
+static char * process_string(const char *input, size_t len);
+static char * get_quoted_string(const char **string);
+static JSON_Value * parse_object_value(const char **string, size_t nesting);
+static JSON_Value * parse_array_value(const char **string, size_t nesting);
+static JSON_Value * parse_string_value(const char **string);
+static JSON_Value * parse_boolean_value(const char **string);
+static JSON_Value * parse_number_value(const char **string);
+static JSON_Value * parse_null_value(const char **string);
+static JSON_Value * parse_value(const char **string, size_t nesting);
+
+/* Serialization */
+static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf);
+static int json_serialize_string(const char *string, char *buf);
+static int append_indent(char *buf, int level);
+static int append_string(char *buf, const char *string);
+
+/* Various */
+static char * parson_strndup(const char *string, size_t n) {
+ char *output_string = (char*)parson_malloc(n + 1);
+ if (!output_string)
+ return NULL;
+ output_string[n] = '\0';
+ strncpy(output_string, string, n);
+ return output_string;
+}
+
+static char * parson_strdup(const char *string) {
+ return parson_strndup(string, strlen(string));
+}
+
+static int is_utf16_hex(const unsigned char *s) {
+ return isxdigit(s[0]) && isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]);
+}
+
+static int num_bytes_in_utf8_sequence(unsigned char c) {
+ if (c == 0xC0 || c == 0xC1 || c > 0xF4 || IS_CONT(c)) {
+ return 0;
+ } else if ((c & 0x80) == 0) { /* 0xxxxxxx */
+ return 1;
+ } else if ((c & 0xE0) == 0xC0) { /* 110xxxxx */
+ return 2;
+ } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx */
+ return 3;
+ } else if ((c & 0xF8) == 0xF0) { /* 11110xxx */
+ return 4;
+ }
+ return 0; /* won't happen */
+}
+
+static int verify_utf8_sequence(const unsigned char *string, int *len) {
+ unsigned int cp = 0;
+ *len = num_bytes_in_utf8_sequence(string[0]);
+
+ if (*len == 1) {
+ cp = string[0];
+ } else if (*len == 2 && IS_CONT(string[1])) {
+ cp = string[0] & 0x1F;
+ cp = (cp << 6) | (string[1] & 0x3F);
+ } else if (*len == 3 && IS_CONT(string[1]) && IS_CONT(string[2])) {
+ cp = ((unsigned char)string[0]) & 0xF;
+ cp = (cp << 6) | (string[1] & 0x3F);
+ cp = (cp << 6) | (string[2] & 0x3F);
+ } else if (*len == 4 && IS_CONT(string[1]) && IS_CONT(string[2]) && IS_CONT(string[3])) {
+ cp = string[0] & 0x7;
+ cp = (cp << 6) | (string[1] & 0x3F);
+ cp = (cp << 6) | (string[2] & 0x3F);
+ cp = (cp << 6) | (string[3] & 0x3F);
+ } else {
+ return 0;
+ }
+
+ /* overlong encodings */
+ if ((cp < 0x80 && *len > 1) ||
+ (cp < 0x800 && *len > 2) ||
+ (cp < 0x10000 && *len > 3)) {
+ return 0;
+ }
+
+ /* invalid unicode */
+ if (cp > 0x10FFFF) {
+ return 0;
+ }
+
+ /* surrogate halves */
+ if (cp >= 0xD800 && cp <= 0xDFFF) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int is_valid_utf8(const char *string, size_t string_len) {
+ int len = 0;
+ const char *string_end = string + string_len;
+ while (string < string_end) {
+ if (!verify_utf8_sequence((const unsigned char*)string, &len)) {
+ return 0;
+ }
+ string += len;
+ }
+ return 1;
+}
+
+static int is_decimal(const char *string, size_t length) {
+ if (length > 1 && string[0] == '0' && string[1] != '.')
+ return 0;
+ if (length > 2 && !strncmp(string, "-0", 2) && string[2] != '.')
+ return 0;
+ while (length--)
+ if (strchr("xX", string[length]))
+ return 0;
+ return 1;
+}
+
+static char * read_file(const char * filename) {
+ FILE *fp = fopen(filename, "r");
+ size_t file_size;
+ long pos;
+ char *file_contents;
+ if (!fp)
+ return NULL;
+ fseek(fp, 0L, SEEK_END);
+ pos = ftell(fp);
+ if (pos < 0) {
+ fclose(fp);
+ return NULL;
+ }
+ file_size = pos;
+ rewind(fp);
+ file_contents = (char*)parson_malloc(sizeof(char) * (file_size + 1));
+ if (!file_contents) {
+ fclose(fp);
+ return NULL;
+ }
+ if (fread(file_contents, file_size, 1, fp) < 1) {
+ if (ferror(fp)) {
+ fclose(fp);
+ parson_free(file_contents);
+ return NULL;
+ }
+ }
+ fclose(fp);
+ file_contents[file_size] = '\0';
+ return file_contents;
+}
+
+static void remove_comments(char *string, const char *start_token, const char *end_token) {
+ int in_string = 0, escaped = 0;
+ size_t i;
+ char *ptr = NULL, current_char;
+ size_t start_token_len = strlen(start_token);
+ size_t end_token_len = strlen(end_token);
+ if (start_token_len == 0 || end_token_len == 0)
+ return;
+ while ((current_char = *string) != '\0') {
+ if (current_char == '\\' && !escaped) {
+ escaped = 1;
+ string++;
+ continue;
+ } else if (current_char == '\"' && !escaped) {
+ in_string = !in_string;
+ } else if (!in_string && strncmp(string, start_token, start_token_len) == 0) {
+ for(i = 0; i < start_token_len; i++)
+ string[i] = ' ';
+ string = string + start_token_len;
+ ptr = strstr(string, end_token);
+ if (!ptr)
+ return;
+ for (i = 0; i < (ptr - string) + end_token_len; i++)
+ string[i] = ' ';
+ string = ptr + end_token_len - 1;
+ }
+ escaped = 0;
+ string++;
+ }
+}
+
+/* JSON Object */
+static JSON_Object * json_object_init(void) {
+ JSON_Object *new_obj = (JSON_Object*)parson_malloc(sizeof(JSON_Object));
+ if (!new_obj)
+ return NULL;
+ new_obj->names = (char**)NULL;
+ new_obj->values = (JSON_Value**)NULL;
+ new_obj->capacity = 0;
+ new_obj->count = 0;
+ return new_obj;
+}
+
+static JSON_Status json_object_add(JSON_Object *object, const char *name, JSON_Value *value) {
+ size_t index = 0;
+ if (object == NULL || name == NULL || value == NULL) {
+ return JSONFailure;
+ }
+ if (object->count >= object->capacity) {
+ size_t new_capacity = MAX(object->capacity * 2, STARTING_CAPACITY);
+ if (new_capacity > OBJECT_MAX_CAPACITY)
+ return JSONFailure;
+ if (json_object_resize(object, new_capacity) == JSONFailure)
+ return JSONFailure;
+ }
+ if (json_object_get_value(object, name) != NULL)
+ return JSONFailure;
+ index = object->count;
+ object->names[index] = parson_strdup(name);
+ if (object->names[index] == NULL)
+ return JSONFailure;
+ object->values[index] = value;
+ object->count++;
+ return JSONSuccess;
+}
+
+static JSON_Status json_object_resize(JSON_Object *object, size_t new_capacity) {
+ char **temp_names = NULL;
+ JSON_Value **temp_values = NULL;
+
+ if ((object->names == NULL && object->values != NULL) ||
+ (object->names != NULL && object->values == NULL) ||
+ new_capacity == 0) {
+ return JSONFailure; /* Shouldn't happen */
+ }
+
+ temp_names = (char**)parson_malloc(new_capacity * sizeof(char*));
+ if (temp_names == NULL)
+ return JSONFailure;
+
+ temp_values = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*));
+ if (temp_values == NULL) {
+ parson_free(temp_names);
+ return JSONFailure;
+ }
+
+ if (object->names != NULL && object->values != NULL && object->count > 0) {
+ memcpy(temp_names, object->names, object->count * sizeof(char*));
+ memcpy(temp_values, object->values, object->count * sizeof(JSON_Value*));
+ }
+ parson_free(object->names);
+ parson_free(object->values);
+ object->names = temp_names;
+ object->values = temp_values;
+ object->capacity = new_capacity;
+ return JSONSuccess;
+}
+
+static JSON_Value * json_object_nget_value(const JSON_Object *object, const char *name, size_t n) {
+ size_t i, name_length;
+ for (i = 0; i < json_object_get_count(object); i++) {
+ name_length = strlen(object->names[i]);
+ if (name_length != n)
+ continue;
+ if (strncmp(object->names[i], name, n) == 0)
+ return object->values[i];
+ }
+ return NULL;
+}
+
+static void json_object_free(JSON_Object *object) {
+ while(object->count--) {
+ parson_free(object->names[object->count]);
+ json_value_free(object->values[object->count]);
+ }
+ parson_free(object->names);
+ parson_free(object->values);
+ parson_free(object);
+}
+
+/* JSON Array */
+static JSON_Array * json_array_init(void) {
+ JSON_Array *new_array = (JSON_Array*)parson_malloc(sizeof(JSON_Array));
+ if (!new_array)
+ return NULL;
+ new_array->items = (JSON_Value**)NULL;
+ new_array->capacity = 0;
+ new_array->count = 0;
+ return new_array;
+}
+
+static JSON_Status json_array_add(JSON_Array *array, JSON_Value *value) {
+ if (array->count >= array->capacity) {
+ size_t new_capacity = MAX(array->capacity * 2, STARTING_CAPACITY);
+ if (new_capacity > ARRAY_MAX_CAPACITY)
+ return JSONFailure;
+ if (json_array_resize(array, new_capacity) == JSONFailure)
+ return JSONFailure;
+ }
+ array->items[array->count] = value;
+ array->count++;
+ return JSONSuccess;
+}
+
+static JSON_Status json_array_resize(JSON_Array *array, size_t new_capacity) {
+ JSON_Value **new_items = NULL;
+ if (new_capacity == 0) {
+ return JSONFailure;
+ }
+ new_items = (JSON_Value**)parson_malloc(new_capacity * sizeof(JSON_Value*));
+ if (new_items == NULL) {
+ return JSONFailure;
+ }
+ if (array->items != NULL && array->count > 0) {
+ memcpy(new_items, array->items, array->count * sizeof(JSON_Value*));
+ }
+ parson_free(array->items);
+ array->items = new_items;
+ array->capacity = new_capacity;
+ return JSONSuccess;
+}
+
+static void json_array_free(JSON_Array *array) {
+ while (array->count--)
+ json_value_free(array->items[array->count]);
+ parson_free(array->items);
+ parson_free(array);
+}
+
+/* JSON Value */
+static JSON_Value * json_value_init_string_no_copy(char *string) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value)
+ return NULL;
+ new_value->type = JSONString;
+ new_value->value.string = string;
+ return new_value;
+}
+
+/* Parser */
+static void skip_quotes(const char **string) {
+ SKIP_CHAR(string);
+ while (**string != '\"') {
+ if (**string == '\0')
+ return;
+ if (**string == '\\') {
+ SKIP_CHAR(string);
+ if (**string == '\0')
+ return;
+ }
+ SKIP_CHAR(string);
+ }
+ SKIP_CHAR(string);
+}
+
+static int parse_utf_16(const char **unprocessed, char **processed) {
+ unsigned int cp, lead, trail;
+ char *processed_ptr = *processed;
+ const char *unprocessed_ptr = *unprocessed;
+ unprocessed_ptr++; /* skips u */
+ if (!is_utf16_hex((const unsigned char*)unprocessed_ptr) || sscanf(unprocessed_ptr, "%4x", &cp) == EOF)
+ return JSONFailure;
+ if (cp < 0x80) {
+ *processed_ptr = cp; /* 0xxxxxxx */
+ } else if (cp < 0x800) {
+ *processed_ptr++ = ((cp >> 6) & 0x1F) | 0xC0; /* 110xxxxx */
+ *processed_ptr = ((cp ) & 0x3F) | 0x80; /* 10xxxxxx */
+ } else if (cp < 0xD800 || cp > 0xDFFF) {
+ *processed_ptr++ = ((cp >> 12) & 0x0F) | 0xE0; /* 1110xxxx */
+ *processed_ptr++ = ((cp >> 6) & 0x3F) | 0x80; /* 10xxxxxx */
+ *processed_ptr = ((cp ) & 0x3F) | 0x80; /* 10xxxxxx */
+ } else if (cp >= 0xD800 && cp <= 0xDBFF) { /* lead surrogate (0xD800..0xDBFF) */
+ lead = cp;
+ unprocessed_ptr += 4; /* should always be within the buffer, otherwise previous sscanf would fail */
+ if (*unprocessed_ptr++ != '\\' || *unprocessed_ptr++ != 'u' || /* starts with \u? */
+ !is_utf16_hex((const unsigned char*)unprocessed_ptr) ||
+ sscanf(unprocessed_ptr, "%4x", &trail) == EOF ||
+ trail < 0xDC00 || trail > 0xDFFF) { /* valid trail surrogate? (0xDC00..0xDFFF) */
+ return JSONFailure;
+ }
+ cp = ((((lead-0xD800)&0x3FF)<<10)|((trail-0xDC00)&0x3FF))+0x010000;
+ *processed_ptr++ = (((cp >> 18) & 0x07) | 0xF0); /* 11110xxx */
+ *processed_ptr++ = (((cp >> 12) & 0x3F) | 0x80); /* 10xxxxxx */
+ *processed_ptr++ = (((cp >> 6) & 0x3F) | 0x80); /* 10xxxxxx */
+ *processed_ptr = (((cp ) & 0x3F) | 0x80); /* 10xxxxxx */
+ } else { /* trail surrogate before lead surrogate */
+ return JSONFailure;
+ }
+ unprocessed_ptr += 3;
+ *processed = processed_ptr;
+ *unprocessed = unprocessed_ptr;
+ return JSONSuccess;
+}
+
+
+/* Copies and processes passed string up to supplied length.
+Example: "\u006Corem ipsum" -> lorem ipsum */
+static char* process_string(const char *input, size_t len) {
+ const char *input_ptr = input;
+ size_t initial_size = (len + 1) * sizeof(char);
+ size_t final_size = 0;
+ char *output = (char*)parson_malloc(initial_size);
+ char *output_ptr = output;
+ char *resized_output = NULL;
+ while ((*input_ptr != '\0') && (size_t)(input_ptr - input) < len) {
+ if (*input_ptr == '\\') {
+ input_ptr++;
+ switch (*input_ptr) {
+ case '\"': *output_ptr = '\"'; break;
+ case '\\': *output_ptr = '\\'; break;
+ case '/': *output_ptr = '/'; break;
+ case 'b': *output_ptr = '\b'; break;
+ case 'f': *output_ptr = '\f'; break;
+ case 'n': *output_ptr = '\n'; break;
+ case 'r': *output_ptr = '\r'; break;
+ case 't': *output_ptr = '\t'; break;
+ case 'u':
+ if (parse_utf_16(&input_ptr, &output_ptr) == JSONFailure)
+ goto error;
+ break;
+ default:
+ goto error;
+ }
+ } else if ((unsigned char)*input_ptr < 0x20) {
+ goto error; /* 0x00-0x19 are invalid characters for json string (http://www.ietf.org/rfc/rfc4627.txt) */
+ } else {
+ *output_ptr = *input_ptr;
+ }
+ output_ptr++;
+ input_ptr++;
+ }
+ *output_ptr = '\0';
+ /* resize to new length */
+ final_size = (size_t)(output_ptr-output) + 1;
+ resized_output = (char*)parson_malloc(final_size);
+ if (resized_output == NULL)
+ goto error;
+ memcpy(resized_output, output, final_size);
+ parson_free(output);
+ return resized_output;
+error:
+ parson_free(output);
+ return NULL;
+}
+
+/* Return processed contents of a string between quotes and
+ skips passed argument to a matching quote. */
+static char * get_quoted_string(const char **string) {
+ const char *string_start = *string;
+ size_t string_len = 0;
+ skip_quotes(string);
+ if (**string == '\0')
+ return NULL;
+ string_len = *string - string_start - 2; /* length without quotes */
+ return process_string(string_start + 1, string_len);
+}
+
+static JSON_Value * parse_value(const char **string, size_t nesting) {
+ if (nesting > MAX_NESTING)
+ return NULL;
+ SKIP_WHITESPACES(string);
+ switch (**string) {
+ case '{':
+ return parse_object_value(string, nesting + 1);
+ case '[':
+ return parse_array_value(string, nesting + 1);
+ case '\"':
+ return parse_string_value(string);
+ case 'f': case 't':
+ return parse_boolean_value(string);
+ case '-':
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ return parse_number_value(string);
+ case 'n':
+ return parse_null_value(string);
+ default:
+ return NULL;
+ }
+}
+
+static JSON_Value * parse_object_value(const char **string, size_t nesting) {
+ JSON_Value *output_value = json_value_init_object(), *new_value = NULL;
+ JSON_Object *output_object = json_value_get_object(output_value);
+ char *new_key = NULL;
+ if (output_value == NULL)
+ return NULL;
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ if (**string == '}') { /* empty object */
+ SKIP_CHAR(string);
+ return output_value;
+ }
+ while (**string != '\0') {
+ new_key = get_quoted_string(string);
+ SKIP_WHITESPACES(string);
+ if (new_key == NULL || **string != ':') {
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_CHAR(string);
+ new_value = parse_value(string, nesting);
+ if (new_value == NULL) {
+ parson_free(new_key);
+ json_value_free(output_value);
+ return NULL;
+ }
+ if(json_object_add(output_object, new_key, new_value) == JSONFailure) {
+ parson_free(new_key);
+ parson_free(new_value);
+ json_value_free(output_value);
+ return NULL;
+ }
+ parson_free(new_key);
+ SKIP_WHITESPACES(string);
+ if (**string != ',')
+ break;
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ }
+ SKIP_WHITESPACES(string);
+ if (**string != '}' || /* Trim object after parsing is over */
+ json_object_resize(output_object, json_object_get_count(output_object)) == JSONFailure) {
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_CHAR(string);
+ return output_value;
+}
+
+static JSON_Value * parse_array_value(const char **string, size_t nesting) {
+ JSON_Value *output_value = json_value_init_array(), *new_array_value = NULL;
+ JSON_Array *output_array = json_value_get_array(output_value);
+ if (!output_value)
+ return NULL;
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ if (**string == ']') { /* empty array */
+ SKIP_CHAR(string);
+ return output_value;
+ }
+ while (**string != '\0') {
+ new_array_value = parse_value(string, nesting);
+ if (!new_array_value) {
+ json_value_free(output_value);
+ return NULL;
+ }
+ if(json_array_add(output_array, new_array_value) == JSONFailure) {
+ parson_free(new_array_value);
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_WHITESPACES(string);
+ if (**string != ',')
+ break;
+ SKIP_CHAR(string);
+ SKIP_WHITESPACES(string);
+ }
+ SKIP_WHITESPACES(string);
+ if (**string != ']' || /* Trim array after parsing is over */
+ json_array_resize(output_array, json_array_get_count(output_array)) == JSONFailure) {
+ json_value_free(output_value);
+ return NULL;
+ }
+ SKIP_CHAR(string);
+ return output_value;
+}
+
+static JSON_Value * parse_string_value(const char **string) {
+ JSON_Value *value = NULL;
+ char *new_string = get_quoted_string(string);
+ if (new_string == NULL)
+ return NULL;
+ value = json_value_init_string_no_copy(new_string);
+ if (value == NULL) {
+ parson_free(new_string);
+ return NULL;
+ }
+ return value;
+}
+
+static JSON_Value * parse_boolean_value(const char **string) {
+ size_t true_token_size = SIZEOF_TOKEN("true");
+ size_t false_token_size = SIZEOF_TOKEN("false");
+ if (strncmp("true", *string, true_token_size) == 0) {
+ *string += true_token_size;
+ return json_value_init_boolean(1);
+ } else if (strncmp("false", *string, false_token_size) == 0) {
+ *string += false_token_size;
+ return json_value_init_boolean(0);
+ }
+ return NULL;
+}
+
+static JSON_Value * parse_number_value(const char **string) {
+ char *end;
+ double number = strtod(*string, &end);
+ JSON_Value *output_value;
+ if (is_decimal(*string, end - *string)) {
+ *string = end;
+ output_value = json_value_init_number(number);
+ } else {
+ output_value = NULL;
+ }
+ return output_value;
+}
+
+static JSON_Value * parse_null_value(const char **string) {
+ size_t token_size = SIZEOF_TOKEN("null");
+ if (strncmp("null", *string, token_size) == 0) {
+ *string += token_size;
+ return json_value_init_null();
+ }
+ return NULL;
+}
+
+/* Serialization */
+#define APPEND_STRING(str) do { written = append_string(buf, (str)); \
+ if (written < 0) { return -1; } \
+ if (buf != NULL) { buf += written; } \
+ written_total += written; } while(0)
+
+#define APPEND_INDENT(level) do { written = append_indent(buf, (level)); \
+ if (written < 0) { return -1; } \
+ if (buf != NULL) { buf += written; } \
+ written_total += written; } while(0)
+
+static int json_serialize_to_buffer_r(const JSON_Value *value, char *buf, int level, int is_pretty, char *num_buf)
+{
+ const char *key = NULL, *string = NULL;
+ JSON_Value *temp_value = NULL;
+ JSON_Array *array = NULL;
+ JSON_Object *object = NULL;
+ size_t i = 0, count = 0;
+ double num = 0.0;
+ int written = -1, written_total = 0;
+
+ switch (json_value_get_type(value)) {
+ case JSONArray:
+ array = json_value_get_array(value);
+ count = json_array_get_count(array);
+ APPEND_STRING("[");
+ if (count > 0 && is_pretty)
+ APPEND_STRING("\n");
+ for (i = 0; i < count; i++) {
+ if (is_pretty)
+ APPEND_INDENT(level+1);
+ temp_value = json_array_get_value(array, i);
+ written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf);
+ if (written < 0)
+ return -1;
+ if (buf != NULL)
+ buf += written;
+ written_total += written;
+ if (i < (count - 1))
+ APPEND_STRING(",");
+ if (is_pretty)
+ APPEND_STRING("\n");
+ }
+ if (count > 0 && is_pretty)
+ APPEND_INDENT(level);
+ APPEND_STRING("]");
+ return written_total;
+ case JSONObject:
+ object = json_value_get_object(value);
+ count = json_object_get_count(object);
+ APPEND_STRING("{");
+ if (count > 0 && is_pretty)
+ APPEND_STRING("\n");
+ for (i = 0; i < count; i++) {
+ key = json_object_get_name(object, i);
+ if (is_pretty)
+ APPEND_INDENT(level+1);
+ written = json_serialize_string(key, buf);
+ if (written < 0)
+ return -1;
+ if (buf != NULL)
+ buf += written;
+ written_total += written;
+ APPEND_STRING(":");
+ if (is_pretty)
+ APPEND_STRING(" ");
+ temp_value = json_object_get_value(object, key);
+ written = json_serialize_to_buffer_r(temp_value, buf, level+1, is_pretty, num_buf);
+ if (written < 0)
+ return -1;
+ if (buf != NULL)
+ buf += written;
+ written_total += written;
+ if (i < (count - 1))
+ APPEND_STRING(",");
+ if (is_pretty)
+ APPEND_STRING("\n");
+ }
+ if (count > 0 && is_pretty)
+ APPEND_INDENT(level);
+ APPEND_STRING("}");
+ return written_total;
+ case JSONString:
+ string = json_value_get_string(value);
+ written = json_serialize_string(string, buf);
+ if (written < 0)
+ return -1;
+ if (buf != NULL)
+ buf += written;
+ written_total += written;
+ return written_total;
+ case JSONBoolean:
+ if (json_value_get_boolean(value))
+ APPEND_STRING("true");
+ else
+ APPEND_STRING("false");
+ return written_total;
+ case JSONNumber:
+ num = json_value_get_number(value);
+ if (buf != NULL)
+ num_buf = buf;
+ if (num == ((double)(int)num)) /* check if num is integer */
+ written = sprintf(num_buf, "%d", (int)num);
+ else
+ written = sprintf(num_buf, DOUBLE_SERIALIZATION_FORMAT, num);
+ if (written < 0)
+ return -1;
+ if (buf != NULL)
+ buf += written;
+ written_total += written;
+ return written_total;
+ case JSONNull:
+ APPEND_STRING("null");
+ return written_total;
+ case JSONError:
+ return -1;
+ default:
+ return -1;
+ }
+}
+
+static int json_serialize_string(const char *string, char *buf) {
+ size_t i = 0, len = strlen(string);
+ char c = '\0';
+ int written = -1, written_total = 0;
+ APPEND_STRING("\"");
+ for (i = 0; i < len; i++) {
+ c = string[i];
+ switch (c) {
+ case '\"': APPEND_STRING("\\\""); break;
+ case '\\': APPEND_STRING("\\\\"); break;
+ case '/': APPEND_STRING("\\/"); break; /* to make json embeddable in xml\/html */
+ case '\b': APPEND_STRING("\\b"); break;
+ case '\f': APPEND_STRING("\\f"); break;
+ case '\n': APPEND_STRING("\\n"); break;
+ case '\r': APPEND_STRING("\\r"); break;
+ case '\t': APPEND_STRING("\\t"); break;
+ default:
+ if (buf != NULL) {
+ buf[0] = c;
+ buf += 1;
+ }
+ written_total += 1;
+ break;
+ }
+ }
+ APPEND_STRING("\"");
+ return written_total;
+}
+
+static int append_indent(char *buf, int level) {
+ int i;
+ int written = -1, written_total = 0;
+ for (i = 0; i < level; i++) {
+ APPEND_STRING(" ");
+ }
+ return written_total;
+}
+
+static int append_string(char *buf, const char *string) {
+ if (buf == NULL) {
+ return (int)strlen(string);
+ }
+ return sprintf(buf, "%s", string);
+}
+
+#undef APPEND_STRING
+#undef APPEND_INDENT
+
+/* Parser API */
+JSON_Value * json_parse_file(const char *filename) {
+ char *file_contents = read_file(filename);
+ JSON_Value *output_value = NULL;
+ if (file_contents == NULL)
+ return NULL;
+ output_value = json_parse_string(file_contents);
+ parson_free(file_contents);
+ return output_value;
+}
+
+JSON_Value * json_parse_file_with_comments(const char *filename) {
+ char *file_contents = read_file(filename);
+ JSON_Value *output_value = NULL;
+ if (file_contents == NULL)
+ return NULL;
+ output_value = json_parse_string_with_comments(file_contents);
+ parson_free(file_contents);
+ return output_value;
+}
+
+JSON_Value * json_parse_string(const char *string) {
+ if (string == NULL)
+ return NULL;
+ SKIP_WHITESPACES(&string);
+ if (*string != '{' && *string != '[')
+ return NULL;
+ return parse_value((const char**)&string, 0);
+}
+
+JSON_Value * json_parse_string_with_comments(const char *string) {
+ JSON_Value *result = NULL;
+ char *string_mutable_copy = NULL, *string_mutable_copy_ptr = NULL;
+ string_mutable_copy = parson_strdup(string);
+ if (string_mutable_copy == NULL)
+ return NULL;
+ remove_comments(string_mutable_copy, "/*", "*/");
+ remove_comments(string_mutable_copy, "//", "\n");
+ string_mutable_copy_ptr = string_mutable_copy;
+ SKIP_WHITESPACES(&string_mutable_copy_ptr);
+ if (*string_mutable_copy_ptr != '{' && *string_mutable_copy_ptr != '[') {
+ parson_free(string_mutable_copy);
+ return NULL;
+ }
+ result = parse_value((const char**)&string_mutable_copy_ptr, 0);
+ parson_free(string_mutable_copy);
+ return result;
+}
+
+
+/* JSON Object API */
+
+JSON_Value * json_object_get_value(const JSON_Object *object, const char *name) {
+ if (object == NULL || name == NULL)
+ return NULL;
+ return json_object_nget_value(object, name, strlen(name));
+}
+
+const char * json_object_get_string(const JSON_Object *object, const char *name) {
+ return json_value_get_string(json_object_get_value(object, name));
+}
+
+double json_object_get_number(const JSON_Object *object, const char *name) {
+ return json_value_get_number(json_object_get_value(object, name));
+}
+
+JSON_Object * json_object_get_object(const JSON_Object *object, const char *name) {
+ return json_value_get_object(json_object_get_value(object, name));
+}
+
+JSON_Array * json_object_get_array(const JSON_Object *object, const char *name) {
+ return json_value_get_array(json_object_get_value(object, name));
+}
+
+int json_object_get_boolean(const JSON_Object *object, const char *name) {
+ return json_value_get_boolean(json_object_get_value(object, name));
+}
+
+JSON_Value * json_object_dotget_value(const JSON_Object *object, const char *name) {
+ const char *dot_position = strchr(name, '.');
+ if (!dot_position)
+ return json_object_get_value(object, name);
+ object = json_value_get_object(json_object_nget_value(object, name, dot_position - name));
+ return json_object_dotget_value(object, dot_position + 1);
+}
+
+const char * json_object_dotget_string(const JSON_Object *object, const char *name) {
+ return json_value_get_string(json_object_dotget_value(object, name));
+}
+
+double json_object_dotget_number(const JSON_Object *object, const char *name) {
+ return json_value_get_number(json_object_dotget_value(object, name));
+}
+
+JSON_Object * json_object_dotget_object(const JSON_Object *object, const char *name) {
+ return json_value_get_object(json_object_dotget_value(object, name));
+}
+
+JSON_Array * json_object_dotget_array(const JSON_Object *object, const char *name) {
+ return json_value_get_array(json_object_dotget_value(object, name));
+}
+
+int json_object_dotget_boolean(const JSON_Object *object, const char *name) {
+ return json_value_get_boolean(json_object_dotget_value(object, name));
+}
+
+size_t json_object_get_count(const JSON_Object *object) {
+ return object ? object->count : 0;
+}
+
+const char * json_object_get_name(const JSON_Object *object, size_t index) {
+ if (index >= json_object_get_count(object))
+ return NULL;
+ return object->names[index];
+}
+
+/* JSON Array API */
+JSON_Value * json_array_get_value(const JSON_Array *array, size_t index) {
+ if (index >= json_array_get_count(array))
+ return NULL;
+ return array->items[index];
+}
+
+const char * json_array_get_string(const JSON_Array *array, size_t index) {
+ return json_value_get_string(json_array_get_value(array, index));
+}
+
+double json_array_get_number(const JSON_Array *array, size_t index) {
+ return json_value_get_number(json_array_get_value(array, index));
+}
+
+JSON_Object * json_array_get_object(const JSON_Array *array, size_t index) {
+ return json_value_get_object(json_array_get_value(array, index));
+}
+
+JSON_Array * json_array_get_array(const JSON_Array *array, size_t index) {
+ return json_value_get_array(json_array_get_value(array, index));
+}
+
+int json_array_get_boolean(const JSON_Array *array, size_t index) {
+ return json_value_get_boolean(json_array_get_value(array, index));
+}
+
+size_t json_array_get_count(const JSON_Array *array) {
+ return array ? array->count : 0;
+}
+
+/* JSON Value API */
+JSON_Value_Type json_value_get_type(const JSON_Value *value) {
+ return value ? value->type : JSONError;
+}
+
+JSON_Object * json_value_get_object(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONObject ? value->value.object : NULL;
+}
+
+JSON_Array * json_value_get_array(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONArray ? value->value.array : NULL;
+}
+
+const char * json_value_get_string(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONString ? value->value.string : NULL;
+}
+
+double json_value_get_number(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONNumber ? value->value.number : 0;
+}
+
+int json_value_get_boolean(const JSON_Value *value) {
+ return json_value_get_type(value) == JSONBoolean ? value->value.boolean : -1;
+}
+
+void json_value_free(JSON_Value *value) {
+ switch (json_value_get_type(value)) {
+ case JSONObject:
+ json_object_free(value->value.object);
+ break;
+ case JSONString:
+ if (value->value.string) { parson_free(value->value.string); }
+ break;
+ case JSONArray:
+ json_array_free(value->value.array);
+ break;
+ default:
+ break;
+ }
+ parson_free(value);
+}
+
+JSON_Value * json_value_init_object(void) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value)
+ return NULL;
+ new_value->type = JSONObject;
+ new_value->value.object = json_object_init();
+ if (!new_value->value.object) {
+ parson_free(new_value);
+ return NULL;
+ }
+ return new_value;
+}
+
+JSON_Value * json_value_init_array(void) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value)
+ return NULL;
+ new_value->type = JSONArray;
+ new_value->value.array = json_array_init();
+ if (!new_value->value.array) {
+ parson_free(new_value);
+ return NULL;
+ }
+ return new_value;
+}
+
+JSON_Value * json_value_init_string(const char *string) {
+ char *copy = NULL;
+ JSON_Value *value;
+ size_t string_len = 0;
+ if (string == NULL)
+ return NULL;
+ string_len = strlen(string);
+ if (!is_valid_utf8(string, string_len))
+ return NULL;
+ copy = parson_strndup(string, string_len);
+ if (copy == NULL)
+ return NULL;
+ value = json_value_init_string_no_copy(copy);
+ if (value == NULL)
+ parson_free(copy);
+ return value;
+}
+
+JSON_Value * json_value_init_number(double number) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value)
+ return NULL;
+ new_value->type = JSONNumber;
+ new_value->value.number = number;
+ return new_value;
+}
+
+JSON_Value * json_value_init_boolean(int boolean) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value)
+ return NULL;
+ new_value->type = JSONBoolean;
+ new_value->value.boolean = boolean ? 1 : 0;
+ return new_value;
+}
+
+JSON_Value * json_value_init_null(void) {
+ JSON_Value *new_value = (JSON_Value*)parson_malloc(sizeof(JSON_Value));
+ if (!new_value)
+ return NULL;
+ new_value->type = JSONNull;
+ return new_value;
+}
+
+JSON_Value * json_value_deep_copy(const JSON_Value *value) {
+ size_t i = 0;
+ JSON_Value *return_value = NULL, *temp_value_copy = NULL, *temp_value = NULL;
+ const char *temp_string = NULL, *temp_key = NULL;
+ char *temp_string_copy = NULL;
+ JSON_Array *temp_array = NULL, *temp_array_copy = NULL;
+ JSON_Object *temp_object = NULL, *temp_object_copy = NULL;
+
+ switch (json_value_get_type(value)) {
+ case JSONArray:
+ temp_array = json_value_get_array(value);
+ return_value = json_value_init_array();
+ if (return_value == NULL)
+ return NULL;
+ temp_array_copy = json_value_get_array(return_value);
+ for (i = 0; i < json_array_get_count(temp_array); i++) {
+ temp_value = json_array_get_value(temp_array, i);
+ temp_value_copy = json_value_deep_copy(temp_value);
+ if (temp_value_copy == NULL) {
+ json_value_free(return_value);
+ return NULL;
+ }
+ if (json_array_add(temp_array_copy, temp_value_copy) == JSONFailure) {
+ json_value_free(return_value);
+ json_value_free(temp_value_copy);
+ return NULL;
+ }
+ }
+ return return_value;
+ case JSONObject:
+ temp_object = json_value_get_object(value);
+ return_value = json_value_init_object();
+ if (return_value == NULL)
+ return NULL;
+ temp_object_copy = json_value_get_object(return_value);
+ for (i = 0; i < json_object_get_count(temp_object); i++) {
+ temp_key = json_object_get_name(temp_object, i);
+ temp_value = json_object_get_value(temp_object, temp_key);
+ temp_value_copy = json_value_deep_copy(temp_value);
+ if (temp_value_copy == NULL) {
+ json_value_free(return_value);
+ return NULL;
+ }
+ if (json_object_add(temp_object_copy, temp_key, temp_value_copy) == JSONFailure) {
+ json_value_free(return_value);
+ json_value_free(temp_value_copy);
+ return NULL;
+ }
+ }
+ return return_value;
+ case JSONBoolean:
+ return json_value_init_boolean(json_value_get_boolean(value));
+ case JSONNumber:
+ return json_value_init_number(json_value_get_number(value));
+ case JSONString:
+ temp_string = json_value_get_string(value);
+ temp_string_copy = parson_strdup(temp_string);
+ if (temp_string_copy == NULL)
+ return NULL;
+ return_value = json_value_init_string_no_copy(temp_string_copy);
+ if (return_value == NULL)
+ parson_free(temp_string_copy);
+ return return_value;
+ case JSONNull:
+ return json_value_init_null();
+ case JSONError:
+ return NULL;
+ default:
+ return NULL;
+ }
+}
+
+size_t json_serialization_size(const JSON_Value *value) {
+ char num_buf[1100]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */
+ int res = json_serialize_to_buffer_r(value, NULL, 0, 0, num_buf);
+ return res < 0 ? 0 : (size_t)(res + 1);
+}
+
+JSON_Status json_serialize_to_buffer(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) {
+ int written = -1;
+ size_t needed_size_in_bytes = json_serialization_size(value);
+ if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes) {
+ return JSONFailure;
+ }
+ written = json_serialize_to_buffer_r(value, buf, 0, 0, NULL);
+ if (written < 0)
+ return JSONFailure;
+ return JSONSuccess;
+}
+
+JSON_Status json_serialize_to_file(const JSON_Value *value, const char *filename) {
+ JSON_Status return_code = JSONSuccess;
+ FILE *fp = NULL;
+ char *serialized_string = json_serialize_to_string(value);
+ if (serialized_string == NULL) {
+ return JSONFailure;
+ }
+ fp = fopen (filename, "w");
+ if (fp != NULL) {
+ if (fputs (serialized_string, fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ if (fclose (fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ }
+ json_free_serialized_string(serialized_string);
+ return return_code;
+}
+
+char * json_serialize_to_string(const JSON_Value *value) {
+ JSON_Status serialization_result = JSONFailure;
+ size_t buf_size_bytes = json_serialization_size(value);
+ char *buf = NULL;
+ if (buf_size_bytes == 0) {
+ return NULL;
+ }
+ buf = (char*)parson_malloc(buf_size_bytes);
+ if (buf == NULL)
+ return NULL;
+ serialization_result = json_serialize_to_buffer(value, buf, buf_size_bytes);
+ if (serialization_result == JSONFailure) {
+ json_free_serialized_string(buf);
+ return NULL;
+ }
+ return buf;
+}
+
+size_t json_serialization_size_pretty(const JSON_Value *value) {
+ char num_buf[1100]; /* recursively allocating buffer on stack is a bad idea, so let's do it only once */
+ int res = json_serialize_to_buffer_r(value, NULL, 0, 1, num_buf);
+ return res < 0 ? 0 : (size_t)(res + 1);
+}
+
+JSON_Status json_serialize_to_buffer_pretty(const JSON_Value *value, char *buf, size_t buf_size_in_bytes) {
+ int written = -1;
+ size_t needed_size_in_bytes = json_serialization_size_pretty(value);
+ if (needed_size_in_bytes == 0 || buf_size_in_bytes < needed_size_in_bytes)
+ return JSONFailure;
+ written = json_serialize_to_buffer_r(value, buf, 0, 1, NULL);
+ if (written < 0)
+ return JSONFailure;
+ return JSONSuccess;
+}
+
+JSON_Status json_serialize_to_file_pretty(const JSON_Value *value, const char *filename) {
+ JSON_Status return_code = JSONSuccess;
+ FILE *fp = NULL;
+ char *serialized_string = json_serialize_to_string_pretty(value);
+ if (serialized_string == NULL) {
+ return JSONFailure;
+ }
+ fp = fopen (filename, "w");
+ if (fp != NULL) {
+ if (fputs (serialized_string, fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ if (fclose (fp) == EOF) {
+ return_code = JSONFailure;
+ }
+ }
+ json_free_serialized_string(serialized_string);
+ return return_code;
+}
+
+char * json_serialize_to_string_pretty(const JSON_Value *value) {
+ JSON_Status serialization_result = JSONFailure;
+ size_t buf_size_bytes = json_serialization_size_pretty(value);
+ char *buf = NULL;
+ if (buf_size_bytes == 0) {
+ return NULL;
+ }
+ buf = (char*)parson_malloc(buf_size_bytes);
+ if (buf == NULL)
+ return NULL;
+ serialization_result = json_serialize_to_buffer_pretty(value, buf, buf_size_bytes);
+ if (serialization_result == JSONFailure) {
+ json_free_serialized_string(buf);
+ return NULL;
+ }
+ return buf;
+}
+
+void json_free_serialized_string(char *string) {
+ parson_free(string);
+}
+
+JSON_Status json_array_remove(JSON_Array *array, size_t ix) {
+ JSON_Value *temp_value = NULL;
+ size_t last_element_ix = 0;
+ if (array == NULL || ix >= json_array_get_count(array)) {
+ return JSONFailure;
+ }
+ last_element_ix = json_array_get_count(array) - 1;
+ json_value_free(json_array_get_value(array, ix));
+ if (ix != last_element_ix) { /* Replace value with one from the end of array */
+ temp_value = json_array_get_value(array, last_element_ix);
+ if (temp_value == NULL) {
+ return JSONFailure;
+ }
+ array->items[ix] = temp_value;
+ }
+ array->count -= 1;
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_value(JSON_Array *array, size_t ix, JSON_Value *value) {
+ if (array == NULL || value == NULL || ix >= json_array_get_count(array)) {
+ return JSONFailure;
+ }
+ json_value_free(json_array_get_value(array, ix));
+ array->items[ix] = value;
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_string(JSON_Array *array, size_t i, const char* string) {
+ JSON_Value *value = json_value_init_string(string);
+ if (value == NULL)
+ return JSONFailure;
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_number(JSON_Array *array, size_t i, double number) {
+ JSON_Value *value = json_value_init_number(number);
+ if (value == NULL)
+ return JSONFailure;
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_boolean(JSON_Array *array, size_t i, int boolean) {
+ JSON_Value *value = json_value_init_boolean(boolean);
+ if (value == NULL)
+ return JSONFailure;
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_replace_null(JSON_Array *array, size_t i) {
+ JSON_Value *value = json_value_init_null();
+ if (value == NULL)
+ return JSONFailure;
+ if (json_array_replace_value(array, i, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_clear(JSON_Array *array) {
+ size_t i = 0;
+ if (array == NULL)
+ return JSONFailure;
+ for (i = 0; i < json_array_get_count(array); i++) {
+ json_value_free(json_array_get_value(array, i));
+ }
+ array->count = 0;
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_value(JSON_Array *array, JSON_Value *value) {
+ if (array == NULL || value == NULL)
+ return JSONFailure;
+ return json_array_add(array, value);
+}
+
+JSON_Status json_array_append_string(JSON_Array *array, const char *string) {
+ JSON_Value *value = json_value_init_string(string);
+ if (value == NULL)
+ return JSONFailure;
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_number(JSON_Array *array, double number) {
+ JSON_Value *value = json_value_init_number(number);
+ if (value == NULL)
+ return JSONFailure;
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_boolean(JSON_Array *array, int boolean) {
+ JSON_Value *value = json_value_init_boolean(boolean);
+ if (value == NULL)
+ return JSONFailure;
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_array_append_null(JSON_Array *array) {
+ JSON_Value *value = json_value_init_null();
+ if (value == NULL)
+ return JSONFailure;
+ if (json_array_append_value(array, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_set_value(JSON_Object *object, const char *name, JSON_Value *value) {
+ size_t i = 0;
+ JSON_Value *old_value;
+ if (object == NULL || name == NULL || value == NULL)
+ return JSONFailure;
+ old_value = json_object_get_value(object, name);
+ if (old_value != NULL) { /* free and overwrite old value */
+ json_value_free(old_value);
+ for (i = 0; i < json_object_get_count(object); i++) {
+ if (strcmp(object->names[i], name) == 0) {
+ object->values[i] = value;
+ return JSONSuccess;
+ }
+ }
+ }
+ /* add new key value pair */
+ return json_object_add(object, name, value);
+}
+
+JSON_Status json_object_set_string(JSON_Object *object, const char *name, const char *string) {
+ return json_object_set_value(object, name, json_value_init_string(string));
+}
+
+JSON_Status json_object_set_number(JSON_Object *object, const char *name, double number) {
+ return json_object_set_value(object, name, json_value_init_number(number));
+}
+
+JSON_Status json_object_set_boolean(JSON_Object *object, const char *name, int boolean) {
+ return json_object_set_value(object, name, json_value_init_boolean(boolean));
+}
+
+JSON_Status json_object_set_null(JSON_Object *object, const char *name) {
+ return json_object_set_value(object, name, json_value_init_null());
+}
+
+JSON_Status json_object_dotset_value(JSON_Object *object, const char *name, JSON_Value *value) {
+ const char *dot_pos = NULL;
+ char *current_name = NULL;
+ JSON_Object *temp_obj = NULL;
+ JSON_Value *new_value = NULL;
+ if (value == NULL || name == NULL || value == NULL)
+ return JSONFailure;
+ dot_pos = strchr(name, '.');
+ if (dot_pos == NULL) {
+ return json_object_set_value(object, name, value);
+ } else {
+ current_name = parson_strndup(name, dot_pos - name);
+ temp_obj = json_object_get_object(object, current_name);
+ if (temp_obj == NULL) {
+ new_value = json_value_init_object();
+ if (new_value == NULL) {
+ parson_free(current_name);
+ return JSONFailure;
+ }
+ if (json_object_add(object, current_name, new_value) == JSONFailure) {
+ json_value_free(new_value);
+ parson_free(current_name);
+ return JSONFailure;
+ }
+ temp_obj = json_object_get_object(object, current_name);
+ }
+ parson_free(current_name);
+ return json_object_dotset_value(temp_obj, dot_pos + 1, value);
+ }
+}
+
+JSON_Status json_object_dotset_string(JSON_Object *object, const char *name, const char *string) {
+ JSON_Value *value = json_value_init_string(string);
+ if (value == NULL)
+ return JSONFailure;
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_dotset_number(JSON_Object *object, const char *name, double number) {
+ JSON_Value *value = json_value_init_number(number);
+ if (value == NULL)
+ return JSONFailure;
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_dotset_boolean(JSON_Object *object, const char *name, int boolean) {
+ JSON_Value *value = json_value_init_boolean(boolean);
+ if (value == NULL)
+ return JSONFailure;
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_dotset_null(JSON_Object *object, const char *name) {
+ JSON_Value *value = json_value_init_null();
+ if (value == NULL)
+ return JSONFailure;
+ if (json_object_dotset_value(object, name, value) == JSONFailure) {
+ json_value_free(value);
+ return JSONFailure;
+ }
+ return JSONSuccess;
+}
+
+JSON_Status json_object_remove(JSON_Object *object, const char *name) {
+ size_t i = 0, last_item_index = 0;
+ if (object == NULL || json_object_get_value(object, name) == NULL)
+ return JSONFailure;
+ last_item_index = json_object_get_count(object) - 1;
+ for (i = 0; i < json_object_get_count(object); i++) {
+ if (strcmp(object->names[i], name) == 0) {
+ parson_free(object->names[i]);
+ json_value_free(object->values[i]);
+ if (i != last_item_index) { /* Replace key value pair with one from the end */
+ object->names[i] = object->names[last_item_index];
+ object->values[i] = object->values[last_item_index];
+ }
+ object->count -= 1;
+ return JSONSuccess;
+ }
+ }
+ return JSONFailure; /* No execution path should end here */
+}
+
+JSON_Status json_object_dotremove(JSON_Object *object, const char *name) {
+ const char *dot_pos = strchr(name, '.');
+ char *current_name = NULL;
+ JSON_Object *temp_obj = NULL;
+ if (dot_pos == NULL) {
+ return json_object_remove(object, name);
+ } else {
+ current_name = parson_strndup(name, dot_pos - name);
+ temp_obj = json_object_get_object(object, current_name);
+ if (temp_obj == NULL) {
+ parson_free(current_name);
+ return JSONFailure;
+ }
+ parson_free(current_name);
+ return json_object_dotremove(temp_obj, dot_pos + 1);
+ }
+}
+
+JSON_Status json_object_clear(JSON_Object *object) {
+ size_t i = 0;
+ if (object == NULL) {
+ return JSONFailure;
+ }
+ for (i = 0; i < json_object_get_count(object); i++) {
+ parson_free(object->names[i]);
+ json_value_free(object->values[i]);
+ }
+ object->count = 0;
+ return JSONSuccess;
+}
+
+JSON_Status json_validate(const JSON_Value *schema, const JSON_Value *value) {
+ JSON_Value *temp_schema_value = NULL, *temp_value = NULL;
+ JSON_Array *schema_array = NULL, *value_array = NULL;
+ JSON_Object *schema_object = NULL, *value_object = NULL;
+ JSON_Value_Type schema_type = JSONError, value_type = JSONError;
+ const char *key = NULL;
+ size_t i = 0, count = 0;
+ if (schema == NULL || value == NULL)
+ return JSONFailure;
+ schema_type = json_value_get_type(schema);
+ value_type = json_value_get_type(value);
+ if (schema_type != value_type && schema_type != JSONNull) /* null represents all values */
+ return JSONFailure;
+ switch (schema_type) {
+ case JSONArray:
+ schema_array = json_value_get_array(schema);
+ value_array = json_value_get_array(value);
+ count = json_array_get_count(schema_array);
+ if (count == 0)
+ return JSONSuccess; /* Empty array allows all types */
+ /* Get first value from array, rest is ignored */
+ temp_schema_value = json_array_get_value(schema_array, 0);
+ for (i = 0; i < json_array_get_count(value_array); i++) {
+ temp_value = json_array_get_value(value_array, i);
+ if (json_validate(temp_schema_value, temp_value) == 0) {
+ return JSONFailure;
+ }
+ }
+ return JSONSuccess;
+ case JSONObject:
+ schema_object = json_value_get_object(schema);
+ value_object = json_value_get_object(value);
+ count = json_object_get_count(schema_object);
+ if (count == 0)
+ return JSONSuccess; /* Empty object allows all objects */
+ else if (json_object_get_count(value_object) < count)
+ return JSONFailure; /* Tested object mustn't have less name-value pairs than schema */
+ for (i = 0; i < count; i++) {
+ key = json_object_get_name(schema_object, i);
+ temp_schema_value = json_object_get_value(schema_object, key);
+ temp_value = json_object_get_value(value_object, key);
+ if (temp_value == NULL)
+ return JSONFailure;
+ if (json_validate(temp_schema_value, temp_value) == JSONFailure)
+ return JSONFailure;
+ }
+ return JSONSuccess;
+ case JSONString: case JSONNumber: case JSONBoolean: case JSONNull:
+ return JSONSuccess; /* equality already tested before switch */
+ case JSONError: default:
+ return JSONFailure;
+ }
+}
+
+JSON_Status json_value_equals(const JSON_Value *a, const JSON_Value *b) {
+ JSON_Object *a_object = NULL, *b_object = NULL;
+ JSON_Array *a_array = NULL, *b_array = NULL;
+ const char *a_string = NULL, *b_string = NULL;
+ const char *key = NULL;
+ size_t a_count = 0, b_count = 0, i = 0;
+ JSON_Value_Type a_type, b_type;
+ a_type = json_value_get_type(a);
+ b_type = json_value_get_type(b);
+ if (a_type != b_type) {
+ return 0;
+ }
+ switch (a_type) {
+ case JSONArray:
+ a_array = json_value_get_array(a);
+ b_array = json_value_get_array(b);
+ a_count = json_array_get_count(a_array);
+ b_count = json_array_get_count(b_array);
+ if (a_count != b_count) {
+ return 0;
+ }
+ for (i = 0; i < a_count; i++) {
+ if (!json_value_equals(json_array_get_value(a_array, i),
+ json_array_get_value(b_array, i))) {
+ return 0;
+ }
+ }
+ return 1;
+ case JSONObject:
+ a_object = json_value_get_object(a);
+ b_object = json_value_get_object(b);
+ a_count = json_object_get_count(a_object);
+ b_count = json_object_get_count(b_object);
+ if (a_count != b_count) {
+ return 0;
+ }
+ for (i = 0; i < a_count; i++) {
+ key = json_object_get_name(a_object, i);
+ if (!json_value_equals(json_object_get_value(a_object, key),
+ json_object_get_value(b_object, key))) {
+ return 0;
+ }
+ }
+ return 1;
+ case JSONString:
+ a_string = json_value_get_string(a);
+ b_string = json_value_get_string(b);
+ return strcmp(a_string, b_string) == 0;
+ case JSONBoolean:
+ return json_value_get_boolean(a) == json_value_get_boolean(b);
+ case JSONNumber:
+ return fabs(json_value_get_number(a) - json_value_get_number(b)) < 0.000001; /* EPSILON */
+ case JSONError:
+ return 1;
+ case JSONNull:
+ return 1;
+ default:
+ return 1;
+ }
+}
+
+JSON_Value_Type json_type(const JSON_Value *value) {
+ return json_value_get_type(value);
+}
+
+JSON_Object * json_object (const JSON_Value *value) {
+ return json_value_get_object(value);
+}
+
+JSON_Array * json_array (const JSON_Value *value) {
+ return json_value_get_array(value);
+}
+
+const char * json_string (const JSON_Value *value) {
+ return json_value_get_string(value);
+}
+
+double json_number (const JSON_Value *value) {
+ return json_value_get_number(value);
+}
+
+int json_boolean(const JSON_Value *value) {
+ return json_value_get_boolean(value);
+}
+
+void json_set_allocation_functions(JSON_Malloc_Function malloc_fun, JSON_Free_Function free_fun) {
+ parson_malloc = malloc_fun;
+ parson_free = free_fun;
+}
diff --git a/lora_pkt_fwd/src/timersync.c b/lora_pkt_fwd/src/timersync.c
new file mode 100644
index 0000000..3dd919b
--- /dev/null
+++ b/lora_pkt_fwd/src/timersync.c
@@ -0,0 +1,146 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ LoRa concentrator : Timer synchronization
+ Provides synchronization between unix, concentrator and gps clocks
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdio.h> /* printf, fprintf, snprintf, fopen, fputs */
+#include <stdint.h> /* C99 types */
+#include <pthread.h>
+
+#include "trace.h"
+#include "timersync.h"
+#include "loragw_hal.h"
+#include "loragw_reg.h"
+#include "loragw_aux.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define timersub(a, b, result) \
+ do { \
+ (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
+ (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
+ if ((result)->tv_usec < 0) { \
+ --(result)->tv_sec; \
+ (result)->tv_usec += 1000000; \
+ } \
+ } while (0)
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */
+
+static pthread_mutex_t mx_timersync = PTHREAD_MUTEX_INITIALIZER; /* control access to timer sync offsets */
+static struct timeval offset_unix_concent = {0,0}; /* timer offset between unix host and concentrator */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE SHARED VARIABLES (GLOBAL) ------------------------------------ */
+extern bool exit_sig;
+extern bool quit_sig;
+extern pthread_mutex_t mx_concent;
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
+
+int get_concentrator_time(struct timeval *concent_time, struct timeval unix_time) {
+ struct timeval local_timeval;
+
+ if (concent_time == NULL) {
+ MSG("ERROR: %s invalid parameter\n", __FUNCTION__);
+ return -1;
+ }
+
+ pthread_mutex_lock(&mx_timersync); /* protect global variable access */
+ timersub(&unix_time, &offset_unix_concent, &local_timeval);
+ pthread_mutex_unlock(&mx_timersync);
+
+ /* TODO: handle sx1301 coutner wrap-up !! */
+ concent_time->tv_sec = local_timeval.tv_sec;
+ concent_time->tv_usec = local_timeval.tv_usec;
+
+ MSG_DEBUG(DEBUG_TIMERSYNC, " --> TIME: unix current time is %ld,%ld\n", unix_time.tv_sec, unix_time.tv_usec);
+ MSG_DEBUG(DEBUG_TIMERSYNC, " offset is %ld,%ld\n", offset_unix_concent.tv_sec, offset_unix_concent.tv_usec);
+ MSG_DEBUG(DEBUG_TIMERSYNC, " sx1301 current time is %ld,%ld\n", local_timeval.tv_sec, local_timeval.tv_usec);
+
+ return 0;
+}
+
+/* ---------------------------------------------------------------------------------------------- */
+/* --- THREAD 6: REGULARLAY MONITOR THE OFFSET BETWEEN UNIX CLOCK AND CONCENTRATOR CLOCK -------- */
+
+void thread_timersync(void) {
+ struct timeval unix_timeval;
+ struct timeval concentrator_timeval;
+ uint32_t sx1301_timecount = 0;
+ struct timeval offset_previous = {0,0};
+ struct timeval offset_drift = {0,0}; /* delta between current and previous offset */
+
+ while (!exit_sig && !quit_sig) {
+ /* Regularly disable GPS mode of concentrator's counter, in order to get
+ real timer value for synchronizing with host's unix timer */
+ MSG("\nINFO: Disabling GPS mode for concentrator's counter...\n");
+ pthread_mutex_lock(&mx_concent);
+ lgw_reg_w(LGW_GPS_EN, 0);
+ pthread_mutex_unlock(&mx_concent);
+
+ /* Get current unix time */
+ gettimeofday(&unix_timeval, NULL);
+
+ /* Get current concentrator counter value (1MHz) */
+ pthread_mutex_lock(&mx_concent);
+ lgw_get_trigcnt(&sx1301_timecount);
+ pthread_mutex_unlock(&mx_concent);
+ concentrator_timeval.tv_sec = sx1301_timecount / 1000000UL;
+ concentrator_timeval.tv_usec = sx1301_timecount - (concentrator_timeval.tv_sec * 1000000UL);
+
+ /* Compute offset between unix and concentrator timers, with microsecond precision */
+ offset_previous.tv_sec = offset_unix_concent.tv_sec;
+ offset_previous.tv_usec = offset_unix_concent.tv_usec;
+
+ /* TODO: handle sx1301 coutner wrap-up */
+ pthread_mutex_lock(&mx_timersync); /* protect global variable access */
+ timersub(&unix_timeval, &concentrator_timeval, &offset_unix_concent);
+ pthread_mutex_unlock(&mx_timersync);
+
+ timersub(&offset_unix_concent, &offset_previous, &offset_drift);
+
+ MSG_DEBUG(DEBUG_TIMERSYNC, " sx1301 = %u (µs) - timeval (%ld,%ld)\n",
+ sx1301_timecount,
+ concentrator_timeval.tv_sec,
+ concentrator_timeval.tv_usec);
+ MSG_DEBUG(DEBUG_TIMERSYNC, " unix_timeval = %ld,%ld\n", unix_timeval.tv_sec, unix_timeval.tv_usec);
+
+ MSG("INFO: host/sx1301 time offset=(%lds:%ldµs) - drift=%ldµs\n",
+ offset_unix_concent.tv_sec,
+ offset_unix_concent.tv_usec,
+ offset_drift.tv_sec * 1000000UL + offset_drift.tv_usec);
+ MSG("INFO: Enabling GPS mode for concentrator's counter.\n\n");
+ pthread_mutex_lock(&mx_concent); /* TODO: Is it necessary to protect here? */
+ lgw_reg_w(LGW_GPS_EN, 1);
+ pthread_mutex_unlock(&mx_concent);
+
+ /* delay next sync */
+ /* If we consider a crystal oscillator precision of about 20ppm worst case, and a clock
+ running at 1MHz, this would mean 1µs drift every 50000µs (10000000/20).
+ As here the time precision is not critical, we should be able to cope with at least 1ms drift,
+ which should occur after 50s (50000µs * 1000).
+ Let's set the thread sleep to 1 minute for now */
+ wait_ms(60000);
+ }
+}
diff --git a/lora_pkt_fwd/update_gwid.sh b/lora_pkt_fwd/update_gwid.sh
new file mode 100755
index 0000000..2aeb87f
--- /dev/null
+++ b/lora_pkt_fwd/update_gwid.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+# This script is a helper to update the Gateway_ID field of given
+# JSON configuration file, as a EUI-64 address generated from the 48-bits MAC
+# address of the device it is run from.
+#
+# Usage examples:
+# ./update_gwid.sh ./local_conf.json
+
+iot_sk_update_gwid() {
+ # get gateway ID from its MAC address to generate an EUI-64 address
+ GWID_MIDFIX="FFFE"
+ GWID_BEGIN=$(ip link show eth0 | awk '/ether/ {print $2}' | awk -F\: '{print $1$2$3}')
+ GWID_END=$(ip link show eth0 | awk '/ether/ {print $2}' | awk -F\: '{print $4$5$6}')
+
+ # replace last 8 digits of default gateway ID by actual GWID, in given JSON configuration file
+ sed -i 's/\(^\s*"gateway_ID":\s*"\).\{16\}"\s*\(,\?\).*$/\1'${GWID_BEGIN}${GWID_MIDFIX}${GWID_END}'"\2/' $1
+
+ echo "Gateway_ID set to "$GWID_BEGIN$GWID_MIDFIX$GWID_END" in file "$1
+}
+
+if [ $# -ne 1 ]
+then
+ echo "Usage: $0 [filename]"
+ echo " filename: Path to JSON file containing Gateway_ID for packet forwarder"
+ exit 1
+fi
+
+iot_sk_update_gwid $1
+
+exit 0
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..d25574a
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,247 @@
+ / _____) _ | |
+ ( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+ (______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Lora network packet forwarder project
+======================================
+
+1. Core program: lora_pkt_fwd
+-------------------------------
+
+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.
+
+ ((( 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 |
+ +- - - - - - - - - - - - - - -+
+
+Uplink: radio packets received by the gateway, with metadata added by the
+gateway, forwarded to the server. Might also include gateway status.
+
+Downlink: packets generated by the server, with additional metadata, to be
+transmitted by the gateway on the radio channel. Might also include
+configuration data for the gateway.
+
+2. Helper programs
+-------------------
+
+Those programs are included in the project to provide examples on how to
+communicate with the packet forwarder, and to help the system builder use it
+without having to implement a full Lora network server.
+
+### 3.1. util_sink ###
+
+The packet sink is a simple helper program listening on a single port for UDP
+datagrams, and displaying a message each time one is received. The content of
+the datagram itself is ignored.
+
+### 3.2. util_ack ###
+
+The packet acknowledger is a simple helper program listening on a single UDP
+port and responding to PUSH_DATA datagrams with PUSH_ACK, and to PULL_DATA
+datagrams with PULL_ACK.
+
+### 3.3. util_tx_test ###
+
+The network packet sender is a simple helper program used to send packets
+through the gateway-to-server downlink route.
+
+4. Helper scripts
+-----------------
+
+### 4.1. lora_gateway/reset_lgw.sh
+
+This script, provided with the HAL (lora_gateway), must be launched on IoT Start
+Kit platform to reset concentrator chip through GPIO, before starting any
+application using the concentrator, like the packet forwarder.
+
+### 4.2. packet_forwarder/lora_pkt_fwd/update_gwid.sh
+
+This script allows automatic update of Gateway_ID with unique MAC address, in
+packet forwarder JSON configuration file.
+Please refer to the script header for more details.
+
+5. Changelog
+-------------
+
+### v4.0.1 - 2017-03-16 ###
+
+* Class-B: Added xtal error correction to beacon frequency
+* Class-B: Added support for all regions to beacon frame format (various
+datarates imply different frame sizes), as defined by LoRaWAN v1.1.
+
+### v4.0.0 - 2017-01-10 ###
+
+* Added Class-B support, as defined in LoRaWAN v1.1
+* Downlink only support "tmst" or "tmms" timestamp. "time" is not supported
+anymore ("time" field is kept in Uplink as an informative field).
+* Reworked thread_gps to handle GPS UBX messages for native GPS time.
+* Updated Gateway <-> NetworkServer protocol to describe the new "tmms" field.
+* Updated global_conf.PCB286*.json to remove indexes of the TX gain LUT above
+20dBm. Use PCB336 (aka GW v1.5) to comply with ETSI TX mask between 20dBm and
+27dBm.
+
+### v3.1.0 - 2016-09-07 ###
+
+* Updated "Listen-Before-Talk" JSON configuration to match with LBT rework.
+* Added TX Notch Filter JSON configuration.
+* Updated Parson library to latest version
+* Fixed Class-B beacon CRC-16 calculation
+* Removed JiT time_on_air local function, and use lgw_time_on_air() function
+
+### v3.0.0 - 2016-05-19 ###
+
+* Merged all different flavours of packet forwarder into one unique lora_pkt_fwd
+ Note: Various flavours can still be achieved using the corresponding
+ global_conf.json.XXX file provided in lora_pkt_fwd/cfg.
+* Added downlink "just-in-time" scheduling to optimize downlink capacity.
+* Updated Gateway <-> NetworkServer protocol to describe the new format of
+"tx_ack" message.
+* Added "Listen-Before-Talk" JSON configuration.
+* Splitted reset_pkt_fwd.sh script in 2 different scripts:
+ - reset_lgw.sh, provided with the HAL (lora_gateway)
+ - update_gwid.sh, provided with lora_pkt_fwd
+
+WARNING: Gateway <-> Network Server protocol version has changed. Please refer
+ to PROTOCOL.txt file.
+
+### v2.2.1 - 2016-04-12 ###
+
+* util_tx_test: added FSK support and specific payload for easier PER testing.
+* base64: fixed padding check.
+* Updated all makefiles to handle the creation of obj directory when necessary.
+* [gps/beacon]_pkt_fwd: fixed crash on exit when GPS not enabled.
+* [*]_pkt_fwd: added a cfg/ directory containing different flavours or the
+global_conf.json file for different boards: Ref Design PCB_E336 (GW1.5-27dBm),
+Ref Design PCB_E286 (GW1.0), Ref Design with US902 frequency plan.
+
+### v2.2.0 - 2015-10-08 ###
+
+* Removed FTDI support in makefiles to align with HAL v3.2.0.
+* Force IPv4 mode usage on UDP socket, instead of auto. The auto mode was
+causing an issue to properly resolve LoRa server hostname given in JSON
+configuration file (MariaDB issue: https://mariadb.atlassian.net/browse/MDEV-4356,
+https://mariadb.atlassian.net/browse/MDEV-4379).
+
+### v2.1.0 - 2015-06-29 ###
+
+* Added helper script for concentrator reset through GPIO, needed on IoT
+Starter Kit (reset_pkt_fwd.sh).
+* The same reset_pkt_fwd.sh script also allows to automatically update the
+Gateway_ID field in JSON configuration file, with board MAC address.
+* Updated JSON configuration file with proper default value for IoT Starter
+Kit: server address set to local server, GPS device path set to proper value
+(/dev/ttyAMA0).
+
+### v2.0.0 - 2015-04-30 ###
+
+* Changed: Several configuration parameters are now set dynamically from the
+JSON configuration file: RSSI offset, concentrator clock source, radio type,
+TX gain table, network type. The HAL does not need to be recompiled any more to
+update those parameters. An example for IoT Starter Kit platform is provided in
+global_conf.json for basic, gps and beacon packet_forwarder.
+* Removed: Band frequency JSON configuration file has been removed. An example
+for EU 868MHz is provided in global_conf.json for basic, gps and beacon packet
+forwarder.
+* Changed: Updated makefiles to allow cross compilation from environment
+variable (ARCH, CROSS_COMPILE).
+
+** WARNING: **
+** Update your JSON configuration file with new dynamic parameters. **
+
+### v1.4.1 - 2015-01-23 ###
+
+* Bugfix: fixed LP-116, fdev parameter parsed incorrectly, making FSK TX fail.
+* Bugfix: fixed a platform-dependant minor rounding issue.
+* Beta: updated beacon format, partially aligned with latest class B proposal.
+
+### v1.4.0 - 2014-10-16 ###
+
+* Feature: Adding TX FSK support.
+* Feature: optional auto-quit if a certain number of PULL_ACK is missed.
+* Feature: upstream and downstream ping time is displayed on console.
+* Bugfix: some beacons were missed at high beaconing frequency.
+* Bugfix: critical snprintf error caused a crash for long payloads.
+* FSK bitrate now appears in the upstream JSON.
+
+### v1.3.0 - 2014-03-28 ###
+
+* Feature: adding preliminary beacon support for class B development.
+* Solved warnings with 64b integer printf when compiling on x86_64.
+* Updated build system for easier deployment on various hardware.
+* Changed threads organization in the forwarder programs.
+* Removed duplicate protocol documentation.
+
+### v1.2.0 - 2014-02-03 ###
+
+* Feature: added a GPS-enabled packet forwarder, used to timestamp received
+packet with a globally-synchronous microsecond-accurate timestamp.
+* Feature: GPS packet forwarder sends status report on the uplink, protocol
+specification updated accordingly (report include gateway geolocation).
+* Feature: packets can be sent without CRC at radio layer.
+* Bugfix: no more crash with base64 padded input.
+* Bugfix: no more rounding errors on the 'freq' value sent to server.
+* A minimum preamble of 6 Lora symbol is enforced for optimum sensitivity.
+* Padded Base64 is sent on uplink, downlink accepts padded and unpadded Base64.
+* Updated the Parson JSON library to a version that supports comments.
+* Added .md (Markdown) extension to readme files for better Github viewing.
+
+### v1.1.0 - 2013-12-09 ###
+
+* Feature: added packet filtering parameters to the JSON configuration files.
+* Bugfix: will not send a datagram if all the packets returned by the receive()
+function have been filtered out.
+* Bugfix: removed leading zeros for the timestamp in the upstream JSON because
+it is not compliant with JSON standard (might be interpreted as octal number).
+* Removed TXT extension for README files for better Github integration.
+* Cleaned-up documentation, moving change log to top README.
+* Modified Makefiles to ease cross-compilation.
+
+### v1.0.0 - 2013-11-22 ###
+
+* Initial release of the packet forwarder, protocol specifications and helper
+programs.
+
+6. Legal notice
+----------------
+
+The information presented in this project documentation does not form part of
+any quotation or contract, is believed to be accurate and reliable and may be
+changed without notice. No liability will be accepted by the publisher for any
+consequence of its use. Publication thereof does not convey nor imply any
+license under patent or other industrial or intellectual property rights.
+Semtech assumes no responsibility or liability whatsoever for any failure or
+unexpected operation resulting from misuse, neglect improper installation,
+repair or improper handling or unusual physical or electrical stress
+including, but not limited to, exposure to parameters beyond the specified
+maximum ratings or operation outside the specified range.
+
+SEMTECH PRODUCTS ARE NOT DESIGNED, INTENDED, AUTHORIZED OR WARRANTED TO BE
+SUITABLE FOR USE IN LIFE-SUPPORT APPLICATIONS, DEVICES OR SYSTEMS OR OTHER
+CRITICAL APPLICATIONS. INCLUSION OF SEMTECH PRODUCTS IN SUCH APPLICATIONS IS
+UNDERSTOOD TO BE UNDERTAKEN SOLELY AT THE CUSTOMERS OWN RISK. Should a
+customer purchase or use Semtech products for any such unauthorized
+application, the customer shall indemnify and hold Semtech and its officers,
+employees, subsidiaries, affiliates, and distributors harmless against all
+claims, costs damages and attorney fees which could arise.
+
+*EOF*
diff --git a/util_ack/Makefile b/util_ack/Makefile
new file mode 100644
index 0000000..4990099
--- /dev/null
+++ b/util_ack/Makefile
@@ -0,0 +1,33 @@
+### Application-specific constants
+
+APP_NAME := util_ack
+
+### Constant symbols
+
+CC := $(CROSS_COMPILE)gcc
+AR := $(CROSS_COMPILE)ar
+
+CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I.
+
+OBJDIR = obj
+
+### General build targets
+
+all: $(APP_NAME)
+
+clean:
+ rm -f $(OBJDIR)/*.o
+ rm -f $(APP_NAME)
+
+### Main program compilation and assembly
+
+$(OBJDIR):
+ mkdir -p $(OBJDIR)
+
+$(OBJDIR)/%.o: src/%.c | $(OBJDIR)
+ $(CC) -c $(CFLAGS) $< -o $@
+
+$(APP_NAME): $(OBJDIR)/$(APP_NAME).o
+ $(CC) $< -o $@
+
+### EOF \ No newline at end of file
diff --git a/util_ack/readme.md b/util_ack/readme.md
new file mode 100644
index 0000000..9de6a40
--- /dev/null
+++ b/util_ack/readme.md
@@ -0,0 +1,65 @@
+ / _____) _ | |
+ ( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+ (______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Utility: packet acknowledger
+=============================
+
+1. Introduction
+----------------
+
+The packet acknowledger is a simple helper program listening on a single UDP
+port and responding to PUSH_DATA datagrams with PUSH_ACK, and to PULL_DATA
+datagrams with PULL_ACK.
+
+Informations about the datagrams received and the answers send are display on
+screen to help communication debugging.
+
+Packets not following the protocol detailed in the PROTOCOL.TXT document in the
+basic_pkt_fwt directory are ignored.
+
+2. Dependencies
+----------------
+
+This program follows the v1.1 version of the gateway-to-server protocol.
+
+3. Usage
+---------
+
+Start the program with the port number as first and only argument.
+
+To stop the application, press Ctrl+C.
+
+4. License
+-----------
+
+Copyright (C) 2013, 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.
+
+*EOF* \ No newline at end of file
diff --git a/util_ack/src/util_ack.c b/util_ack/src/util_ack.c
new file mode 100644
index 0000000..7acd32e
--- /dev/null
+++ b/util_ack/src/util_ack.c
@@ -0,0 +1,193 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Network sink, receives UDP packets and sends an acknowledge
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+/* fix an issue between POSIX and C99 */
+#if __STDC_VERSION__ >= 199901L
+ #define _XOPEN_SOURCE 600
+#else
+ #define _XOPEN_SOURCE 500
+#endif
+
+#include <stdint.h> /* C99 types */
+#include <stdio.h> /* printf, fprintf, sprintf, fopen, fputs */
+#include <unistd.h> /* usleep */
+
+#include <string.h> /* memset */
+#include <time.h> /* time, clock_gettime, strftime, gmtime, clock_nanosleep*/
+#include <stdlib.h> /* atoi, exit */
+#include <errno.h> /* error messages */
+
+#include <sys/socket.h> /* socket specific definitions */
+#include <netinet/in.h> /* INET constants and stuff */
+#include <arpa/inet.h> /* IP address conversion stuff */
+#include <netdb.h> /* gai_strerror */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#define STRINGIFY(x) #x
+#define STR(x) STRINGIFY(x)
+#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#define PROTOCOL_VERSION 2
+
+#define PKT_PUSH_DATA 0
+#define PKT_PUSH_ACK 1
+#define PKT_PULL_DATA 2
+#define PKT_PULL_RESP 3
+#define PKT_PULL_ACK 4
+
+/* -------------------------------------------------------------------------- */
+/* --- MAIN FUNCTION -------------------------------------------------------- */
+
+int main(int argc, char **argv)
+{
+ int i; /* loop variable and temporary variable for return value */
+
+ /* server socket creation */
+ int sock; /* socket file descriptor */
+ struct addrinfo hints;
+ struct addrinfo *result; /* store result of getaddrinfo */
+ struct addrinfo *q; /* pointer to move into *result data */
+ char host_name[64];
+ char port_name[64];
+
+ /* variables for receiving and sending packets */
+ struct sockaddr_storage dist_addr;
+ socklen_t addr_len = sizeof dist_addr;
+ uint8_t databuf[4096];
+ int byte_nb;
+
+ /* variables for protocol management */
+ uint32_t raw_mac_h; /* Most Significant Nibble, network order */
+ uint32_t raw_mac_l; /* Least Significant Nibble, network order */
+ uint64_t gw_mac; /* MAC address of the client (gateway) */
+ uint8_t ack_command;
+
+ /* check if port number was passed as parameter */
+ if (argc != 2) {
+ MSG("Usage: util_ack <port number>\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* prepare hints to open network sockets */
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC; /* should handle IP v4 or v6 automatically */
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE; /* will assign local IP automatically */
+
+ /* look for address */
+ i = getaddrinfo(NULL, argv[1], &hints, &result);
+ if (i != 0) {
+ MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i));
+ exit(EXIT_FAILURE);
+ }
+
+ /* try to open socket and bind it */
+ for (q=result; q!=NULL; q=q->ai_next) {
+ sock = socket(q->ai_family, q->ai_socktype,q->ai_protocol);
+ if (sock == -1) {
+ continue; /* socket failed, try next field */
+ } else {
+ i = bind(sock, q->ai_addr, q->ai_addrlen);
+ if (i == -1) {
+ shutdown(sock, SHUT_RDWR);
+ continue; /* bind failed, try next field */
+ } else {
+ break; /* success, get out of loop */
+ }
+ }
+ }
+ if (q == NULL) {
+ MSG("ERROR: failed to open socket or to bind to it\n");
+ i = 1;
+ for (q=result; q!=NULL; q=q->ai_next) {
+ getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
+ MSG("INFO: result %i host:%s service:%s\n", i, host_name, port_name);
+ ++i;
+ }
+ exit(EXIT_FAILURE);
+ }
+ MSG("INFO: util_ack listening on port %s\n", argv[1]);
+ freeaddrinfo(result);
+
+ while (1) {
+ /* wait to receive a packet */
+ byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len);
+ if (byte_nb == -1) {
+ MSG("ERROR: recvfrom returned %s \n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ /* display info about the sender */
+ i = getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
+ if (i == -1) {
+ MSG("ERROR: getnameinfo returned %s \n", gai_strerror(i));
+ exit(EXIT_FAILURE);
+ }
+ printf(" -> pkt in , host %s (port %s), %i bytes", host_name, port_name, byte_nb);
+
+ /* check and parse the payload */
+ if (byte_nb < 12) { /* not enough bytes for packet from gateway */
+ printf(" (too short for GW <-> MAC protocol)\n");
+ continue;
+ }
+ /* don't touch the token in position 1-2, it will be sent back "as is" for acknowledgement */
+ if (databuf[0] != PROTOCOL_VERSION) { /* check protocol version number */
+ printf(", invalid version %u\n", databuf[0]);
+ continue;
+ }
+ raw_mac_h = *((uint32_t *)(databuf+4));
+ raw_mac_l = *((uint32_t *)(databuf+8));
+ gw_mac = ((uint64_t)ntohl(raw_mac_h) << 32) + (uint64_t)ntohl(raw_mac_l);
+
+ /* interpret gateway command */
+ switch (databuf[3]) {
+ case PKT_PUSH_DATA:
+ printf(", PUSH_DATA from gateway 0x%08X%08X\n", (uint32_t)(gw_mac >> 32), (uint32_t)(gw_mac & 0xFFFFFFFF));
+ ack_command = PKT_PUSH_ACK;
+ printf("<- pkt out, PUSH_ACK for host %s (port %s)", host_name, port_name);
+ break;
+ case PKT_PULL_DATA:
+ printf(", PULL_DATA from gateway 0x%08X%08X\n", (uint32_t)(gw_mac >> 32), (uint32_t)(gw_mac & 0xFFFFFFFF));
+ ack_command = PKT_PULL_ACK;
+ printf("<- pkt out, PULL_ACK for host %s (port %s)", host_name, port_name);
+ break;
+ default:
+ printf(", unexpected command %u\n", databuf[3]);
+ continue;
+ }
+
+ /* add some artificial latency */
+ usleep(30000); /* 30 ms */
+
+ /* send acknowledge and check return value */
+ databuf[3] = ack_command;
+ byte_nb = sendto(sock, (void *)databuf, 4, 0, (struct sockaddr *)&dist_addr, addr_len);
+ if (byte_nb == -1) {
+ printf(", send error:%s\n", strerror(errno));
+ } else {
+ printf(", %i bytes sent\n", byte_nb);
+ }
+ }
+}
diff --git a/util_sink/Makefile b/util_sink/Makefile
new file mode 100644
index 0000000..cc6a751
--- /dev/null
+++ b/util_sink/Makefile
@@ -0,0 +1,33 @@
+### Application-specific constants
+
+APP_NAME := util_sink
+
+### Constant symbols
+
+CC := $(CROSS_COMPILE)gcc
+AR := $(CROSS_COMPILE)ar
+
+CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I.
+
+OBJDIR = obj
+
+### General build targets
+
+all: $(APP_NAME)
+
+clean:
+ rm -f $(OBJDIR)/*.o
+ rm -f $(APP_NAME)
+
+### Main program compilation and assembly
+
+$(OBJDIR):
+ mkdir -p $(OBJDIR)
+
+$(OBJDIR)/%.o: src/%.c | $(OBJDIR)
+ $(CC) -c $(CFLAGS) $< -o $@
+
+$(APP_NAME): $(OBJDIR)/$(APP_NAME).o
+ $(CC) $< -o $@
+
+### EOF
diff --git a/util_sink/readme.md b/util_sink/readme.md
new file mode 100644
index 0000000..9bf8992
--- /dev/null
+++ b/util_sink/readme.md
@@ -0,0 +1,62 @@
+ / _____) _ | |
+ ( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+ (______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Utility: packet sink
+=====================
+
+1. Introduction
+----------------
+
+The packet sink is a simple helper program listening on a single port for UDP
+datagrams, and displaying a message each time one is received. The content of
+the datagram itself is ignored.
+
+This allow to test another software (locally or on another computer) that
+sends UDP datagrams without having ICMP 'port closed' errors each time.
+
+2. Dependencies
+----------------
+
+None.
+
+3. Usage
+---------
+
+Start the program with the port number as first and only argument.
+
+To stop the application, press Ctrl+C.
+
+4. License
+-----------
+
+Copyright (C) 2013, 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.
+
+*EOF* \ No newline at end of file
diff --git a/util_sink/src/util_sink.c b/util_sink/src/util_sink.c
new file mode 100644
index 0000000..3c09c9b
--- /dev/null
+++ b/util_sink/src/util_sink.c
@@ -0,0 +1,125 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Network sink, receives UDP packets on certain ports and discards them
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+/* fix an issue between POSIX and C99 */
+#if __STDC_VERSION__ >= 199901L
+ #define _XOPEN_SOURCE 600
+#else
+ #define _XOPEN_SOURCE 500
+#endif
+
+#include <stdint.h> /* C99 types */
+#include <stdio.h> /* printf, fprintf, sprintf, fopen, fputs */
+
+#include <string.h> /* memset */
+#include <time.h> /* time, clock_gettime, strftime, gmtime, clock_nanosleep*/
+#include <stdlib.h> /* atoi, exit */
+#include <errno.h> /* error messages */
+
+#include <sys/socket.h> /* socket specific definitions */
+#include <netinet/in.h> /* INET constants and stuff */
+#include <arpa/inet.h> /* IP address conversion stuff */
+#include <netdb.h> /* gai_strerror */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#define STRINGIFY(x) #x
+#define STR(x) STRINGIFY(x)
+#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */
+
+/* -------------------------------------------------------------------------- */
+/* --- MAIN FUNCTION -------------------------------------------------------- */
+
+int main(int argc, char **argv)
+{
+ int i; /* loop variable and temporary variable for return value */
+
+ /* server socket creation */
+ int sock; /* socket file descriptor */
+ struct addrinfo hints;
+ struct addrinfo *result; /* store result of getaddrinfo */
+ struct addrinfo *q; /* pointer to move into *result data */
+ char host_name[64];
+ char port_name[64];
+
+ /* variables for receiving packets */
+ struct sockaddr_storage dist_addr;
+ socklen_t addr_len = sizeof dist_addr;
+ uint8_t databuf[4096];
+ int byte_nb;
+
+ /* check if port number was passed as parameter */
+ if (argc != 2) {
+ MSG("Usage: util_sink <port number>\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* prepare hints to open network sockets */
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC; /* should handle IP v4 or v6 automatically */
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE; /* will assign local IP automatically */
+
+ /* look for address */
+ i = getaddrinfo(NULL, argv[1], &hints, &result);
+ if (i != 0) {
+ MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i));
+ exit(EXIT_FAILURE);
+ }
+
+ /* try to open socket and bind it */
+ for (q=result; q!=NULL; q=q->ai_next) {
+ sock = socket(q->ai_family, q->ai_socktype,q->ai_protocol);
+ if (sock == -1) {
+ continue; /* socket failed, try next field */
+ } else {
+ i = bind(sock, q->ai_addr, q->ai_addrlen);
+ if (i == -1) {
+ shutdown(sock, SHUT_RDWR);
+ continue; /* bind failed, try next field */
+ } else {
+ break; /* success, get out of loop */
+ }
+ }
+ }
+ if (q == NULL) {
+ MSG("ERROR: failed to open socket or to bind to it\n");
+ i = 1;
+ for (q=result; q!=NULL; q=q->ai_next) {
+ getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
+ MSG("result %i host:%s service:%s\n", i, host_name, port_name);
+ ++i;
+ }
+ exit(EXIT_FAILURE);
+ }
+ MSG("INFO: util_sink listening on port %s\n", argv[1]);
+ freeaddrinfo(result);
+
+ while (1) {
+ byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len);
+ if (byte_nb == -1) {
+ MSG("ERROR: recvfrom returned %s \n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
+ printf("Got packet from host %s port %s, %i bytes long\n", host_name, port_name, byte_nb);
+ }
+}
diff --git a/util_tx_test/Makefile b/util_tx_test/Makefile
new file mode 100644
index 0000000..880b10d
--- /dev/null
+++ b/util_tx_test/Makefile
@@ -0,0 +1,36 @@
+### Application-specific constants
+
+APP_NAME := util_tx_test
+
+### Constant symbols
+
+CC := $(CROSS_COMPILE)gcc
+AR := $(CROSS_COMPILE)ar
+
+CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I.
+
+OBJDIR = obj
+INCLUDES = $(wildcard inc/*.h)
+
+### General build targets
+
+all: $(APP_NAME)
+
+clean:
+ rm -f $(OBJDIR)/*.o
+ rm -f $(APP_NAME)
+
+### Sub-modules compilation
+
+$(OBJDIR):
+ mkdir -p $(OBJDIR)
+
+$(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR)
+ $(CC) -c $(CFLAGS) $< -o $@
+
+### Main program assembly
+
+$(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(OBJDIR)/base64.o
+ $(CC) $< $(OBJDIR)/base64.o -o $@
+
+### EOF
diff --git a/util_tx_test/inc/base64.h b/util_tx_test/inc/base64.h
new file mode 100644
index 0000000..e57eb47
--- /dev/null
+++ b/util_tx_test/inc/base64.h
@@ -0,0 +1,62 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Base64 encoding & decoding library
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+#ifndef _BASE64_H
+#define _BASE64_H
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+/**
+@brief Encode binary data in Base64 string (no padding)
+@param in pointer to a table of binary data
+@param size number of bytes to be encoded to base64
+@param out pointer to a string where the function will output encoded data
+@param max_len max length of the out string (including null char)
+@return >=0 length of the resulting string (w/o null char), -1 for error
+*/
+int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len);
+
+/**
+@brief Decode Base64 string to binary data (no padding)
+@param in string containing only base64 valid characters
+@param size number of characters to be decoded from base64 (w/o null char)
+@param out pointer to a data buffer where the function will output decoded data
+@param out_max_len usable size of the output data buffer
+@return >=0 number of bytes written to the data buffer, -1 for error
+*/
+int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len);
+
+/* === derivative functions === */
+
+/**
+@brief Encode binary data in Base64 string (with added padding)
+*/
+int bin_to_b64(const uint8_t * in, int size, char * out, int max_len);
+
+/**
+@brief Decode Base64 string to binary data (remove padding if necessary)
+*/
+int b64_to_bin(const char * in, int size, uint8_t * out, int max_len);
+
+#endif
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/util_tx_test/readme.md b/util_tx_test/readme.md
new file mode 100644
index 0000000..f307292
--- /dev/null
+++ b/util_tx_test/readme.md
@@ -0,0 +1,77 @@
+ / _____) _ | |
+ ( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+ (______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Utility: network packet sender
+===============================
+
+1. Introduction
+----------------
+
+The network packet sender is a simple helper program used to send packets
+through the gateway-to-server downlink route.
+
+The program start by waiting for a gateway to send it a PULL_DATA datagram.
+After that, it will send back to the gateway a specified amount of PULL_RESP
+datagrams, each containing a packet to be sent immediately and a variable
+payload.
+
+2. Dependencies
+----------------
+
+This program follows the v1.1 version of the gateway-to-server protocol.
+
+3. Usage
+---------
+
+The application runs until the specified number of packets have been sent.
+Press Ctrl+C to stop the application before that.
+
+Use the -h option to get help and details about available options.
+
+The packets are [9-n] bytes long, and have following payload content:
+```
++----------+---------------+---------------+---------------+---------------+---+---+---+---+---+---+---+---+
+| Id | PktCnt[31:24] | PktCnt[23:16] | PktCnt[15:8] | PktCnt[7:0] | P | E | R |FCS| 0 | 1 |...| n |
++----------+---------------+---------------+---------------+---------------+---+---+---+---+---+---+---+---+
+
+Id : User defined ID to differentiate sender at receiver side. (8 bits)
+PktCnt : Packet counter incremented at each transmission. (32 bits)
+‘P’, ‘E’, ‘R’ : ASCII values for characters 'P', 'E' and 'R'.
+FCS : Checksum: 8-bits sum of Id, PktCnt[31 :24] , PktCnt[23 :16] , PktCnt[15 :8] , PktCnt[7:0], ‘P’,’E’,’R’
+0,1, ..., n : Padding bytes up until user specified payload length.
+```
+
+4. License
+-----------
+
+Copyright (C) 2013, 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.
+
+*EOF*
diff --git a/util_tx_test/src/base64.c b/util_tx_test/src/base64.c
new file mode 100644
index 0000000..8ba908e
--- /dev/null
+++ b/util_tx_test/src/base64.c
@@ -0,0 +1,308 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Base64 encoding & decoding library
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "base64.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#define CRIT(a) fprintf(stderr, "\nCRITICAL file:%s line:%u msg:%s\n", __FILE__, __LINE__,a);exit(EXIT_FAILURE)
+
+//#define DEBUG(args...) fprintf(stderr,"debug: " args) /* diagnostic message that is destined to the user */
+#define DEBUG(args...)
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MODULE-WIDE VARIABLES ---------------------------------------- */
+
+static char code_62 = '+'; /* RFC 1421 standard character for code 62 */
+static char code_63 = '/'; /* RFC 1421 standard character for code 63 */
+static char code_pad = '='; /* RFC 1421 padding character if padding */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
+
+/**
+@brief Convert a code in the range 0-63 to an ASCII character
+*/
+char code_to_char(uint8_t x);
+
+/**
+@brief Convert an ASCII character to a code in the range 0-63
+*/
+uint8_t char_to_code(char x);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+char code_to_char(uint8_t x) {
+ if (x <= 25) {
+ return 'A' + x;
+ } else if ((x >= 26) && (x <= 51)) {
+ return 'a' + (x-26);
+ } else if ((x >= 52) && (x <= 61)) {
+ return '0' + (x-52);
+ } else if (x == 62) {
+ return code_62;
+ } else if (x == 63) {
+ return code_63;
+ } else {
+ DEBUG("ERROR: %i IS OUT OF RANGE 0-63 FOR BASE64 ENCODING\n", x);
+ exit(EXIT_FAILURE);
+ } //TODO: improve error management
+}
+
+uint8_t char_to_code(char x) {
+ if ((x >= 'A') && (x <= 'Z')) {
+ return (uint8_t)x - (uint8_t)'A';
+ } else if ((x >= 'a') && (x <= 'z')) {
+ return (uint8_t)x - (uint8_t)'a' + 26;
+ } else if ((x >= '0') && (x <= '9')) {
+ return (uint8_t)x - (uint8_t)'0' + 52;
+ } else if (x == code_62) {
+ return 62;
+ } else if (x == code_63) {
+ return 63;
+ } else {
+ DEBUG("ERROR: %c (0x%x) IS INVALID CHARACTER FOR BASE64 DECODING\n", x, x);
+ exit(EXIT_FAILURE);
+ } //TODO: improve error management
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
+
+int bin_to_b64_nopad(const uint8_t * in, int size, char * out, int max_len) {
+ int i;
+ int result_len; /* size of the result */
+ int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */
+ int last_bytes; /* number of unsigned chars <3 in the last block */
+ int last_chars; /* number of characters <4 in the last block */
+ uint32_t b;
+
+ /* check input values */
+ if ((out == NULL) || (in == NULL)) {
+ DEBUG("ERROR: NULL POINTER AS OUTPUT IN BIN_TO_B64\n");
+ return -1;
+ }
+ if (size == 0) {
+ *out = 0; /* null string */
+ return 0;
+ }
+
+ /* calculate the number of base64 'blocks' */
+ full_blocks = size / 3;
+ last_bytes = size % 3;
+ switch (last_bytes) {
+ case 0: /* no byte left to encode */
+ last_chars = 0;
+ break;
+ case 1: /* 1 byte left to encode -> +2 chars */
+ last_chars = 2;
+ break;
+ case 2: /* 2 bytes left to encode -> +3 chars */
+ last_chars = 3;
+ break;
+ default:
+ CRIT("switch default that should not be possible");
+ }
+
+ /* check if output buffer is big enough */
+ result_len = (4*full_blocks) + last_chars;
+ if (max_len < (result_len + 1)) { /* 1 char added for string terminator */
+ DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN BIN_TO_B64\n");
+ return -1;
+ }
+
+ /* process all the full blocks */
+ for (i=0; i < full_blocks; ++i) {
+ b = (0xFF & in[3*i] ) << 16;
+ b |= (0xFF & in[3*i + 1]) << 8;
+ b |= 0xFF & in[3*i + 2];
+ out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
+ out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
+ out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F);
+ out[4*i + 3] = code_to_char( b & 0x3F);
+ }
+
+ /* process the last 'partial' block and terminate string */
+ i = full_blocks;
+ if (last_chars == 0) {
+ out[4*i] = 0; /* null character to terminate string */
+ } else if (last_chars == 2) {
+ b = (0xFF & in[3*i] ) << 16;
+ out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
+ out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
+ out[4*i + 2] = 0; /* null character to terminate string */
+ } else if (last_chars == 3) {
+ b = (0xFF & in[3*i] ) << 16;
+ b |= (0xFF & in[3*i + 1]) << 8;
+ out[4*i + 0] = code_to_char((b >> 18) & 0x3F);
+ out[4*i + 1] = code_to_char((b >> 12) & 0x3F);
+ out[4*i + 2] = code_to_char((b >> 6 ) & 0x3F);
+ out[4*i + 3] = 0; /* null character to terminate string */
+ }
+
+ return result_len;
+}
+
+int b64_to_bin_nopad(const char * in, int size, uint8_t * out, int max_len) {
+ int i;
+ int result_len; /* size of the result */
+ int full_blocks; /* number of 3 unsigned chars / 4 characters blocks */
+ int last_chars; /* number of characters <4 in the last block */
+ int last_bytes; /* number of unsigned chars <3 in the last block */
+ uint32_t b;
+ ;
+
+ /* check input values */
+ if ((out == NULL) || (in == NULL)) {
+ DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n");
+ return -1;
+ }
+ if (size == 0) {
+ return 0;
+ }
+
+ /* calculate the number of base64 'blocks' */
+ full_blocks = size / 4;
+ last_chars = size % 4;
+ switch (last_chars) {
+ case 0: /* no char left to decode */
+ last_bytes = 0;
+ break;
+ case 1: /* only 1 char left is an error */
+ DEBUG("ERROR: ONLY ONE CHAR LEFT IN B64_TO_BIN\n");
+ return -1;
+ case 2: /* 2 chars left to decode -> +1 byte */
+ last_bytes = 1;
+ break;
+ case 3: /* 3 chars left to decode -> +2 bytes */
+ last_bytes = 2;
+ break;
+ default:
+ CRIT("switch default that should not be possible");
+ }
+
+ /* check if output buffer is big enough */
+ result_len = (3*full_blocks) + last_bytes;
+ if (max_len < result_len) {
+ DEBUG("ERROR: OUTPUT BUFFER TOO SMALL IN B64_TO_BIN\n");
+ return -1;
+ }
+
+ /* process all the full blocks */
+ for (i=0; i < full_blocks; ++i) {
+ b = (0x3F & char_to_code(in[4*i] )) << 18;
+ b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
+ b |= (0x3F & char_to_code(in[4*i + 2])) << 6;
+ b |= 0x3F & char_to_code(in[4*i + 3]);
+ out[3*i + 0] = (b >> 16) & 0xFF;
+ out[3*i + 1] = (b >> 8 ) & 0xFF;
+ out[3*i + 2] = b & 0xFF;
+ }
+
+ /* process the last 'partial' block */
+ i = full_blocks;
+ if (last_bytes == 1) {
+ b = (0x3F & char_to_code(in[4*i] )) << 18;
+ b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
+ out[3*i + 0] = (b >> 16) & 0xFF;
+ if (((b >> 12) & 0x0F) != 0) {
+ DEBUG("WARNING: last character contains unusable bits\n");
+ }
+ } else if (last_bytes == 2) {
+ b = (0x3F & char_to_code(in[4*i] )) << 18;
+ b |= (0x3F & char_to_code(in[4*i + 1])) << 12;
+ b |= (0x3F & char_to_code(in[4*i + 2])) << 6;
+ out[3*i + 0] = (b >> 16) & 0xFF;
+ out[3*i + 1] = (b >> 8 ) & 0xFF;
+ if (((b >> 6) & 0x03) != 0) {
+ DEBUG("WARNING: last character contains unusable bits\n");
+ }
+ }
+
+ return result_len;
+}
+
+int bin_to_b64(const uint8_t * in, int size, char * out, int max_len) {
+ int ret;
+
+ ret = bin_to_b64_nopad(in, size, out, max_len);
+
+ if (ret == -1) {
+ return -1;
+ }
+ switch (ret%4) {
+ case 0: /* nothing to do */
+ return ret;
+ case 1:
+ DEBUG("ERROR: INVALID UNPADDED BASE64 STRING\n");
+ return -1;
+ case 2: /* 2 chars in last block, must add 2 padding char */
+ if (max_len >= (ret + 2 + 1)) {
+ out[ret] = code_pad;
+ out[ret+1] = code_pad;
+ out[ret+2] = 0;
+ return ret+2;
+ } else {
+ DEBUG("ERROR: not enough room to add padding in bin_to_b64\n");
+ return -1;
+ }
+ case 3: /* 3 chars in last block, must add 1 padding char */
+ if (max_len >= (ret + 1 + 1)) {
+ out[ret] = code_pad;
+ out[ret+1] = 0;
+ return ret+1;
+ } else {
+ DEBUG("ERROR: not enough room to add padding in bin_to_b64\n");
+ return -1;
+ }
+ default:
+ CRIT("switch default that should not be possible");
+ }
+}
+
+int b64_to_bin(const char * in, int size, uint8_t * out, int max_len) {
+ if (in == NULL) {
+ DEBUG("ERROR: NULL POINTER AS OUTPUT OR INPUT IN B64_TO_BIN\n");
+ return -1;
+ }
+ if ((size%4 == 0) && (size >= 4)) { /* potentially padded Base64 */
+ if (in[size-2] == code_pad) { /* 2 padding char to ignore */
+ return b64_to_bin_nopad(in, size-2, out, max_len);
+ } else if (in[size-1] == code_pad) { /* 1 padding char to ignore */
+ return b64_to_bin_nopad(in, size-1, out, max_len);
+ } else { /* no padding to ignore */
+ return b64_to_bin_nopad(in, size, out, max_len);
+ }
+ } else { /* treat as unpadded Base64 */
+ return b64_to_bin_nopad(in, size, out, max_len);
+ }
+}
+
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/util_tx_test/src/util_tx_test.c b/util_tx_test/src/util_tx_test.c
new file mode 100644
index 0000000..00b0c03
--- /dev/null
+++ b/util_tx_test/src/util_tx_test.c
@@ -0,0 +1,502 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Ask a gateway to emit packets using GW <-> server protocol
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+/* fix an issue between POSIX and C99 */
+#if __STDC_VERSION__ >= 199901L
+ #define _XOPEN_SOURCE 600
+#else
+ #define _XOPEN_SOURCE 500
+#endif
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+#include <stdio.h> /* printf fprintf sprintf fopen fputs */
+#include <unistd.h> /* getopt access usleep */
+
+#include <string.h> /* memset */
+#include <signal.h> /* sigaction */
+#include <stdlib.h> /* exit codes */
+#include <errno.h> /* error messages */
+
+#include <sys/socket.h> /* socket specific definitions */
+#include <netinet/in.h> /* INET constants and stuff */
+#include <arpa/inet.h> /* IP address conversion stuff */
+#include <netdb.h> /* gai_strerror */
+
+#include "base64.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#define PROTOCOL_VERSION 2
+
+#define PKT_PUSH_DATA 0
+#define PKT_PUSH_ACK 1
+#define PKT_PULL_DATA 2
+#define PKT_PULL_RESP 3
+#define PKT_PULL_ACK 4
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */
+
+/* signal handling variables */
+struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */
+static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */
+static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
+
+static void sig_handler(int sigio);
+
+void usage (void);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+static void sig_handler(int sigio) {
+ if (sigio == SIGQUIT) {
+ quit_sig = 1;;
+ } else if ((sigio == SIGINT) || (sigio == SIGTERM)) {
+ exit_sig = 1;
+ }
+}
+
+/* describe command line options */
+void usage(void) {
+ MSG("Usage: util_tx_test {options}\n");
+ MSG("Available options:\n");
+ MSG(" -h print this help\n");
+ MSG(" -n <int or service> port number for gateway link\n");
+ MSG(" -f <float> target frequency in MHz\n");
+ MSG(" -m <str> Modulation type ['LORA, 'FSK']\n");
+ MSG(" -s <int> Spreading Factor [7:12]\n");
+ MSG(" -b <int> Modulation bandwidth in kHz [125,250,500]\n");
+ MSG(" -d <uint> FSK frequency deviation in kHz [1:250]\n");
+ MSG(" -r <float> FSK bitrate in kbps [0.5:250]\n");
+ MSG(" -p <int> RF power (dBm)\n");
+ MSG(" -z <uint> Payload size in bytes [9:255]\n");
+ MSG(" -t <int> pause between packets (ms)\n");
+ MSG(" -x <int> numbers of times the sequence is repeated\n");
+ MSG(" -v <uint> test ID, inserted in payload for PER test [0:255]\n");
+ MSG(" -i send packet using inverted modulation polarity \n");
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- MAIN FUNCTION -------------------------------------------------------- */
+
+int main(int argc, char **argv)
+{
+ int i, j, x;
+ unsigned int xu;
+ char arg_s[64];
+
+ /* application parameters */
+ char mod[64] = "LORA"; /* LoRa modulation by default */
+ float f_target = 866.0; /* target frequency */
+ int sf = 10; /* SF10 by default */
+ int bw = 125; /* 125kHz bandwidth by default */
+ int pow = 14; /* 14 dBm by default */
+ int delay = 1000; /* 1 second between packets by default */
+ int repeat = 1; /* sweep only once by default */
+ bool invert = false;
+ float br_kbps = 50; /* 50 kbps by default */
+ uint8_t fdev_khz = 25; /* 25 khz by default */
+
+ /* packet payload variables */
+ int payload_size = 9; /* minimum size for PER frame */
+ uint8_t payload_bin[255];
+ char payload_b64[341];
+ int payload_index;
+
+ /* PER payload variables */
+ uint8_t id = 0;
+
+ /* server socket creation */
+ int sock; /* socket file descriptor */
+ struct addrinfo hints;
+ struct addrinfo *result; /* store result of getaddrinfo */
+ struct addrinfo *q; /* pointer to move into *result data */
+ char serv_port[8] = "1680";
+ char host_name[64];
+ char port_name[64];
+
+ /* variables for receiving and sending packets */
+ struct sockaddr_storage dist_addr;
+ socklen_t addr_len = sizeof dist_addr;
+ uint8_t databuf[500];
+ int buff_index;
+ int byte_nb;
+
+ /* variables for gateway identification */
+ uint32_t raw_mac_h; /* Most Significant Nibble, network order */
+ uint32_t raw_mac_l; /* Least Significant Nibble, network order */
+ uint64_t gw_mac; /* MAC address of the client (gateway) */
+
+ /* prepare hints to open network sockets */
+ memset(&hints, 0, sizeof hints);
+ hints.ai_family = AF_UNSPEC; /* should handle IP v4 or v6 automatically */
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_flags = AI_PASSIVE; /* will assign local IP automatically */
+
+ /* parse command line options */
+ while ((i = getopt (argc, argv, "hn:f:m:s:b:d:r:p:z:t:x:v:i")) != -1) {
+ switch (i) {
+ case 'h':
+ usage();
+ return EXIT_FAILURE;
+ break;
+
+ case 'n': /* -n <int or service> port number for gateway link */
+ strncpy(serv_port, optarg, sizeof serv_port);
+ break;
+
+ case 'f': /* -f <float> target frequency in MHz */
+ i = sscanf(optarg, "%f", &f_target);
+ if ((i != 1) || (f_target < 30.0) || (f_target > 3000.0)) {
+ MSG("ERROR: invalid TX frequency\n");
+ return EXIT_FAILURE;
+ }
+ break;
+
+ case 'm': /* -m <str> Modulation type */
+ i = sscanf(optarg, "%s", arg_s);
+ if ((i != 1) || ((strcmp(arg_s, "LORA") != 0) && (strcmp(arg_s, "FSK")))) {
+ MSG("ERROR: invalid modulation type\n");
+ usage();
+ return EXIT_FAILURE;
+ } else {
+ sprintf(mod, "%s", arg_s);
+ }
+ break;
+
+ case 's': /* -s <int> Spreading Factor */
+ i = sscanf(optarg, "%i", &sf);
+ if ((i != 1) || (sf < 7) || (sf > 12)) {
+ MSG("ERROR: invalid spreading factor\n");
+ return EXIT_FAILURE;
+ }
+ break;
+
+ case 'b': /* -b <int> Modulation bandwidth in kHz */
+ i = sscanf(optarg, "%i", &bw);
+ if ((i != 1) || ((bw != 125) && (bw != 250) && (bw != 500))) {
+ MSG("ERROR: invalid LORA bandwidth\n");
+ return EXIT_FAILURE;
+ }
+ break;
+
+ case 'd': /* -d <uint> FSK frequency deviation */
+ i = sscanf(optarg, "%u", &xu);
+ if ((i != 1) || (xu < 1) || (xu > 250)) {
+ MSG("ERROR: invalid FSK frequency deviation\n");
+ usage();
+ return EXIT_FAILURE;
+ } else {
+ fdev_khz = (uint8_t)xu;
+ }
+ break;
+
+ case 'r': /* -q <float> FSK bitrate */
+ i = sscanf(optarg, "%f", &br_kbps);
+ if ((i != 1) || (br_kbps < 0.5) || (br_kbps > 250)) {
+ MSG("ERROR: invalid FSK bitrate\n");
+ usage();
+ return EXIT_FAILURE;
+ }
+ break;
+
+ case 'p': /* -p <int> RF power */
+ i = sscanf(optarg, "%i", &pow);
+ if ((i != 1) || (pow < 0) || (pow > 30)) {
+ MSG("ERROR: invalid RF power\n");
+ return EXIT_FAILURE;
+ }
+ break;
+
+ case 'z': /* -z <uint> Payload size */
+ i = sscanf(optarg, "%i", &payload_size);
+ if ((i != 1) || (payload_size < 9) || (payload_size > 255)) {
+ MSG("ERROR: invalid payload size\n");
+ usage();
+ return EXIT_FAILURE;
+ }
+ break;
+
+ case 't': /* -t <int> pause between RF packets (ms) */
+ i = sscanf(optarg, "%i", &delay);
+ if ((i != 1) || (delay < 0)) {
+ MSG("ERROR: invalid time between RF packets\n");
+ return EXIT_FAILURE;
+ }
+ break;
+
+ case 'x': /* -x <int> numbers of times the sequence is repeated */
+ i = sscanf(optarg, "%u", &repeat);
+ if ((i != 1) || (repeat < 1)) {
+ MSG("ERROR: invalid number of repeats\n");
+ return EXIT_FAILURE;
+ }
+ break;
+
+ case 'v': /* -v <uint> test Id */
+ i = sscanf(optarg, "%u", &xu);
+ if ((i != 1) || ((xu < 1) && (xu > 255))) {
+ MSG("ERROR: invalid Id\n");
+ return EXIT_FAILURE;
+ } else {
+ id = (uint8_t)xu;
+ }
+ break;
+
+ case 'i': /* -i send packet using inverted modulation polarity */
+ invert = true;
+ break;
+
+ default:
+ MSG("ERROR: argument parsing failure, use -h option for help\n");
+ usage();
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* compose local address (auto-complete a structure for socket) */
+ i = getaddrinfo(NULL, serv_port, &hints, &result);
+ if (i != 0) {
+ MSG("ERROR: getaddrinfo returned %s\n", gai_strerror(i));
+ exit(EXIT_FAILURE);
+ }
+
+ /* try to open socket and bind to it */
+ for (q=result; q!=NULL; q=q->ai_next) {
+ sock = socket(q->ai_family, q->ai_socktype,q->ai_protocol);
+ if (sock == -1) {
+ continue; /* socket failed, try next field */
+ } else {
+ i = bind(sock, q->ai_addr, q->ai_addrlen);
+ if (i == -1) {
+ shutdown(sock, SHUT_RDWR);
+ continue; /* bind failed, try next field */
+ } else {
+ break; /* success, get out of loop */
+ }
+ }
+ }
+ if (q == NULL) {
+ MSG("ERROR: failed to open socket or to bind to it\n");
+ exit(EXIT_FAILURE);
+ }
+ freeaddrinfo(result);
+
+ /* configure signal handling */
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigact.sa_handler = sig_handler;
+ sigaction(SIGQUIT, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+ sigaction(SIGTERM, &sigact, NULL);
+
+ /* display setup summary */
+ if (strcmp(mod, "FSK") == 0) {
+ MSG("INFO: %i FSK pkts @%f MHz (FDev %u kHz, Bitrate %.2f kbps, %uB payload) %i dBm, %i ms between each\n", repeat, f_target, fdev_khz, br_kbps, payload_size, pow, delay);
+ } else {
+ MSG("INFO: %i LoRa pkts @%f MHz (BW %u kHz, SF%i, %uB payload) %i dBm, %i ms between each\n", repeat, f_target, bw, sf, payload_size, pow, delay);
+ }
+
+ /* wait to receive a PULL_DATA request packet */
+ MSG("INFO: waiting to receive a PULL_DATA request on port %s\n", serv_port);
+ while (1) {
+ byte_nb = recvfrom(sock, databuf, sizeof databuf, 0, (struct sockaddr *)&dist_addr, &addr_len);
+ if ((quit_sig == 1) || (exit_sig == 1)) {
+ exit(EXIT_SUCCESS);
+ } else if (byte_nb < 0) {
+ MSG("WARNING: recvfrom returned an error\n");
+ } else if ((byte_nb < 12) || (databuf[0] != PROTOCOL_VERSION) || (databuf[3] != PKT_PULL_DATA)) {
+ MSG("INFO: packet received, not PULL_DATA request\n");
+ } else {
+ break; /* success! */
+ }
+ }
+
+ /* retrieve gateway MAC from the request */
+ raw_mac_h = *((uint32_t *)(databuf+4));
+ raw_mac_l = *((uint32_t *)(databuf+8));
+ gw_mac = ((uint64_t)ntohl(raw_mac_h) << 32) + (uint64_t)ntohl(raw_mac_l);
+
+ /* display info about the sender */
+ i = getnameinfo((struct sockaddr *)&dist_addr, addr_len, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST);
+ if (i == -1) {
+ MSG("ERROR: getnameinfo returned %s \n", gai_strerror(i));
+ exit(EXIT_FAILURE);
+ }
+ MSG("INFO: PULL_DATA request received from gateway 0x%08X%08X (host %s, port %s)\n", (uint32_t)(gw_mac >> 32), (uint32_t)(gw_mac & 0xFFFFFFFF), host_name, port_name);
+
+ /* PKT_PULL_RESP datagrams header */
+ databuf[0] = PROTOCOL_VERSION;
+ databuf[1] = 0; /* no token */
+ databuf[2] = 0; /* no token */
+ databuf[3] = PKT_PULL_RESP;
+ buff_index = 4;
+
+ /* start of JSON structure */
+ memcpy((void *)(databuf + buff_index), (void *)"{\"txpk\":{\"imme\":true", 20);
+ buff_index += 20;
+
+ /* TX frequency */
+ i = snprintf((char *)(databuf + buff_index), 20, ",\"freq\":%.6f", f_target);
+ if ((i>=0) && (i < 20)) {
+ buff_index += i;
+ } else {
+ MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+
+ /* RF channel */
+ memcpy((void *)(databuf + buff_index), (void *)",\"rfch\":0", 9);
+ buff_index += 9;
+
+ /* TX power */
+ i = snprintf((char *)(databuf + buff_index), 12, ",\"powe\":%i", pow);
+ if ((i>=0) && (i < 12)) {
+ buff_index += i;
+ } else {
+ MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+
+ /* modulation type and parameters */
+ if (strcmp(mod, "FSK") == 0) {
+ i = snprintf((char *)(databuf + buff_index), 50, ",\"modu\":\"FSK\",\"datr\":%u,\"fdev\":%u", (unsigned int)(br_kbps*1e3), (unsigned int)(fdev_khz*1e3));
+ if ((i>=0) && (i < 50)) {
+ buff_index += i;
+ } else {
+ MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ i = snprintf((char *)(databuf + buff_index), 50, ",\"modu\":\"LORA\",\"datr\":\"SF%iBW%i\",\"codr\":\"4/6\"", sf, bw);
+ if ((i>=0) && (i < 50)) {
+ buff_index += i;
+ } else {
+ MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* signal polarity */
+ if (invert) {
+ memcpy((void *)(databuf + buff_index), (void *)",\"ipol\":true", 12);
+ buff_index += 12;
+ } else {
+ memcpy((void *)(databuf + buff_index), (void *)",\"ipol\":false", 13);
+ buff_index += 13;
+ }
+
+ /* Preamble size */
+ if (strcmp(mod, "LORA") == 0) {
+ memcpy((void *)(databuf + buff_index), (void *)",\"prea\":8", 9);
+ buff_index += 9;
+ }
+
+ /* payload size */
+ i = snprintf((char *)(databuf + buff_index), 12, ",\"size\":%i", payload_size);
+ if ((i>=0) && (i < 12)) {
+ buff_index += i;
+ } else {
+ MSG("ERROR: snprintf failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+
+ /* payload JSON object */
+ memcpy((void *)(databuf + buff_index), (void *)",\"data\":\"", 9);
+ buff_index += 9;
+ payload_index = buff_index; /* keep the value where the payload content start */
+
+ /* payload place-holder & end of JSON structure */
+ x = bin_to_b64(payload_bin, payload_size, payload_b64, sizeof payload_b64); /* dummy conversion to get exact size */
+ if (x >= 0) {
+ buff_index += x;
+ } else {
+ MSG("ERROR: bin_to_b64 failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Close JSON structure */
+ memcpy((void *)(databuf + buff_index), (void *)"\"}}", 3);
+ buff_index += 3; /* ends up being the total length of payload */
+
+ /* main loop */
+ for (i = 0; i < repeat; ++i) {
+ /* fill payload */
+ payload_bin[0] = id;
+ payload_bin[1] = (uint8_t)(i >> 24);
+ payload_bin[2] = (uint8_t)(i >> 16);
+ payload_bin[3] = (uint8_t)(i >> 8);
+ payload_bin[4] = (uint8_t)(i);
+ payload_bin[5] = 'P';
+ payload_bin[6] = 'E';
+ payload_bin[7] = 'R';
+ payload_bin[8] = (uint8_t)(payload_bin[0] + payload_bin[1] + payload_bin[2] + payload_bin[3] + payload_bin[4] + payload_bin[5] + payload_bin[6] + payload_bin[7]);
+ for (j = 0; j < (payload_size - 9); j++) {
+ payload_bin[9+j] = j;
+ }
+
+#if 0
+ for (j = 0; j < payload_size; j++ ) {
+ printf("0x%02X ", payload_bin[j]);
+ }
+ printf("\n");
+#endif
+
+ /* encode the payload in Base64 */
+ x = bin_to_b64(payload_bin, payload_size, payload_b64, sizeof payload_b64);
+ if (x >= 0) {
+ memcpy((void *)(databuf + payload_index), (void *)payload_b64, x);
+ } else {
+ MSG("ERROR: bin_to_b64 failed line %u\n", (__LINE__ - 4));
+ exit(EXIT_FAILURE);
+ }
+
+ /* send packet to the gateway */
+ byte_nb = sendto(sock, (void *)databuf, buff_index, 0, (struct sockaddr *)&dist_addr, addr_len);
+ if (byte_nb == -1) {
+ MSG("WARNING: sendto returned an error %s\n", strerror(errno));
+ } else {
+ MSG("INFO: packet #%i sent successfully\n", i);
+ }
+
+ /* wait inter-packet delay */
+ usleep(delay * 1000);
+
+ /* exit loop on user signals */
+ if ((quit_sig == 1) || (exit_sig == 1)) {
+ break;
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
+
+/* --- EOF ------------------------------------------------------------------ */