|
|
@@ -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(¤t_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(¤t_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(¤t_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 ------------------------------------------------------------------ */
|