浏览代码

first ver

forest-rain 4 年之前
当前提交
257a470054
共有 20 个文件被更改,包括 6640 次插入0 次删除
  1. 7 0
      ReadMe.md
  2. 12 0
      SConscript
  3. 二进制
      docs/images/lpf-lns-ttn-eu868.gif
  4. 二进制
      docs/images/lpf-shell-operation.gif
  5. 二进制
      docs/images/lpf-unicore-udp-cn470s.gif
  6. 228 0
      global_conf.json
  7. 62 0
      inc/base64.h
  8. 186 0
      inc/jitqueue.h
  9. 57 0
      inc/lora_pkt_fwd.h
  10. 100 0
      inc/lora_pkt_fwd_dbg.h
  11. 222 0
      inc/parson.h
  12. 7 0
      local_conf.json
  13. 328 0
      readme_Semtech.md
  14. 23 0
      src/SConscript
  15. 311 0
      src/base64.c
  16. 495 0
      src/jitqueue.c
  17. 242 0
      src/lora_pkt_fwd_conf.c
  18. 2353 0
      src/lora_pkt_fwd_gwmp.c
  19. 242 0
      src/lora_pkt_fwd_shell.c
  20. 1765 0
      src/parson.c

+ 7 - 0
ReadMe.md

@@ -0,0 +1,7 @@
+# lora-pkt-fwd软件包 简介
+lora-pkt-fwd软件包基于RTOS(RT-Thread)与LoRa网关模块(eg:SX1302模块..)实现了Semtech定义的GWMP协议,进而实现LoRaWAN数据包通过UDP转发到LoRaWAN服务器。lora-pkt-fwd软件包建立在UDP基础之上,可以用于构建符合Semtech定义的第一代LoRaWAN网关。
+
+lora-pkt-fwd软件包使用lora-gw-driver软件包提供的lora数据包的发送与接收服务,当前主要支持SX1302芯片。
+
+更多详细信息请查看[lora-pkt-fwd/docs](https://github.com/Forest-Rain/lora-pkt-fwd/tree/master/docs)
+

+ 12 - 0
SConscript

@@ -0,0 +1,12 @@
+import os
+from building import *
+
+objs = []
+cwd  = GetCurrentDir()
+list = os.listdir(cwd)
+
+for item in list:
+    if os.path.isfile(os.path.join(cwd, item, 'SConscript')):
+        objs = objs + SConscript(os.path.join(item, 'SConscript'))
+
+Return('objs')

二进制
docs/images/lpf-lns-ttn-eu868.gif


二进制
docs/images/lpf-shell-operation.gif


二进制
docs/images/lpf-unicore-udp-cn470s.gif


+ 228 - 0
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
+    }
+}
+

+ 62 - 0
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 ------------------------------------------------------------------ */

+ 186 - 0
inc/jitqueue.h

@@ -0,0 +1,186 @@
+/*
+ / _____)             _              | |
+( (____  _____ ____ _| |_ _____  ____| |__
+ \____ \| ___ |    (_   _) ___ |/ ___)  _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+  (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 "loragw_hal.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC CONSTANTS ----------------------------------------------------- */
+
+#define JIT_QUEUE_MAX           32  /* Maximum number of packets to be stored in JiT queue */
+#define JIT_NUM_BEACON_IN_QUEUE 3   /* Number of beacons to be loaded in JiT queue at any time */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC TYPES --------------------------------------------------------- */
+
+enum jit_pkt_type_e {
+    JIT_PKT_TYPE_DOWNLINK_CLASS_A,
+    JIT_PKT_TYPE_DOWNLINK_CLASS_B,
+    JIT_PKT_TYPE_DOWNLINK_CLASS_C,
+    JIT_PKT_TYPE_BEACON
+};
+
+enum jit_error_e {
+    JIT_ERROR_OK,           /* Packet ok to be sent */
+    JIT_ERROR_TOO_LATE,     /* Too late to send this packet */
+    JIT_ERROR_TOO_EARLY,    /* Too early to queue this packet */
+    JIT_ERROR_FULL,         /* Downlink queue is full */
+    JIT_ERROR_EMPTY,        /* Downlink queue is empty */
+    JIT_ERROR_COLLISION_PACKET, /* A packet is already enqueued for this timeframe */
+    JIT_ERROR_COLLISION_BEACON, /* A beacon is planned for this timeframe */
+    JIT_ERROR_TX_FREQ,      /* The required frequency for downlink is not supported */
+    JIT_ERROR_TX_POWER,     /* The required power for downlink is not supported */
+    JIT_ERROR_GPS_UNLOCKED, /* GPS timestamp could not be used as GPS is unlocked */
+    JIT_ERROR_INVALID       /* Packet is invalid */
+};
+
+struct jit_node_s {
+    /* API fields */
+    struct lgw_pkt_tx_s pkt;        /* TX packet */
+    enum jit_pkt_type_e pkt_type;   /* Packet type: Downlink, Beacon... */
+
+    /* Internal fields */
+    uint32_t pre_delay;             /* Amount of time before packet timestamp to be reserved */
+    uint32_t post_delay;            /* Amount of time after packet timestamp to be reserved (time on air) */
+};
+
+struct jit_queue_s {
+    uint8_t num_pkt;                /* Total number of packets in the queue (downlinks, beacons...) */
+    uint8_t num_beacon;             /* Number of beacons in the queue */
+    struct jit_node_s nodes[JIT_QUEUE_MAX]; /* Nodes/packets array in the queue */
+};
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+/**
+@brief Check if a JiT queue is full.
+
+@param queue[in] Just in Time queue to be checked.
+@return true if queue is full, false otherwise.
+*/
+bool jit_queue_is_full(struct jit_queue_s *queue);
+
+/**
+@brief Check if a JiT queue is empty.
+
+@param queue[in] Just in Time queue to be checked.
+@return true if queue is empty, false otherwise.
+*/
+bool jit_queue_is_empty(struct jit_queue_s *queue);
+
+/**
+@brief Initialize a Just in Time queue.
+
+@param queue[in] Just in Time queue to be initialized. Memory should have been allocated already.
+
+This function is used to reset every elements in the allocated queue.
+*/
+void jit_queue_init(struct jit_queue_s *queue);
+
+#if ( defined LORAGW_LIB_USING_LORA_CHIP_SX1301 ) 
+/**
+@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);
+#else
+/**
+@brief Add a packet in a Just-in-Time queue
+
+@param queue[in/out] Just in Time queue in which the packet should be inserted
+@param time_us[in] Current concentrator time
+@param packet[in] Packet to be queued in JiT queue
+@param pkt_type[in] Type of packet to be queued: Downlink, Beacon
+@return success if the function was able to queue the packet
+
+This function is typically used when a packet is received from server for downlink.
+It will check if packet can be queued, with several criterias. Once the packet is queued, it has to be
+sent over the air. So all checks should happen before the packet being actually in the queue.
+*/
+enum jit_error_e jit_enqueue(struct jit_queue_s *queue, uint32_t time_us, struct lgw_pkt_tx_s *packet, enum jit_pkt_type_e pkt_type);
+
+#endif
+/**
+@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);
+
+#if ( defined LORAGW_LIB_USING_LORA_CHIP_SX1301 ) 
+/**
+@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);
+#else
+/**
+@brief Check if there is a packet soon to be sent from the JiT queue.
+
+@param queue[in] Just in Time queue to parse for peeking a packet
+@param time_us[in] Current concentrator time
+@param pkt_idx[out] Packet index which is soon to be dequeued.
+@return success if the function was able to parse the queue. pkt_idx is set to -1 if no packet found.
+
+This function is typically used to check in JiT queue if there is a packet soon to be sent.
+It search the packet with the highest priority in queue, and check if its timestamp is near
+enough the current concentrator time.
+*/
+enum jit_error_e jit_peek(struct jit_queue_s *queue, uint32_t time_us, int *pkt_idx);
+#endif
+/**
+@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 ------------------------------------------------------------------ */

+ 57 - 0
inc/lora_pkt_fwd.h

@@ -0,0 +1,57 @@
+#ifndef _LORA_PKT_FWD_H
+#define _LORA_PKT_FWD_H
+
+#include "loragw_usr.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+// LoRa Packet Forward(LPF) northbound interface
+typedef enum
+{
+    LPF_NORTHBOUND_IF_OVER_WIFI = 0,        // over wifi
+    LPF_NORTHBOUND_IF_OVER_ETHERNET,        // over ethernet
+    LPF_NORTHBOUND_IF_NUMS,                 // northbound if numbers
+}lpf_northbound_interface_t;
+
+// LoRa Packet Forward(LPF) supported network server
+typedef enum
+{
+    LPF_SUPPORTED_NETWORK_SERVER_LIERDA_UNICORE_GWMP = 0,          // lierda unicore
+    LPF_SUPPORTED_NETWORK_SERVER_TTN_GWMP,                         // ttn 
+    LPF_SUPPORTED_NETWORK_SERVER_TENCENT_IOT_EXPLORER_GWMP,        // tencent iot-explorer
+    LPF_SUPPORTED_NETWORK_SERVER_NUMS,                             // server numbers
+}lpf_supported_server_t;
+
+#pragma pack(push,1) // lign to byte
+/**
+@struct lpf_conf_srv_s
+@brief Configuration structure for lora pkt fwd server conf
+*/
+struct lpf_conf_srv_s
+{
+    uint16_t magic;
+    lpf_northbound_interface_t northboud_if;
+
+    char server_addr[64];
+    char server_port_up[8];
+    char server_port_down[8];
+    
+    char json_conf_file[64];        /* lgw json conf file */
+};
+#define LPF_CONF_SRV_SIZE   sizeof(struct lpf_conf_srv_s)
+#pragma pack(pop)  
+
+extern struct lpf_conf_srv_s lpf_conf_srv;
+extern const struct lpf_conf_srv_s lpf_conf_srv_default[];
+extern struct lgw_conf_usr_s lpf_conf_lgw;
+extern const struct lgw_conf_usr_s lpf_conf_lgw_default;
+
+extern int lpf_init(void);
+extern void lpf_nv_params_init(void);
+extern int lpf_conf_lgw_nv_save(void *buff, int len);
+extern int lpf_conf_srv_nv_save(void *buff, int len);
+
+#endif
+
+/* --- EOF ------------------------------------------------------------------ */

+ 100 - 0
inc/lora_pkt_fwd_dbg.h

@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2006-2018, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef __LORA_PKT_FWD_DEBUG_H__
+#define __LORA_PKT_FWD_DEBUG_H__
+
+#include "rtconfig.h"
+
+#ifdef RT_USING_ULOG
+#include <rtdbg.h>
+#include <ulog.h> 
+#endif
+
+/* Using this macro to control all LoRa Packet Forward debug features. */
+#ifdef LORA_PKT_FWD_DEBUG
+
+/* Turn on some of these (set to non-zero) to debug LoRa Packet Forward */
+
+#ifndef LPF_DBG_GWMP
+#define LPF_DBG_GWMP           0
+#endif
+
+#ifndef LPF_DBG_SHELL
+#define LPF_DBG_SHELL          0
+#endif
+
+#ifndef LPF_DBG_PARAMS_CONF
+#define LPF_DBG_PARAMS_CONF    0
+#endif
+
+#if defined RT_USING_ULOG
+
+#define LPF_DEBUG_LOG(type, level, ...)                                       \
+do                                                                            \
+{                                                                             \
+    if (type)                                                                 \
+    {                                                                         \
+        ulog_output(level, LOG_TAG, RT_TRUE, __VA_ARGS__);                    \
+    }                                                                         \
+}                                                                             \
+while (0)
+
+#define LPF_DEBUG_LOG_RAW(type, ...)                                          \
+do                                                                            \
+{                                                                             \
+    if (type)                                                                 \
+    {                                                                         \
+        ulog_raw(__VA_ARGS__);                                                \
+    }                                                                         \
+}                                                                             \
+while (0)
+
+#define LPF_DEBUG_LOG_HEXDUMP(type, buf, size)                \
+do                                                                            \
+{                                                                             \
+    if (type)                                                                 \
+    {                                                                         \
+        ulog_hexdump(LOG_TAG, 16, buf, size);                                 \
+    }                                                                         \
+}                                                                             \
+while (0)
+
+#else
+
+#define LPF_DEBUG_LOG(type, level, ...)                                \
+do                                                                            \
+{                                                                             \
+    if (type)                                                                 \
+    {                                                                         \
+        rt_kprintf(__VA_ARGS__);                                              \
+        rt_kprintf("\r\n");                                                   \
+    }                                                                         \
+}                                                                             \
+while (0)
+
+#define LPF_DEBUG_LOG_RAW(type, ...)                                   \
+do                                                                            \
+{                                                                             \
+    if (type)                                                                 \
+    {                                                                         \
+        rt_kprintf(__VA_ARGS__);                                              \
+        rt_kprintf("\r\n");                                                   \
+    }                                                                         \
+}                                                                             \
+while (0)
+#define LPF_DEBUG_LOG_HEXDUMP(type, buf, size)
+
+#endif /* RT_USING_ULOG */
+
+
+#else /* LPF_DEBUG */
+
+#define LPF_DEBUG_LOG(type, level, ...)
+#define LPF_DEBUG_RAW(type, ...)
+#define LPF_DEBUG_LOG_HEXDUMP(type, buf, size)
+#endif /* LORA_PACKET_SNIFFER_DEBUG */
+
+#endif /* __LORA_PKT_FWD_DEBUG_H__ */

+ 222 - 0
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

+ 7 - 0
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) */
+    }
+}

+ 328 - 0
readme_Semtech.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*

+ 23 - 0
src/SConscript

@@ -0,0 +1,23 @@
+from building import *
+
+src   = []
+cwd   = GetCurrentDir()
+include_path = [cwd+'/../inc']
+
+# sx130x
+if GetDepend('LORA_PKT_FWD_USING_MULTI_CHANNEL_MODE'):
+    src = Split('''
+    base64.c
+    jitqueue.c
+    parson.c
+    lora_pkt_fwd_conf.c
+    lora_pkt_fwd_shell.c
+    ''')
+
+if GetDepend('LORA_PKT_FWD_USING_PROTOCOL_SEMTECH_GWMP'):
+    src += ['lora_pkt_fwd_gwmp.c']
+
+
+group = DefineGroup('lora_pkt_fwd', src, depend = [''], CPPPATH = include_path)
+
+Return('group')

+ 311 - 0
src/base64.c

@@ -0,0 +1,311 @@
+/*
+ / _____)             _              | |
+( (____  _____ ____ _| |_ _____  ____| |__
+ \____ \| ___ |    (_   _) ___ |/ ___)  _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+  (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 exit(x) 
+
+#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 MSG_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 {
+        MSG_DEBUG("ERROR: %i IS OUT OF RANGE 0-63 FOR BASE64 ENCODING\n", x);
+        exit(EXIT_FAILURE);
+    } //TODO: improve error management
+    return 0;
+}
+
+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 {
+        MSG_DEBUG("ERROR: %c (0x%x) IS INVALID CHARACTER FOR BASE64 DECODING\n", x, x);
+        exit(EXIT_FAILURE);
+    } //TODO: improve error management
+    return 0;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- 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)) {
+        MSG_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 */
+        MSG_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)) {
+        MSG_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 */
+            MSG_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) {
+        MSG_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) {
+            MSG_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) {
+            MSG_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:
+            MSG_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 {
+                MSG_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 {
+                MSG_DEBUG("ERROR: not enough room to add padding in bin_to_b64\n");
+                return -1;
+            }
+        default:
+            CRIT("switch default that should not be possible");
+            return -1;
+    }
+}
+
+int b64_to_bin(const char * in, int size, uint8_t * out, int max_len) {
+    if (in == NULL) {
+        MSG_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 ------------------------------------------------------------------ */

+ 495 - 0
src/jitqueue.c

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

+ 242 - 0
src/lora_pkt_fwd_conf.c

@@ -0,0 +1,242 @@
+/*
+ / _____)             _              | |
+( (____  _____ ____ _| |_ _____  ____| |__
+ \____ \| ___ |    (_   _) ___ |/ ___)  _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+  (C)2013 Semtech-Cycleo
+
+Description:
+    Minimum test program for the loragw_hal 'library'
+
+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>         /* LOG_D */
+#include <string.h>        /* memset */
+
+#include <rthw.h>
+#include <rtdevice.h>
+#include "board.h"
+#include <rtthread.h>
+#include <fal.h>
+#include <easyflash.h>
+
+#include "loragw_hal.h"
+#include "loragw_usr.h"
+#include "lora_pkt_fwd.h"
+
+#define DBG_LVL     DBG_INFO
+#define LOG_TAG    "lfp.conf"
+#include "lora_pkt_fwd_dbg.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#define DEFAULT_RSSI_OFFSET -176
+#define DEFAULT_NOTCH_FREQ  129000U
+
+struct lgw_conf_usr_s lpf_conf_lgw;
+/* --- PRIVATE VARIABLES ---------------------------------------------------- */
+const struct lgw_conf_usr_s lpf_conf_lgw_default =  
+{
+    .magic = 0xAA55,
+    
+    .lorawan_public = true,/* true for lorawan */
+
+    .rxrf =
+    {
+        {
+            .enable  = true,
+            .invert_pol = false, /* false for lorawan */
+            .freq_hz = LGD_RF_CHIAN0_CENTER_FREQ,
+        },
+        
+        {
+            .enable  = true,
+            .invert_pol = false, /* false for lorawan */
+            .freq_hz = LGD_RF_CHIAN1_CENTER_FREQ,
+        },
+    },
+    
+    .rxif = 
+    {    
+        .channel_if_enable =  { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0 },
+        .channel_if_rfchain = { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0 },
+        .channel_if_freq =
+        { 
+            -300000,
+            -100000,
+            100000,
+            300000,
+            -300000,
+            -100000,
+            100000,
+            300000,
+        },
+    },
+    
+    .txrf =
+    {
+        .freq_hz = 475300000,
+        .rf_power = 20,
+        .datarate = 7,
+
+         /* lora */
+        .modulation = MOD_LORA,
+        .invert_pol = false, /* true for lorawan downlink */
+        .preamble = 8,
+        .no_header = false, 
+        .no_crc = false, /* true for lorawan downlink */
+        .bandwidth = BW_125KHZ,
+        .coderate = CR_LORA_4_5, 
+        // FSK
+        .f_dev = 25,//25kHz
+
+        .tx_mode = IMMEDIATE,//TIMESTAMPED,
+        
+#ifdef LORA_GW_DRIVER_USING_SX1302_LSD4WN_2K830NE0
+        .external_pa = true,
+#endif            
+    },
+};
+
+#ifdef PKG_USING_EASYFLASH  
+static int lpf_get_lgw_nv_params(void *buff, int len);
+static int lpf_get_srv_nv_params(void *buff, int len);
+#endif
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+#if 0
+static void sig_handler(int sigio) {
+    if (sigio == SIGQUIT) {
+        quit_sig = 1;
+    } else if ((sigio == SIGINT) || (sigio == SIGTERM)) {
+        exit_sig = 1;
+    }
+}
+#endif
+
+/* -------------------------------------------------------------------------- */
+/* --- MAIN FUNCTION -------------------------------------------------------- */
+void lpf_nv_params_init(void)
+{
+#ifdef PKG_USING_EASYFLASH
+    fal_init();
+    easyflash_init();
+#endif
+
+     struct lgw_conf_usr_s lpf_conf_lgw_temp;
+    /* get lpf chip Parameters */
+     if ( lpf_get_lgw_nv_params(&lpf_conf_lgw_temp,LGW_CONF_USR_SIZE) == LGW_CONF_USR_SIZE && ( lpf_conf_lgw_temp.magic == 0xAA55 ))
+     {
+         lpf_conf_lgw = lpf_conf_lgw_temp;
+         LPF_DEBUG_LOG(LPF_DBG_PARAMS_CONF, LOG_LVL_INFO,"Get LPF GW Chip Parameters Successed!\n");
+     }
+     else
+     {
+         LPF_DEBUG_LOG(LPF_DBG_PARAMS_CONF, LOG_LVL_INFO,"Get LPF GW Chip Parameters Failed,Use default Parameters!\n");
+         lpf_conf_lgw = lpf_conf_lgw_default;
+     }
+
+    struct lpf_conf_srv_s lpf_conf_srv_temp;
+     /* get lpf srv Parameters */
+    if (lpf_get_srv_nv_params(&lpf_conf_srv_temp,LPF_CONF_SRV_SIZE) != LPF_CONF_SRV_SIZE )
+    {
+        LPF_DEBUG_LOG(LPF_DBG_PARAMS_CONF, LOG_LVL_INFO,"Get LPF Server Parameters Failed,Use default Parameters!\n");
+        lpf_conf_srv = lpf_conf_srv_default[LPF_SUPPORTED_NETWORK_SERVER_LIERDA_UNICORE_GWMP];
+    }
+    else
+    {
+        lpf_conf_srv = lpf_conf_srv_temp;
+        LPF_DEBUG_LOG(LPF_DBG_PARAMS_CONF, LOG_LVL_INFO,"Get LPF Server Parameters Successed!\n");
+    }
+}
+
+int lpf_conf_lgw_nv_save(void *buff, int len)
+{
+#ifdef PKG_USING_EASYFLASH
+    EfErrCode result = EF_NO_ERR;
+
+    /* set and store the wlan config information to Env */
+    result = ef_set_env_blob("lpf_conf_lgw", buff, len);
+    if (result == EF_NO_ERR)
+    {
+        LPF_DEBUG_LOG(LPF_DBG_PARAMS_CONF, LOG_LVL_INFO,"LPF GW Chip Parameters(%d) Save Successed!\n", len);
+        return RT_EOK;
+    }
+    else
+    {
+        LPF_DEBUG_LOG(LPF_DBG_PARAMS_CONF, LOG_LVL_INFO,"LPF GW Chip Parameters Save Fail,%d!\n", result);
+        return -RT_ERROR;
+    }
+#else
+    return -RT_ERROR;
+#endif    
+}
+
+int lpf_conf_srv_nv_save(void *buff, int len)
+{
+#ifdef PKG_USING_EASYFLASH    
+    EfErrCode result = EF_NO_ERR;
+    
+    /* set and store the lpf server config information to Env */
+    result = ef_set_env_blob("lpf_conf_srv", buff, len);
+
+    if (result == EF_NO_ERR)
+    {
+        LPF_DEBUG_LOG(LPF_DBG_PARAMS_CONF, LOG_LVL_INFO,"LPF SRV Parameters(%d) Save Successed!\n", len);
+        return RT_EOK;
+    }
+    else
+    {
+        LPF_DEBUG_LOG(LPF_DBG_PARAMS_CONF, LOG_LVL_INFO,"LPF SRV Parameters Save Fail,%d!\n", result);
+        return -RT_ERROR;
+    }
+#else    
+    return -RT_ERROR;
+#endif    
+}
+
+int lpf_get_lgw_nv_params(void *buff, int len)
+{
+    size_t saved_len = 0;
+#ifdef PKG_USING_EASYFLASH
+    ef_get_env_blob("lpf_conf_lgw", buff, len, &saved_len);
+#endif
+    return saved_len;
+}
+
+int lpf_get_srv_nv_params(void *buff, int len)
+{
+    size_t saved_len = 0;
+#ifdef PKG_USING_EASYFLASH
+    ef_get_env_blob("lpf_conf_srv", buff, len, &saved_len);
+#endif
+    return saved_len;
+}
+
+
+/* --- EOF ------------------------------------------------------------------ */

+ 2353 - 0
src/lora_pkt_fwd_gwmp.c

@@ -0,0 +1,2353 @@
+/*
+ / _____)             _              | |
+( (____  _____ ____ _| |_ _____  ____| |__
+ \____ \| ___ |    (_   _) ___ |/ ___)  _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+  (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
+            Forest-Rain
+*/
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+/* fix an issue between POSIX and C99 */
+#if __STDC_VERSION__ >= 199901L
+    #define _XOPEN_SOURCE 600
+#else
+    #define _XOPEN_SOURCE 500
+#endif
+
+#include <rtthread.h>
+
+#include <stdint.h>         /* C99 types */
+#include <stdbool.h>        /* bool type */
+#include <stdio.h>          /* MSG, fprintf, snprintf, fopen, fputs */
+#include <inttypes.h>       /* PRIx64, PRIu64... */
+#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 <sys/socket.h>     /* socket specific definitions */
+
+#include <netdb.h>          /* gai_strerror */
+#include <pthread.h>
+
+#include "jitqueue.h"
+#include "timersync.h"
+#include "parson.h"
+#include "base64.h"
+#include "loragw_hal.h"
+#include "loragw_usr.h"
+
+#include "clock_time.h"
+#include "lora_pkt_fwd.h"
+
+#ifdef PKG_USING_EASYFLASH
+#include <easyflash.h>
+#endif
+
+#define DEBUG_PKT_FWD   1//0
+#define DEBUG_TIMERSYNC 0
+#define DEBUG_BEACON    0
+#define DEBUG_LOG       1
+
+#define LOG_TAG     "lpf.gwmp"
+#include "lora_pkt_fwd_dbg.h"
+
+#define MSG(args...)                  LPF_DEBUG_LOG(LPF_DBG_GWMP, LOG_LVL_INFO, args)
+#define MSG_DEBUG(args...)            LPF_DEBUG_LOG(LPF_DBG_GWMP, LOG_LVL_DBG, args) 
+#define DEBUG_MSG(args...)            LPF_DEBUG_LOG(LPF_DBG_GWMP, LOG_LVL_DBG, args)
+
+#define exit(x) 
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a)   (sizeof(a) / sizeof((a)[0]))
+#define STRINGIFY(x)    #x
+#define STR(x)          STRINGIFY(x)
+
+#define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min)
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#ifndef VERSION_STRING
+  #define VERSION_STRING "undefined"
+#endif
+
+#define DEFAULT_KEEPALIVE   5           /* default time interval for downstream keep-alive packet */
+#define DEFAULT_STAT        30          /* default time interval for statistics ms */
+#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.6 */
+#define PROTOCOL_JSON_RXPK_FRAME_FORMAT 1
+
+#define XERR_INIT_AVG       16          /* 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 ACK_BUFF_SIZE   64
+
+#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
+
+struct lpf_conf_srv_s lpf_conf_srv;
+const struct lpf_conf_srv_s lpf_conf_srv_default[LPF_SUPPORTED_NETWORK_SERVER_NUMS] =
+{
+    /* lieda unicore server */
+    {
+        .magic = 0xAA55,
+        .northboud_if = LPF_NORTHBOUND_IF_OVER_WIFI,
+        .server_addr = STR(47.110.127.110),
+        .server_port_up = STR(8441),
+        .server_port_down = STR(8441),
+        .json_conf_file = STR(sdcard/lgw/lgw_unicore_cn470s_udp.json),
+    },
+    
+    /* ttn server */
+    {
+        .magic = 0xAA55,
+        .northboud_if = LPF_NORTHBOUND_IF_OVER_WIFI,
+        .server_addr = STR(eu1.cloud.thethings.network),
+        .server_port_up = STR(1700),
+        .server_port_down = STR(1700),
+        .json_conf_file = STR(sdcard/lgw/lgw_ttn_eu868_udp.json),
+    },
+    
+     /* tencent iot-explorer server */
+    {
+        .magic = 0xAA55,
+        .northboud_if = LPF_NORTHBOUND_IF_OVER_WIFI,
+        .server_addr = STR(loragw.things.qcloud.com),
+        .server_port_up = STR(1700),
+        .server_port_down = STR(1700),
+        .json_conf_file = STR(sdcard/lgw/lgw_ttn_eu868_udp.json),
+    },
+};
+
+/* -------------------------------------------------------------------------- */
+/* --- 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 gweui[17] = {0};
+static char serv_addr[64] = {0}; /* address of the server (host name or IPv4/IPv6) */
+static char serv_port_up[8] = {0}; /* server port for upstream traffic */
+static char serv_port_down[8] = {0}; /* 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 */
+
+/* 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_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 */
+
+/* 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[LGW_RF_CHAIN_NB];
+
+/* Gateway specificities */
+static int8_t antenna_gain = 0;
+
+/* TX capabilities setup by json */
+static struct lgw_tx_gain_lut_s txlut[LGW_RF_CHAIN_NB]; /* TX gain table */
+
+static uint32_t tx_freq_min[LGW_RF_CHAIN_NB]=  /* lowest frequency supported by TX chain */ // 200716濠⒀呭仜婵偟绱撻搹瑙勯槣闁哄牞鎷烽悘蹇撶箻椤e爼鎮抽敓锟�
+{
+    470300000,
+    470300000,
+};
+static uint32_t tx_freq_max[LGW_RF_CHAIN_NB] =  /* highest frequency supported by TX chain */
+{
+    525000000,
+    525000000,
+};
+
+static struct rt_thread lpf_main_thread;
+static rt_uint32_t lpf_main_stack[8192];
+
+#define DEFAULT_PRIORITY    (RT_THREAD_PRIORITY_MAX/2 + RT_THREAD_PRIORITY_MAX/4)
+
+const pthread_attr_t lpf_pthread_attr = 
+{
+    0,                          /* stack base */
+    8192*2,                     /* stack size */
+    PTHREAD_INHERIT_SCHED,      /* Inherit parent prio/policy */
+    SCHED_FIFO,                 /* scheduler policy */
+    {
+        DEFAULT_PRIORITY,       /* scheduler priority */
+    },
+    PTHREAD_CREATE_JOINABLE,    /* detach state */
+};
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
+#ifdef LORA_PKT_FWD_USING_PARAMS_CONF_BY_JSON
+static int parse_gateway_configuration(const char * conf_file);
+#endif
+static double difftimespec(struct timespec end, struct timespec beginning);
+static int get_tx_gain_lut_index(uint8_t rf_chain, int8_t rf_power, uint8_t * lut_index);
+/* threads */
+static void thread_up(void);
+static void thread_down(void);
+static void thread_jit(void);
+
+static void lpf_thread_entry(void* parameter);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+#ifdef LORA_PKT_FWD_USING_PARAMS_CONF_BY_JSON
+static int parse_SX130x_configuration(const char * conf_file) {
+    int i, j;
+    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[] = "SX130x_conf";
+    JSON_Value *root_val = NULL;
+    JSON_Value *val = NULL;
+    JSON_Object *conf_obj = NULL;
+    JSON_Object *conf_txgain_obj;
+    JSON_Object *conf_ts_obj;
+    JSON_Array *conf_txlut_array;
+
+    struct lgw_conf_board_s boardconf;
+    struct lgw_conf_rxrf_s rfconf;
+    struct lgw_conf_rxif_s ifconf;
+    struct lgw_conf_ftime_s tsconf;
+    uint32_t sf, bw, fdev;
+    bool sx1250_tx_lut;
+
+    /* 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 SX1302 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;
+    }
+    val = json_object_get_value(conf_obj, "full_duplex"); /* fetch value (if possible) */
+    if (json_value_get_type(val) == JSONBoolean) {
+        boardconf.full_duplex = (bool)json_value_get_boolean(val);
+    } else {
+        MSG("WARNING: Data type for full_duplex seems wrong, please check\n");
+        boardconf.full_duplex = false;
+    }
+
+    MSG("INFO: lorawan_public %d, clksrc %d, full_duplex %d\n", boardconf.lorawan_public, boardconf.clksrc, boardconf.full_duplex); 
+ 
+    /* 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 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 timestamp configuration */
+    conf_ts_obj = json_object_get_object(conf_obj, "fine_timestamp");
+    if (conf_ts_obj == NULL) {
+        MSG("INFO: %s does not contain a JSON object for precision timestamp\n", conf_file);
+    } else {
+        val = json_object_get_value(conf_ts_obj, "enable"); /* fetch value (if possible) */
+        if (json_value_get_type(val) == JSONBoolean) {
+            tsconf.enable = (bool)json_value_get_boolean(val);
+        } else {
+            MSG("WARNING: Data type for fine_timestamp.enable seems wrong, please check\n");
+            tsconf.enable = false;
+        }
+        if (tsconf.enable == true) {
+            str = json_object_get_string(conf_ts_obj, "mode");
+            if (str == NULL) {
+                MSG("ERROR: fine_timestamp.mode must be configured in %s\n", conf_file);
+                return -1;
+            } else if (!strncmp(str, "high_capacity", 13) || !strncmp(str, "HIGH_CAPACITY", 13)) {
+                tsconf.mode = LGW_FTIME_MODE_HIGH_CAPACITY;
+            } else if (!strncmp(str, "all_sf", 6) || !strncmp(str, "ALL_SF", 6)) {
+                tsconf.mode = LGW_FTIME_MODE_ALL_SF;
+            } else {
+                MSG("ERROR: invalid fine timestamp mode: %s (should be high_capacity or all_sf)\n", str);
+                return -1;
+            }
+            MSG("INFO: Configuring precision timestamp with %s mode\n", str);
+
+            /* all parameters parsed, submitting configuration to the HAL */
+            if (lgw_ftime_setconf(&tsconf) != LGW_HAL_SUCCESS) {
+                MSG("ERROR: Failed to configure fine timestamp\n");
+                return -1;
+            }
+        } else {
+            MSG("INFO: Configuring legacy timestamp\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.rssi_tcomp.coeff_a", i);
+            rfconf.rssi_tcomp.coeff_a = (float)json_object_dotget_number(conf_obj, param_name);
+            snprintf(param_name, sizeof param_name, "radio_%i.rssi_tcomp.coeff_b", i);
+            rfconf.rssi_tcomp.coeff_b = (float)json_object_dotget_number(conf_obj, param_name);
+            snprintf(param_name, sizeof param_name, "radio_%i.rssi_tcomp.coeff_c", i);
+            rfconf.rssi_tcomp.coeff_c = (float)json_object_dotget_number(conf_obj, param_name);
+            snprintf(param_name, sizeof param_name, "radio_%i.rssi_tcomp.coeff_d", i);
+            rfconf.rssi_tcomp.coeff_d = (float)json_object_dotget_number(conf_obj, param_name);
+            snprintf(param_name, sizeof param_name, "radio_%i.rssi_tcomp.coeff_e", i);
+            rfconf.rssi_tcomp.coeff_e = (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 if (!strncmp(str, "SX1250", 6)) {
+                rfconf.type = LGW_RADIO_TYPE_SX1250;
+            } else {
+                MSG("WARNING: invalid radio type: %s (should be SX1255 or SX1257 or SX1250)\n", str);
+            }
+            snprintf(param_name, sizeof param_name, "radio_%i.single_input_mode", i);
+            val = json_object_dotget_value(conf_obj, param_name);
+            if (json_value_get_type(val) == JSONBoolean) {
+                rfconf.single_input_mode = (bool)json_value_get_boolean(val);
+            } else {
+                rfconf.single_input_mode = false;
+            }
+
+            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);
+                    }
+
+                    /* set configuration for tx gains */
+                    memset(&txlut[i], 0, sizeof txlut[i]); /* initialize configuration structure */
+                    snprintf(param_name, sizeof param_name, "radio_%i.tx_gain_lut", i);
+                    conf_txlut_array = json_object_dotget_array(conf_obj, param_name);
+                    if (conf_txlut_array != NULL) {
+                        txlut[i].size = json_array_get_count(conf_txlut_array);
+                        /* Detect if we have a sx125x or sx1250 configuration */
+                        conf_txgain_obj = json_array_get_object(conf_txlut_array, 0);
+                        val = json_object_dotget_value(conf_txgain_obj, "pwr_idx");
+                        if (val != NULL) {
+                            MSG("INFO: Configuring Tx Gain LUT for rf_chain %u with %u indexes for sx1250\n", i, txlut[i].size);
+                            sx1250_tx_lut = true;
+                        } else {
+                            MSG("INFO: Configuring Tx Gain LUT for rf_chain %u with %u indexes for sx125x\n", i, txlut[i].size);
+                            sx1250_tx_lut = false;
+                        }
+                        /* Parse the table */
+                        for (j = 0; j < (int)txlut[i].size; j++) {
+                             /* Sanity check */
+                            if (j >= TX_GAIN_LUT_SIZE_MAX) {
+                                MSG("ERROR: TX Gain LUT [%u] index %d not supported, skip it\n", i, j);
+                                break;
+                            }
+                            /* Get TX gain object from LUT */
+                            conf_txgain_obj = json_array_get_object(conf_txlut_array, j);
+                            /* rf power */
+                            val = json_object_dotget_value(conf_txgain_obj, "rf_power");
+                            if (json_value_get_type(val) == JSONNumber) {
+                                txlut[i].lut[j].rf_power = (int8_t)json_value_get_number(val);
+                            } else {
+                                MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", "rf_power", j);
+                                txlut[i].lut[j].rf_power = 0;
+                            }
+                            /* PA gain */
+                            val = json_object_dotget_value(conf_txgain_obj, "pa_gain");
+                            if (json_value_get_type(val) == JSONNumber) {
+                                txlut[i].lut[j].pa_gain = (uint8_t)json_value_get_number(val);
+                            } else {
+                                MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", "pa_gain", j);
+                                txlut[i].lut[j].pa_gain = 0;
+                            }
+                            if (sx1250_tx_lut == false) {
+                                /* DIG gain */
+                                val = json_object_dotget_value(conf_txgain_obj, "dig_gain");
+                                if (json_value_get_type(val) == JSONNumber) {
+                                    txlut[i].lut[j].dig_gain = (uint8_t)json_value_get_number(val);
+                                } else {
+                                    MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", "dig_gain", j);
+                                    txlut[i].lut[j].dig_gain = 0;
+                                }
+                                /* DAC gain */
+                                val = json_object_dotget_value(conf_txgain_obj, "dac_gain");
+                                if (json_value_get_type(val) == JSONNumber) {
+                                    txlut[i].lut[j].dac_gain = (uint8_t)json_value_get_number(val);
+                                } else {
+                                    MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", "dac_gain", j);
+                                    txlut[i].lut[j].dac_gain = 3; /* This is the only dac_gain supported for now */
+                                }
+                                /* MIX gain */
+                                val = json_object_dotget_value(conf_txgain_obj, "mix_gain");
+                                if (json_value_get_type(val) == JSONNumber) {
+                                    txlut[i].lut[j].mix_gain = (uint8_t)json_value_get_number(val);
+                                } else {
+                                    MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", "mix_gain", j);
+                                    txlut[i].lut[j].mix_gain = 0;
+                                }
+                            } else {
+                                /* TODO: rework this, should not be needed for sx1250 */
+                                txlut[i].lut[j].mix_gain = 5;
+
+                                /* power index */
+                                val = json_object_dotget_value(conf_txgain_obj, "pwr_idx");
+                                if (json_value_get_type(val) == JSONNumber) {
+                                    txlut[i].lut[j].pwr_idx = (uint8_t)json_value_get_number(val);
+                                } else {
+                                    MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", "pwr_idx", j);
+                                    txlut[i].lut[j].pwr_idx = 0;
+                                }
+                            }
+                        }
+                        /* all parameters parsed, submitting configuration to the HAL */
+                        if (txlut[i].size > 0) {
+                            if (lgw_txgain_setconf(i, &txlut[i]) != LGW_HAL_SUCCESS) {
+                                MSG("ERROR: Failed to configure concentrator TX Gain LUT for rf_chain %u\n", i);
+                                return -1;
+                            }
+                        } else {
+                            MSG("WARNING: No TX gain LUT defined for rf_chain %u\n", i);
+                        }
+                    } else {
+                        MSG("WARNING: No TX gain LUT defined for rf_chain %u\n", i);
+                    }
+                }
+            } else {
+                rfconf.tx_enable = false;
+            }
+            MSG("INFO: radio %i enabled (type %s), center frequency %u, RSSI offset %f, tx enabled %d, single input mode %d\n", i, str, rfconf.freq_hz, rfconf.rssi_offset, rfconf.tx_enable, rfconf.single_input_mode);
+        }
+        /* 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 5 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  5: ifconf.datarate = DR_LORA_SF5;  break;
+                case  6: ifconf.datarate = DR_LORA_SF6;  break;
+                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;
+            }
+            val = json_object_dotget_value(conf_obj, "chan_Lora_std.implicit_hdr");
+            if (json_value_get_type(val) == JSONBoolean) {
+                ifconf.implicit_hdr = (bool)json_value_get_boolean(val);
+            } else {
+                ifconf.implicit_hdr = false;
+            }
+            if (ifconf.implicit_hdr == true) {
+                val = json_object_dotget_value(conf_obj, "chan_Lora_std.implicit_payload_length");
+                if (json_value_get_type(val) == JSONNumber) {
+                    ifconf.implicit_payload_length = (uint8_t)json_value_get_number(val);
+                } else {
+                    MSG("ERROR: payload length setting is mandatory for implicit header mode\n");
+                    return -1;
+                }
+                val = json_object_dotget_value(conf_obj, "chan_Lora_std.implicit_crc_en");
+                if (json_value_get_type(val) == JSONBoolean) {
+                    ifconf.implicit_crc_en = (bool)json_value_get_boolean(val);
+                } else {
+                    MSG("ERROR: CRC enable setting is mandatory for implicit header mode\n");
+                    return -1;
+                }
+                val = json_object_dotget_value(conf_obj, "chan_Lora_std.implicit_coderate");
+                if (json_value_get_type(val) == JSONNumber) {
+                    ifconf.implicit_coderate = (uint8_t)json_value_get_number(val);
+                } else {
+                    MSG("ERROR: coding rate setting is mandatory for implicit header mode\n");
+                    return -1;
+                }
+            }
+
+            MSG("INFO: Lora std channel> radio %i, IF %i Hz, %u Hz bw, SF %u, %s\n", ifconf.rf_chain, ifconf.freq_hz, bw, sf, (ifconf.implicit_hdr == true) ? "Implicit header" : "Explicit header");
+        }
+        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;
+#if 0 /* TODO */
+            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;
+#endif
+            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);
+        serv_addr[sizeof serv_addr - 1] = '\0'; /* ensure string termination */
+        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"));
+
+    /* free JSON parsing data structure */
+    json_value_free(root_val);
+    return 0;
+}
+#endif /* JSON FILE */
+
+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, int32_t error_value) {
+    uint8_t buff_ack[64]; /* buffer to give feedback to server */
+    int buff_index;
+    int j;
+
+    /* 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 */
+        switch( error ) {
+            case JIT_ERROR_TX_POWER:
+                memcpy((void *)(buff_ack + buff_index), (void *)"\"warn\":", 7);
+                buff_index += 7;
+                break;
+            default:
+                memcpy((void *)(buff_ack + buff_index), (void *)"\"error\":", 8);
+                buff_index += 8;
+                break;
+        }
+		/* set error/warning type in JSON structure */
+        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;
+        }
+        /* set error/warning details in JSON structure */
+        switch (error)
+        {
+        case JIT_ERROR_TX_POWER:
+            j = snprintf((char *) (buff_ack + buff_index), ACK_BUFF_SIZE - buff_index, ",\"value\":%d", error_value);
+            if (j > 0)
+            {
+                buff_index += j;
+            }
+            else
+            {
+                MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));
+                exit(EXIT_FAILURE);
+            }
+            break;
+        default:
+            /* Do nothing */
+            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 lpf_init(void)
+{
+    if( lpf_main_thread.entry == RT_NULL )
+    {
+        //rt_thread_t tid = rt_thread_create("lora_pkt_fwd", lpf_thread_entry, NULL, 8192, 15, 5);
+        rt_err_t result = rt_thread_init(&lpf_main_thread,  
+                            "lpf-main",                    
+                            lpf_thread_entry, 
+                            RT_NULL,                     
+                            &lpf_main_stack[0],   
+                            sizeof(lpf_main_stack),   
+                            6, 
+                            10); 
+        if (result == RT_EOK)
+        {
+            rt_thread_startup(&lpf_main_thread);
+        }
+        else
+        {
+            MSG("lpf main thread create failed!\n");
+            return false;
+        }
+    }
+    else
+    {
+        MSG("lpf auto start disable or had started\n");
+    }
+    return 0;
+}
+
+void lpf_thread_entry(void* parameter)
+{
+    int i; /* loop variable and temporary variable for return value */
+
+#ifdef LORA_PKT_FWD_USING_PARAMS_CONF_BY_JSON
+    const char * conf_fname = lpf_conf_srv.json_conf_file;
+#endif
+    /* threads */
+    pthread_t thrid_up;
+    pthread_t thrid_down;
+    pthread_t thrid_jit;
+
+    /* network socket creation */
+    struct addrinfo hints;
+    struct addrinfo *result; /* store result of getaddrinfo */
+    struct addrinfo *q; /* pointer to move into *result data */
+
+    /* 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;
+    /* SX1302 data variables */
+    uint32_t inst_tstamp;
+    float temperature;
+
+    /* 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("*** 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
+
+    /* lpf parameter default configuration */
+    lgw_init(&lpf_conf_lgw);
+
+    memcpy((struct lgw_tx_gain_lut_s *)&txlut[0],(struct lgw_tx_gain_lut_s *)lgw_txgain_getconf(0),sizeof(struct lgw_tx_gain_lut_s));
+    
+    strcpy(serv_addr, lpf_conf_srv.server_addr);
+    strcpy(serv_port_up,lpf_conf_srv.server_port_up);
+    strcpy(serv_port_down,lpf_conf_srv.server_port_down);
+    
+    /* parse_gateway_configuration use to setup gateway running parameters
+     *  boardconf
+     */
+#ifdef LORA_PKT_FWD_USING_PARAMS_CONF_BY_JSON
+    /* load configuration files */
+    if (access(conf_fname, R_OK) == 0)
+    { /* if there is a global conf, parse it  */
+        MSG("INFO: found configuration file %s, parsing it\n", conf_fname);
+        int x;
+        x = parse_SX130x_configuration(conf_fname);
+        if (x != 0)
+        {
+            exit(EXIT_FAILURE);
+            return;
+        }
+        x = parse_gateway_configuration(conf_fname);
+        if (x != 0)
+        {
+            exit(EXIT_FAILURE);
+            return;
+        }
+    }
+    else
+    {
+        MSG("ERROR: [main] failed to find any configuration file named %s\n", conf_fname);
+        exit(EXIT_FAILURE);
+    }
+#endif
+    
+    /* sanity check on configuration variables */
+    // TODO
+    struct in_addr addr;
+    /* 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; /* UDP */    
+
+    /* look for server address w/ upstream port */
+    MSG("Network Server: %s\n", serv_addr);
+    
+    //for(uint8_t j = 0;j<10;j++)
+    {
+        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,strerror(i));
+            //if( j == 9 )
+            {
+                exit(EXIT_FAILURE);
+            }
+        }
+    }
+    
+    addr.s_addr = ((struct sockaddr_in*)(result->ai_addr))->sin_addr.s_addr;
+    MSG("Network ip addresss: %s\n", inet_ntoa(addr));
+    MSG("UP Port: %s\n", serv_port_up);
+    MSG("DN Port: %s\n", serv_port_down);
+
+    /* 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 */
+    }
+
+    /* 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, strerror(i));
+        exit(EXIT_FAILURE);
+    }
+    
+    addr.s_addr = ((struct sockaddr_in*)(result->ai_addr))->sin_addr.s_addr;
+    MSG("[dn]ip addresss: %s\n", inet_ntoa(addr));
+    
+    /* 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 */
+    }
+
+    /* 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);
+    
+    /* board reset */
+    lgw_hw_reset();
+
+    /* 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);
+        return;
+    }
+
+    if(lgwm == 0)
+    {
+        /* get the concentrator EUI */
+        i = lgw_get_eui(&lgwm);
+        if (i != LGW_HAL_SUCCESS) {
+            MSG("ERROR: failed to get concentrator EUI\n");
+        } else {
+            MSG("INFO: concentrator EUI: 0x%016" PRIx64 "\n", lgwm);
+        }
+    }
+    /* process some of the configuration variables */
+    net_mac_h = htonl((uint32_t)(0xFFFFFFFF & (lgwm>>32)));
+    net_mac_l = htonl((uint32_t)(0xFFFFFFFF &  lgwm  ));
+    snprintf(gweui, sizeof gweui, "%016"PRIX64, lgwm);
+    MSG("INFO: Gateway MAC: %s\n", (char *)gweui);
+
+    /* spawn threads to manage upstream and downstream */
+    i = pthread_create( &thrid_up, &lpf_pthread_attr, (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, &lpf_pthread_attr, (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, &lpf_pthread_attr, (void * (*)(void *))thread_jit, NULL);
+    if (i != 0) {
+        MSG("ERROR: [main] impossible to create JIT thread\n");
+        exit(EXIT_FAILURE);
+    }
+
+    /* main loop task : statistics collection */
+    while (!exit_sig && !quit_sig) {
+        /* wait for next reporting interval */
+        rt_thread_mdelay(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;
+        }
+
+        /* display a report */
+        MSG("\n##### %s #####\n", stat_timestamp);
+        MSG("### [UPSTREAM] ###\n");
+        MSG("# RF packets received by concentrator: %u\n", cp_nb_rx_rcv);
+        MSG("# CRC_OK: %.2f%%, CRC_FAIL: %.2f%%, NO_CRC: %.2f%%\n", (float)100.0 * rx_ok_ratio, (float)100.0 * rx_bad_ratio, (float)100.0 * rx_nocrc_ratio);
+        MSG("# RF packets forwarded: %u (%u bytes)\n", cp_up_pkt_fwd, cp_up_payload_byte);
+        MSG("# PUSH_DATA datagrams sent: %u (%u bytes)\n", cp_up_dgram_sent, cp_up_network_byte);
+        MSG("# PUSH_DATA acknowledged: %.2f%%\n", (float)100.0 * up_ack_ratio);
+        MSG("### [DOWNSTREAM] ###\n");
+        MSG("# PULL_DATA sent: %u (%.2f%% acknowledged)\n", cp_dw_pull_sent, (float)100.0 * dw_ack_ratio);
+        MSG("# PULL_RESP(onse) datagrams received: %u (%u bytes)\n", cp_dw_dgram_rcv, cp_dw_network_byte);
+        MSG("# RF packets sent to concentrator: %u (%u bytes)\n", (cp_nb_tx_ok+cp_nb_tx_fail), cp_dw_payload_byte);
+        MSG("# TX errors: %u\n", cp_nb_tx_fail);
+        if (cp_nb_tx_requested != 0 ) {
+            MSG("# 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);
+            MSG("# 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);
+            MSG("# 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);
+            MSG("# 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);
+        }
+		MSG("### SX130x Status ###\n");
+      
+        pthread_mutex_lock(&mx_concent);
+
+        i  = lgw_get_instcnt(&inst_tstamp);
+     
+		pthread_mutex_unlock(&mx_concent);
+        if (i != LGW_HAL_SUCCESS) {
+            MSG("# SX1302 time (PPS): unknown\n");
+        } else {
+            MSG("# SX1302 counter (INST): %u\n", inst_tstamp);
+        }
+
+        MSG("### [JIT] ###\n");
+
+        /* get timestamp captured on PPM pulse  */
+        jit_print_queue (&jit_queue[0], false, DEBUG_LOG);
+        /* get timestamp captured on PPM pulse  */
+        jit_print_queue (&jit_queue[1], false, DEBUG_LOG);	
+
+        i = lgw_get_temperature(&temperature);
+        if (i != LGW_HAL_SUCCESS) {
+            MSG("### Concentrator temperature unknown ###\n");
+        } else {
+            MSG("### Concentrator temperature: %.0f C ###\n", temperature);
+        }
+        
+        MSG("##### END #####\n");
+   
+        {
+            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, (float)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 */
+
+    /* 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");
+        }
+    }
+    /* board reset */
+    lgw_hw_reset();
+    
+    MSG("INFO: Exiting packet forwarder program\n");
+    exit(EXIT_SUCCESS);
+}
+MSH_CMD_EXPORT(lpf_thread_entry, lora pkt fwd entry );
+/* -------------------------------------------------------------------------- */
+/* --- 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;
+
+    /* 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;
+    
+    /* 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)) {
+            rt_thread_mdelay(FETCH_SLEEP_MS);
+            continue;
+        }
+
+        /* 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;
+
+                    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->rssic);
+                    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 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 0x%02X\n", p->status);
+                    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_SF5:
+                        memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF5", 12);
+                        buff_index += 12;
+                        break;
+                    case DR_LORA_SF6:
+                        memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF6", 12);
+                        buff_index += 12;
+                        break;
+                    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 0x%02X\n", p->datarate);
+                        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 0x%02X\n", p->bandwidth);
+                        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 0x%02X\n", p->coderate);
+                        memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"?\"", 11);
+                        buff_index += 11;
+                        exit(EXIT_FAILURE);
+                }
+
+                /* Signal RSSI, payload size */
+                j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssis\":%.0f", roundf(p->rssis));
+                if (j > 0) {
+                    buff_index += j;
+                } else {
+                    MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4));
+                    exit(EXIT_FAILURE);
+                }
+
+                /* Lora SNR */
+                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);
+                }
+
+                /* Lora frequency offset */
+                j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"foff\":%d", p->freq_offset);
+                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 0x%02X\n", p->modulation);
+                exit(EXIT_FAILURE);
+            }
+
+            /* Channel RSSI, payload size, 18-23 useful chars */
+            j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssi\":%.0f,\"size\":%u", roundf(p->rssic), 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 */
+
+        MSG("\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 ---------- */
+static int get_tx_gain_lut_index(uint8_t rf_chain, int8_t rf_power, uint8_t * lut_index) {
+    uint8_t pow_index;
+    int current_best_index = -1;
+    uint8_t current_best_match = 0xFF;
+    int diff;
+
+    /* Check input parameters */
+    if (lut_index == NULL) {
+        MSG("ERROR: %s - wrong parameter\n", __FUNCTION__);
+        return -1;
+    }
+
+    /* Search requested power in TX gain LUT */
+    for (pow_index = 0; pow_index < txlut[rf_chain].size; pow_index++) {
+        diff = rf_power - txlut[rf_chain].lut[pow_index].rf_power;
+        if (diff < 0) {
+            /* The selected power must be lower or equal to requested one */
+            continue;
+        } else {
+            /* Record the index corresponding to the closest rf_power available in LUT */
+            if ((current_best_index == -1) || (diff < current_best_match)) {
+                current_best_match = diff;
+                current_best_index = pow_index;
+            }
+        }
+    }
+
+    /* Return corresponding index */
+    if (current_best_index > -1) {
+        *lut_index = (uint8_t)current_best_index;
+    } else {
+        *lut_index = 0;
+        MSG("ERROR: %s - failed to find tx gain lut index\n", __FUNCTION__);
+        return -1;
+    }
+
+    return 0;
+}
+
+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 = { 0 }; /* time of the pull request */
+    struct timespec recv_time = { 0 }; /* 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;
+
+    /* auto-quit variable */
+    uint32_t autoquit_cnt = 0; /* count the number of PULL_DATA sent since the latest PULL_ACK */
+
+    /* Just In Time downlink */
+	uint32_t current_concentrator_time;
+
+    enum jit_error_e jit_result = JIT_ERROR_OK;
+    enum jit_pkt_type_e downlink_type;
+    enum jit_error_e warning_result = JIT_ERROR_OK;
+    int32_t warning_value = 0;
+    uint8_t tx_lut_idx = 0;
+
+    /* 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);
+        return;
+    }
+
+    /* 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;
+
+    /* JIT queue initialization */
+    //jit_queue_init(&jit_queue);
+    jit_queue_init(&jit_queue[0]);
+    jit_queue_init(&jit_queue[1]);
+
+    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);
+            
+            /* 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 */
+            MSG("\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 {
+ 
+                }
+            }
+
+            /* 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  5: txpkt.datarate = DR_LORA_SF5;  break;
+                    case  6: txpkt.datarate = DR_LORA_SF6;  break;
+                    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 */
+            /* reset error/warning results */
+            jit_result = warning_result = JIT_ERROR_OK;
+            warning_value = 0;
+
+            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]);
+            }
+			/* check TX power before trying to queue packet, send a warning if not supported */
+            if (jit_result == JIT_ERROR_OK) {
+                i = get_tx_gain_lut_index(txpkt.rf_chain, txpkt.rf_power, &tx_lut_idx);
+                if ((i < 0) || (txlut[txpkt.rf_chain].lut[tx_lut_idx].rf_power != txpkt.rf_power)) {
+                    /* this RF power is not supported, throw a warning, and use the closest lower power supported */
+                    warning_result = JIT_ERROR_TX_POWER;
+                    warning_value = (int32_t)txlut[txpkt.rf_chain].lut[tx_lut_idx].rf_power;
+                    MSG("WARNING: Requested TX power is not supported (%ddBm), actual power used: %ddBm\n", txpkt.rf_power, warning_value);
+                    txpkt.rf_power = txlut[txpkt.rf_chain].lut[tx_lut_idx].rf_power;
+                }
+            }
+            /* insert packet to be sent into JIT queue */
+            if (jit_result == JIT_ERROR_OK) {
+                pthread_mutex_lock(&mx_concent);
+                lgw_get_instcnt(&current_concentrator_time);
+                pthread_mutex_unlock(&mx_concent);
+                jit_result = jit_enqueue(&jit_queue[txpkt.rf_chain], current_concentrator_time, &txpkt, downlink_type);  
+               if (jit_result != JIT_ERROR_OK) {
+                    MSG("ERROR: Packet REJECTED (jit error=%d)\n", jit_result);
+  				} else {
+                    /* In case of a warning having been raised before, we notify it */
+                    jit_result = warning_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, warning_value);
+        }
+    }
+    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;
+	uint32_t current_concentrator_time;	
+    enum jit_error_e jit_result;
+    enum jit_pkt_type_e pkt_type;
+    uint8_t tx_status;
+	int i;
+
+    while (!exit_sig && !quit_sig) {
+        rt_thread_mdelay(10);
+
+	for (i = 0; i < LGW_RF_CHAIN_NB; i++) {
+	        /* transfer data and metadata to the concentrator, and schedule TX */	
+	        pthread_mutex_lock(&mx_concent);
+	        lgw_get_instcnt(&current_concentrator_time);
+	        pthread_mutex_unlock(&mx_concent);
+        
+			jit_result = jit_peek(&jit_queue[i], current_concentrator_time, &pkt_index);
+        
+	        if (jit_result == JIT_ERROR_OK) {
+	            if (pkt_index > -1) {
+                    MSG_DEBUG("jit_peek() current concentrator time:%d\n",current_concentrator_time);
+                    
+	                jit_result = jit_dequeue(&jit_queue[i], pkt_index, &pkt, &pkt_type);
+	                if (jit_result == JIT_ERROR_OK) {
+
+	                    /* 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(pkt.rf_chain, TX_STATUS, &tx_status);
+
+	                    pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */
+	                    if (result == LGW_HAL_ERROR) {
+	                        MSG("WARNING: [jit%d] lgw_status failed\n", i);
+	                    } else {
+	                        if (tx_status == TX_EMITTING) {
+	                            MSG("ERROR: concentrator is currently emitting on rf_chain %d\n", i);
+	                            print_tx_status(tx_status);
+	                            continue;
+	                        } else if (tx_status == TX_SCHEDULED) {
+	                            MSG("WARNING: a downlink was already scheduled on rf_chain %d, overwritting it...\n", i);
+	                            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 */
+
+                        uint32_t current_concentrator_time;
+                        lgw_get_instcnt(&current_concentrator_time);
+                        MSG_DEBUG("lgw_send() current concentrator time:%d\n",current_concentrator_time);
+                        
+	                    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 on rf_chain %d\n", i);
+	                        continue;
+	                    } else {
+	                        pthread_mutex_lock(&mx_meas_dw);
+	                        meas_nb_tx_ok += 1;
+	                        pthread_mutex_unlock(&mx_meas_dw);
+                            MSG_DEBUG( "lgw_send done on rf_chain %d: count_us=%u\n", i, pkt.count_us);
+	                    }
+	                } else {
+	                    MSG("ERROR: jit_dequeue failed on rf_chain %d with %d\n", i, jit_result);
+	                }
+	            }
+	        } else if (jit_result == JIT_ERROR_EMPTY) {
+	            /* Do nothing, it can happen */
+	        } else {
+	            MSG("ERROR: jit_peek failed on rf_chain %d with %d\n", i, jit_result);
+            }
+        }
+    }
+}
+
+/* --- EOF ------------------------------------------------------------------ */

+ 242 - 0
src/lora_pkt_fwd_shell.c

@@ -0,0 +1,242 @@
+
+#include "rtthread.h"
+#include <stdlib.h>         /* atol,atoi */
+#include <string.h>         
+#include <stdint.h>         /* C99 types */
+#include <stdbool.h>        /* bool type */
+#include <stdio.h>          /* fprintf, snprintf, fopen, fputs */
+#include <inttypes.h>       /* PRIx64, PRIu64... */
+#include <pthread.h>        /* ptherad */
+
+#ifdef PKG_USING_EASYFLASH
+#include <easyflash.h>
+#endif
+#include "loragw_usr.h"
+#include "lora_pkt_fwd.h"
+
+#define DBG_LVL             DBG_INFO
+#define LOG_TAG             "lpf.shell"
+#include "lora_pkt_fwd_dbg.h"
+
+extern pthread_mutex_t mx_concent;
+
+const char *northboud_if_string[LPF_NORTHBOUND_IF_NUMS] = {"wifi","eth"};
+
+typedef enum
+{
+    /* for lora packet sniffer finish\msh */
+    CMD_LPF_PROBE_INDEX = 0,          // lps device probe
+    CMD_LPF_SUSPEND_INDEX,            // lps suspend
+    CMD_LPF_RESUME_INDEX,             // lps resume
+    CMD_LPF_CFG_SAVE_INDEX,           // lps parameters save    
+    CMD_LPF_FACTORY_INDEX,            // lps factory
+    CMD_LPF_SRV_CONFIG_INDEX,         // lps network server setup
+    CMD_LPF_NIF_CONFIG_INDEX,         // lps northboud interface setup
+    CMD_LPF_RADIO_CONFIG_INDEX,       // lps northboud interface setup
+} cmd_lpf_shell_t;
+
+const char* lpf_help_info[] =
+{
+    [CMD_LPF_PROBE_INDEX]           = "lpf probe                     - lora packet forward probe",
+    [CMD_LPF_SUSPEND_INDEX]         = "lps suspend                   - lora packet forward suspend(pause)",
+    [CMD_LPF_RESUME_INDEX]          = "lps resume                    - lora packet forward resume(run)",
+    [CMD_LPF_CFG_SAVE_INDEX]        = "lpf save                      - save parameters to flash",
+    [CMD_LPF_FACTORY_INDEX]         = "lpf factory                   - restore factory settings",
+    [CMD_LPF_SRV_CONFIG_INDEX]      = "lpf srv <cmd_type> <val..>    - config network server,<cmd_type>:name,addr,pup,pud,addr such as 192.168.1.10..",
+    [CMD_LPF_NIF_CONFIG_INDEX]      = "lpf nif <type>                - config northbound interface parameters,such as wifi,eth..",
+    [CMD_LPF_RADIO_CONFIG_INDEX]    = "lpf rxc <num><val..>          - config phy radio rx parameters,<cmd_type>: <rf0><rf1><auto><iq>..",
+};
+
+int lpf(int argc, char ** argv)
+{
+    size_t i = 0;
+
+    if (argc < 2)
+    {
+        /* parameter error */
+        LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, "Usage:\n");
+        for (i = 0; i < sizeof(lpf_help_info) / sizeof(char*); i++)
+        {
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, "%s\n", lpf_help_info[i]);
+        }
+        LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO,"\n");
+    }
+    else
+    {
+        const char *cmd = argv[1];
+
+        if (!rt_strcmp(cmd, "probe"))
+        {
+            int32_t sx1302_chip_version = 0;
+            int status = lgw_get_ver(&sx1302_chip_version);
+
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO,"SX1302 version: 0x%02X\n", sx1302_chip_version);
+
+            if( status == LGW_HAL_SUCCESS && sx1302_chip_version == 0x10 )
+            {
+                LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO,"SX1302 Access Successed\n");
+            }
+            else
+            {
+                LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO,"SX1302 Access Failed\n");
+            }
+            
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, "SX1302 version: 0x%02X", sx1302_chip_version);
+            
+            uint64_t gw_eui;
+            char gweui_string[17] = {0};
+            status = lgw_get_eui(&gw_eui);
+
+            snprintf(gweui_string, sizeof gweui_string, "%016"PRIX64, gw_eui);
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, "SX1302 EUI:%s",gweui_string);
+        }
+        else if (!rt_strcmp(cmd, "rxc"))
+        {
+            const char *cmd1 = argv[2];
+            uint32_t freq  = atol(argv[3]);
+
+            uint8_t enable = 1;/* default enable */
+
+            if( argc >= 5 )
+            {
+                enable = atol(argv[4]);
+            }
+
+            if( argc >= 3 )
+            {
+                if (!rt_strcmp(cmd1, "rf0")) /* radio0 center frequence */
+                {
+                    lpf_conf_lgw.rxrf[0].enable   = enable;
+
+                    lpf_conf_lgw.rxrf[0].freq_hz  = freq;
+                }
+                else if (!rt_strcmp(cmd1, "rf1")) /* radio1 center frequence */
+                {
+                    lpf_conf_lgw.rxrf[1].enable   = enable;
+
+                    lpf_conf_lgw.rxrf[1].freq_hz  = freq;
+                }
+                else if (!rt_strcmp(cmd1, "auto")) /* setup start freq, 200k space, lgw auto setup radio0 and radio1 chanenl freq. */
+                {
+                    lpf_conf_lgw.rxrf[0].enable   = enable;
+                    lpf_conf_lgw.rxrf[1].enable   = enable;
+
+                    /* eg:set start 475.3 -> radio0 = 475.6 ,radio1 = 476.4 */
+                    lpf_conf_lgw.rxrf[0].freq_hz = freq + LGD_RF_CHIAN0_CENTER_FREQ_OFFSET;
+                    lpf_conf_lgw.rxrf[1].freq_hz = freq + LGD_RF_CHIAN1_CENTER_FREQ_OFFSET;
+                }
+            }
+
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, "-items-                     -{cmd}-   -Value-");
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, " radio-chain-0 center freq   {rf0}:   %d,%d", lpf_conf_lgw.rxrf[0].freq_hz,lpf_conf_lgw.rxrf[0].enable);
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, " radio-chain-1 center freq   {rf1}:   %d,%d", lpf_conf_lgw.rxrf[1].freq_hz,lpf_conf_lgw.rxrf[1].enable);
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, " radio-chain-0-1 start freq  {auto}:  %d,%d", (lpf_conf_lgw.rxrf[0].freq_hz - LGD_RF_CHIAN0_CENTER_FREQ_OFFSET),lpf_conf_lgw.rxrf[0].enable);
+
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_DBG, "== Channels Frequence Table: ==\r\n");
+            for(uint8_t i = 0; i < 8; i++)
+            {
+                uint32_t ch_freq = lpf_conf_lgw.rxrf[lpf_conf_lgw.rxif.channel_if_rfchain[i]].freq_hz + lpf_conf_lgw.rxif.channel_if_freq[i];
+                LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, "CH[%i]:%d,rfchain:%d,%s\r\n", i, ch_freq, lpf_conf_lgw.rxif.channel_if_rfchain[i], lpf_conf_lgw.rxif.channel_if_enable[i] ? "enable" : "disable");
+            }
+        }
+        else if (!rt_strcmp(cmd, "nif"))
+        {
+            const char *type = argv[2];
+            if(argc >= 3)
+            {
+                if (!rt_strcmp(type, "eth"))
+                {
+                    lpf_conf_srv.northboud_if = LPF_NORTHBOUND_IF_OVER_ETHERNET;
+                }
+                else  if (!rt_strcmp(type, "wifi"))
+                {
+                    lpf_conf_srv.northboud_if = LPF_NORTHBOUND_IF_OVER_WIFI;
+                }
+            }
+            else
+            {
+                LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, "-items-         -{cmd}-   -Value-     -help-");
+                LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, " Northbound IF   {nif}     %s(%d):     wifi,eth", northboud_if_string[lpf_conf_srv.northboud_if], lpf_conf_srv.northboud_if);
+            }
+        }
+        else if (!rt_strcmp(cmd, "srv"))
+        {
+            if(argc >= 4)
+            {
+                if (!rt_strcmp(argv[2], "name"))
+                {
+                    if (!rt_strcmp(argv[3], "unicore"))
+                    {
+                        lpf_conf_srv = lpf_conf_srv_default[LPF_SUPPORTED_NETWORK_SERVER_LIERDA_UNICORE_GWMP];
+                    }
+                    else if (!rt_strcmp(argv[3], "ttn"))
+                    {
+                        lpf_conf_srv = lpf_conf_srv_default[LPF_SUPPORTED_NETWORK_SERVER_TTN_GWMP];
+                    }
+                }                 
+                if (!rt_strcmp(argv[2], "addr"))
+                {
+                    /* Server Address(IP address or domain name) */
+                    strcpy(lpf_conf_srv.server_addr, argv[3]);
+                }
+                else if (!rt_strcmp(argv[2], "pup"))
+                {
+                    strcpy(lpf_conf_srv.server_port_up, argv[3]);
+                }
+                else if (!rt_strcmp(argv[2], "pdn"))
+                {
+                    strcpy(lpf_conf_srv.server_port_down, argv[3]);
+                }
+                else if (!rt_strcmp(argv[2], "json"))
+                {
+                    strcpy(lpf_conf_srv.json_conf_file,argv[3]);
+                }
+            }
+            
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, "-items-          -{cmd}-   -Value-");
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, " server address   {addr}    %s", lpf_conf_srv.server_addr);
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, " port up          {pup}     %s", lpf_conf_srv.server_port_up);
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, " port down        {pdn}     %s", lpf_conf_srv.server_port_down);
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, " server json file {json}    %s\r\n", lpf_conf_srv.json_conf_file );
+        }
+        else if(!rt_strcmp(cmd, "suspend"))
+        {
+            pthread_mutex_lock(&mx_concent);
+
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, "lpf rx suspend");
+        }
+        else if(!rt_strcmp(cmd, "resume"))
+        {
+            pthread_mutex_unlock(&mx_concent);
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO, "lpf rx resume");
+        }
+        else if(!rt_strcmp(cmd,"save"))
+        {
+#ifdef PKG_USING_EASYFLASH 
+            /* save lora pkt fwd lgw configuration to flash */
+            lpf_conf_lgw_nv_save(&lpf_conf_lgw.magic, LGW_CONF_USR_SIZE);
+            /* save lora pkt fwd server configuration to flash */
+            lpf_conf_srv_nv_save(&lpf_conf_srv.magic,LPF_CONF_SRV_SIZE);
+            
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO,"save done\r\n");
+#else
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO,"Not Support to Save\r\n");
+#endif            
+        }
+        else if(!rt_strcmp(cmd,"factory"))
+        {
+            lpf_conf_lgw = lpf_conf_lgw_default;
+            lpf_conf_srv = lpf_conf_srv_default[LPF_SUPPORTED_NETWORK_SERVER_LIERDA_UNICORE_GWMP];
+#ifdef PKG_USING_EASYFLASH  
+            /* save lora pkt fwd lgw configuration to flash */
+            lpf_conf_lgw_nv_save(&lpf_conf_lgw.magic,LGW_CONF_USR_SIZE);
+            /* save lora pkt fwd server configuration to flash */
+            lpf_conf_srv_nv_save(&lpf_conf_srv.magic,LPF_CONF_SRV_SIZE);
+#endif            
+            LPF_DEBUG_LOG(LPF_DBG_SHELL, LOG_LVL_INFO,"factory done\r\n");
+        }
+    }
+    return 1;
+}
+
+MSH_CMD_EXPORT(lpf, lora pakcet forward shell);

+ 1765 - 0
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;
+}