From 2a02a3a7dfda2679ebda86fa830023fe996a06c9 Mon Sep 17 00:00:00 2001 From: Harsh Sharma <92harshsharma@gmail.com> Date: Wed, 13 Jun 2018 13:26:38 -0500 Subject: Initial commit --- lora_pkt_fwd/src/lora_pkt_fwd.c | 2889 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 2889 insertions(+) create mode 100644 lora_pkt_fwd/src/lora_pkt_fwd.c (limited to 'lora_pkt_fwd/src/lora_pkt_fwd.c') diff --git a/lora_pkt_fwd/src/lora_pkt_fwd.c b/lora_pkt_fwd/src/lora_pkt_fwd.c new file mode 100644 index 0000000..801f28d --- /dev/null +++ b/lora_pkt_fwd/src/lora_pkt_fwd.c @@ -0,0 +1,2889 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + Configure Lora concentrator and forward packets to a server + Use GPS for packet timestamping. + Send a becon at a regular interval without server intervention + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Michael Coracin +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +/* fix an issue between POSIX and C99 */ +#if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 +#else + #define _XOPEN_SOURCE 500 +#endif + +#include /* C99 types */ +#include /* bool type */ +#include /* printf, fprintf, snprintf, fopen, fputs */ + +#include /* memset */ +#include /* sigaction */ +#include /* time, clock_gettime, strftime, gmtime */ +#include /* timeval */ +#include /* getopt, access */ +#include /* atoi, exit */ +#include /* error messages */ +#include /* modf */ +#include + +#include /* socket specific definitions */ +#include /* INET constants and stuff */ +#include /* IP address conversion stuff */ +#include /* gai_strerror */ + +#include + +#include "trace.h" +#include "jitqueue.h" +#include "timersync.h" +#include "parson.h" +#include "base64.h" +#include "loragw_hal.h" +#include "loragw_gps.h" +#include "loragw_aux.h" +#include "loragw_reg.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define STRINGIFY(x) #x +#define STR(x) STRINGIFY(x) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#ifndef VERSION_STRING + #define VERSION_STRING "undefined" +#endif + +#define DEFAULT_SERVER 127.0.0.1 /* hostname also supported */ +#define DEFAULT_PORT_UP 1780 +#define DEFAULT_PORT_DW 1782 +#define DEFAULT_KEEPALIVE 5 /* default time interval for downstream keep-alive packet */ +#define DEFAULT_STAT 30 /* default time interval for statistics */ +#define PUSH_TIMEOUT_MS 100 +#define PULL_TIMEOUT_MS 200 +#define GPS_REF_MAX_AGE 30 /* maximum admitted delay in seconds of GPS loss before considering latest GPS sync unusable */ +#define FETCH_SLEEP_MS 10 /* nb of ms waited when a fetch return no packets */ +#define BEACON_POLL_MS 50 /* time in ms between polling of beacon TX status */ + +#define PROTOCOL_VERSION 2 /* v1.3 */ + +#define XERR_INIT_AVG 128 /* nb of measurements the XTAL correction is averaged on as initial value */ +#define XERR_FILT_COEF 256 /* coefficient for low-pass XTAL error tracking */ + +#define PKT_PUSH_DATA 0 +#define PKT_PUSH_ACK 1 +#define PKT_PULL_DATA 2 +#define PKT_PULL_RESP 3 +#define PKT_PULL_ACK 4 +#define PKT_TX_ACK 5 + +#define NB_PKT_MAX 8 /* max number of packets per fetch/send cycle */ + +#define MIN_LORA_PREAMB 6 /* minimum Lora preamble length for this application */ +#define STD_LORA_PREAMB 8 +#define MIN_FSK_PREAMB 3 /* minimum FSK preamble length for this application */ +#define STD_FSK_PREAMB 5 + +#define STATUS_SIZE 200 +#define TX_BUFF_SIZE ((540 * NB_PKT_MAX) + 30 + STATUS_SIZE) + +#define UNIX_GPS_EPOCH_OFFSET 315964800 /* Number of seconds ellapsed between 01.Jan.1970 00:00:00 + and 06.Jan.1980 00:00:00 */ + +#define DEFAULT_BEACON_FREQ_HZ 869525000 +#define DEFAULT_BEACON_FREQ_NB 1 +#define DEFAULT_BEACON_FREQ_STEP 0 +#define DEFAULT_BEACON_DATARATE 9 +#define DEFAULT_BEACON_BW_HZ 125000 +#define DEFAULT_BEACON_POWER 14 +#define DEFAULT_BEACON_INFODESC 0 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ + +/* signal handling variables */ +volatile bool exit_sig = false; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +volatile bool quit_sig = false; /* 1 -> application terminates without shutting down the hardware */ + +/* packets filtering configuration variables */ +static bool fwd_valid_pkt = true; /* packets with PAYLOAD CRC OK are forwarded */ +static bool fwd_error_pkt = false; /* packets with PAYLOAD CRC ERROR are NOT forwarded */ +static bool fwd_nocrc_pkt = false; /* packets with NO PAYLOAD CRC are NOT forwarded */ + +/* network configuration variables */ +static uint64_t lgwm = 0; /* Lora gateway MAC address */ +static char serv_addr[64] = STR(DEFAULT_SERVER); /* address of the server (host name or IPv4/IPv6) */ +static char serv_port_up[8] = STR(DEFAULT_PORT_UP); /* server port for upstream traffic */ +static char serv_port_down[8] = STR(DEFAULT_PORT_DW); /* server port for downstream traffic */ +static int keepalive_time = DEFAULT_KEEPALIVE; /* send a PULL_DATA request every X seconds, negative = disabled */ + +/* statistics collection configuration variables */ +static unsigned stat_interval = DEFAULT_STAT; /* time interval (in sec) at which statistics are collected and displayed */ + +/* gateway <-> MAC protocol variables */ +static uint32_t net_mac_h; /* Most Significant Nibble, network order */ +static uint32_t net_mac_l; /* Least Significant Nibble, network order */ + +/* network sockets */ +static int sock_up; /* socket for upstream traffic */ +static int sock_down; /* socket for downstream traffic */ + +/* network protocol variables */ +static struct timeval push_timeout_half = {0, (PUSH_TIMEOUT_MS * 500)}; /* cut in half, critical for throughput */ +static struct timeval pull_timeout = {0, (PULL_TIMEOUT_MS * 1000)}; /* non critical for throughput */ + +/* hardware access control and correction */ +pthread_mutex_t mx_concent = PTHREAD_MUTEX_INITIALIZER; /* control access to the concentrator */ +static pthread_mutex_t mx_xcorr = PTHREAD_MUTEX_INITIALIZER; /* control access to the XTAL correction */ +static bool xtal_correct_ok = false; /* set true when XTAL correction is stable enough */ +static double xtal_correct = 1.0; + +/* GPS configuration and synchronization */ +static char gps_tty_path[64] = "\0"; /* path of the TTY port GPS is connected on */ +static int gps_tty_fd = -1; /* file descriptor of the GPS TTY port */ +static bool gps_enabled = false; /* is GPS enabled on that gateway ? */ + +/* GPS time reference */ +static pthread_mutex_t mx_timeref = PTHREAD_MUTEX_INITIALIZER; /* control access to GPS time reference */ +static bool gps_ref_valid; /* is GPS reference acceptable (ie. not too old) */ +static struct tref time_reference_gps; /* time reference used for GPS <-> timestamp conversion */ + +/* Reference coordinates, for broadcasting (beacon) */ +static struct coord_s reference_coord; + +/* Enable faking the GPS coordinates of the gateway */ +static bool gps_fake_enable; /* enable the feature */ + +/* measurements to establish statistics */ +static pthread_mutex_t mx_meas_up = PTHREAD_MUTEX_INITIALIZER; /* control access to the upstream measurements */ +static uint32_t meas_nb_rx_rcv = 0; /* count packets received */ +static uint32_t meas_nb_rx_ok = 0; /* count packets received with PAYLOAD CRC OK */ +static uint32_t meas_nb_rx_bad = 0; /* count packets received with PAYLOAD CRC ERROR */ +static uint32_t meas_nb_rx_nocrc = 0; /* count packets received with NO PAYLOAD CRC */ +static uint32_t meas_up_pkt_fwd = 0; /* number of radio packet forwarded to the server */ +static uint32_t meas_up_network_byte = 0; /* sum of UDP bytes sent for upstream traffic */ +static uint32_t meas_up_payload_byte = 0; /* sum of radio payload bytes sent for upstream traffic */ +static uint32_t meas_up_dgram_sent = 0; /* number of datagrams sent for upstream traffic */ +static uint32_t meas_up_ack_rcv = 0; /* number of datagrams acknowledged for upstream traffic */ + +static pthread_mutex_t mx_meas_dw = PTHREAD_MUTEX_INITIALIZER; /* control access to the downstream measurements */ +static uint32_t meas_dw_pull_sent = 0; /* number of PULL requests sent for downstream traffic */ +static uint32_t meas_dw_ack_rcv = 0; /* number of PULL requests acknowledged for downstream traffic */ +static uint32_t meas_dw_dgram_rcv = 0; /* count PULL response packets received for downstream traffic */ +static uint32_t meas_dw_network_byte = 0; /* sum of UDP bytes sent for upstream traffic */ +static uint32_t meas_dw_payload_byte = 0; /* sum of radio payload bytes sent for upstream traffic */ +static uint32_t meas_nb_tx_ok = 0; /* count packets emitted successfully */ +static uint32_t meas_nb_tx_fail = 0; /* count packets were TX failed for other reasons */ +static uint32_t meas_nb_tx_requested = 0; /* count TX request from server (downlinks) */ +static uint32_t meas_nb_tx_rejected_collision_packet = 0; /* count packets were TX request were rejected due to collision with another packet already programmed */ +static uint32_t meas_nb_tx_rejected_collision_beacon = 0; /* count packets were TX request were rejected due to collision with a beacon already programmed */ +static uint32_t meas_nb_tx_rejected_too_late = 0; /* count packets were TX request were rejected because it is too late to program it */ +static uint32_t meas_nb_tx_rejected_too_early = 0; /* count packets were TX request were rejected because timestamp is too much in advance */ +static uint32_t meas_nb_beacon_queued = 0; /* count beacon inserted in jit queue */ +static uint32_t meas_nb_beacon_sent = 0; /* count beacon actually sent to concentrator */ +static uint32_t meas_nb_beacon_rejected = 0; /* count beacon rejected for queuing */ + +static pthread_mutex_t mx_meas_gps = PTHREAD_MUTEX_INITIALIZER; /* control access to the GPS statistics */ +static bool gps_coord_valid; /* could we get valid GPS coordinates ? */ +static struct coord_s meas_gps_coord; /* GPS position of the gateway */ +static struct coord_s meas_gps_err; /* GPS position of the gateway */ + +static pthread_mutex_t mx_stat_rep = PTHREAD_MUTEX_INITIALIZER; /* control access to the status report */ +static bool report_ready = false; /* true when there is a new report to send to the server */ +static char status_report[STATUS_SIZE]; /* status report as a JSON object */ + +/* beacon parameters */ +static uint32_t beacon_period = 0; /* set beaconing period, must be a sub-multiple of 86400, the nb of sec in a day */ +static uint32_t beacon_freq_hz = DEFAULT_BEACON_FREQ_HZ; /* set beacon TX frequency, in Hz */ +static uint8_t beacon_freq_nb = DEFAULT_BEACON_FREQ_NB; /* set number of beaconing channels beacon */ +static uint32_t beacon_freq_step = DEFAULT_BEACON_FREQ_STEP; /* set frequency step between beacon channels, in Hz */ +static uint8_t beacon_datarate = DEFAULT_BEACON_DATARATE; /* set beacon datarate (SF) */ +static uint32_t beacon_bw_hz = DEFAULT_BEACON_BW_HZ; /* set beacon bandwidth, in Hz */ +static int8_t beacon_power = DEFAULT_BEACON_POWER; /* set beacon TX power, in dBm */ +static uint8_t beacon_infodesc = DEFAULT_BEACON_INFODESC; /* set beacon information descriptor */ + +/* auto-quit function */ +static uint32_t autoquit_threshold = 0; /* enable auto-quit after a number of non-acknowledged PULL_DATA (0 = disabled)*/ + +/* Just In Time TX scheduling */ +static struct jit_queue_s jit_queue; + +/* Gateway specificities */ +static int8_t antenna_gain = 0; + +/* TX capabilities */ +static struct lgw_tx_gain_lut_s txlut; /* TX gain table */ +static uint32_t tx_freq_min[LGW_RF_CHAIN_NB]; /* lowest frequency supported by TX chain */ +static uint32_t tx_freq_max[LGW_RF_CHAIN_NB]; /* highest frequency supported by TX chain */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +static void sig_handler(int sigio); + +static int parse_SX1301_configuration(const char * conf_file); + +static int parse_gateway_configuration(const char * conf_file); + +static uint16_t crc16(const uint8_t * data, unsigned size); + +static double difftimespec(struct timespec end, struct timespec beginning); + +static void gps_process_sync(void); + +static void gps_process_coords(void); + +/* threads */ +void thread_up(void); +void thread_down(void); +void thread_gps(void); +void thread_valid(void); +void thread_jit(void); +void thread_timersync(void); + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +static void sig_handler(int sigio) { + if (sigio == SIGQUIT) { + quit_sig = true; + } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = true; + } + return; +} + +static int parse_SX1301_configuration(const char * conf_file) { + int i; + char param_name[32]; /* used to generate variable parameter names */ + const char *str; /* used to store string value from JSON object */ + const char conf_obj_name[] = "SX1301_conf"; + JSON_Value *root_val = NULL; + JSON_Object *conf_obj = NULL; + JSON_Object *conf_lbt_obj = NULL; + JSON_Object *conf_lbtchan_obj = NULL; + JSON_Value *val = NULL; + JSON_Array *conf_array = NULL; + struct lgw_conf_board_s boardconf; + struct lgw_conf_lbt_s lbtconf; + struct lgw_conf_rxrf_s rfconf; + struct lgw_conf_rxif_s ifconf; + uint32_t sf, bw, fdev; + + /* try to parse JSON */ + root_val = json_parse_file_with_comments(conf_file); + if (root_val == NULL) { + MSG("ERROR: %s is not a valid JSON file\n", conf_file); + exit(EXIT_FAILURE); + } + + /* point to the gateway configuration object */ + conf_obj = json_object_get_object(json_value_get_object(root_val), conf_obj_name); + if (conf_obj == NULL) { + MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj_name); + return -1; + } else { + MSG("INFO: %s does contain a JSON object named %s, parsing SX1301 parameters\n", conf_file, conf_obj_name); + } + + /* set board configuration */ + memset(&boardconf, 0, sizeof boardconf); /* initialize configuration structure */ + val = json_object_get_value(conf_obj, "lorawan_public"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONBoolean) { + boardconf.lorawan_public = (bool)json_value_get_boolean(val); + } else { + MSG("WARNING: Data type for lorawan_public seems wrong, please check\n"); + boardconf.lorawan_public = false; + } + val = json_object_get_value(conf_obj, "clksrc"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + boardconf.clksrc = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for clksrc seems wrong, please check\n"); + boardconf.clksrc = 0; + } + MSG("INFO: lorawan_public %d, clksrc %d\n", boardconf.lorawan_public, boardconf.clksrc); + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_board_setconf(boardconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: Failed to configure board\n"); + return -1; + } + + /* set LBT configuration */ + memset(&lbtconf, 0, sizeof lbtconf); /* initialize configuration structure */ + conf_lbt_obj = json_object_get_object(conf_obj, "lbt_cfg"); /* fetch value (if possible) */ + if (conf_lbt_obj == NULL) { + MSG("INFO: no configuration for LBT\n"); + } else { + val = json_object_get_value(conf_lbt_obj, "enable"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONBoolean) { + lbtconf.enable = (bool)json_value_get_boolean(val); + } else { + MSG("WARNING: Data type for lbt_cfg.enable seems wrong, please check\n"); + lbtconf.enable = false; + } + if (lbtconf.enable == true) { + val = json_object_get_value(conf_lbt_obj, "rssi_target"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + lbtconf.rssi_target = (int8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt_cfg.rssi_target seems wrong, please check\n"); + lbtconf.rssi_target = 0; + } + val = json_object_get_value(conf_lbt_obj, "sx127x_rssi_offset"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + lbtconf.rssi_offset = (int8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt_cfg.sx127x_rssi_offset seems wrong, please check\n"); + lbtconf.rssi_offset = 0; + } + /* set LBT channels configuration */ + conf_array = json_object_get_array(conf_lbt_obj, "chan_cfg"); + if (conf_array != NULL) { + lbtconf.nb_channel = json_array_get_count( conf_array ); + MSG("INFO: %u LBT channels configured\n", lbtconf.nb_channel); + } + for (i = 0; i < (int)lbtconf.nb_channel; i++) { + /* Sanity check */ + if (i >= LBT_CHANNEL_FREQ_NB) + { + MSG("ERROR: LBT channel %d not supported, skip it\n", i ); + break; + } + /* Get LBT channel configuration object from array */ + conf_lbtchan_obj = json_array_get_object(conf_array, i); + + /* Channel frequency */ + val = json_object_dotget_value(conf_lbtchan_obj, "freq_hz"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + lbtconf.channels[i].freq_hz = (uint32_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt_cfg.channels[%d].freq_hz seems wrong, please check\n", i); + lbtconf.channels[i].freq_hz = 0; + } + + /* Channel scan time */ + val = json_object_dotget_value(conf_lbtchan_obj, "scan_time_us"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + lbtconf.channels[i].scan_time_us = (uint16_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt_cfg.channels[%d].scan_time_us seems wrong, please check\n", i); + lbtconf.channels[i].scan_time_us = 0; + } + } + + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_lbt_setconf(lbtconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: Failed to configure LBT\n"); + return -1; + } + } else { + MSG("INFO: LBT is disabled\n"); + } + } + + /* set antenna gain configuration */ + val = json_object_get_value(conf_obj, "antenna_gain"); /* fetch value (if possible) */ + if (val != NULL) { + if (json_value_get_type(val) == JSONNumber) { + antenna_gain = (int8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for antenna_gain seems wrong, please check\n"); + antenna_gain = 0; + } + } + MSG("INFO: antenna_gain %d dBi\n", antenna_gain); + + /* set configuration for tx gains */ + memset(&txlut, 0, sizeof txlut); /* initialize configuration structure */ + for (i = 0; i < TX_GAIN_LUT_SIZE_MAX; i++) { + snprintf(param_name, sizeof param_name, "tx_lut_%i", i); /* compose parameter path inside JSON structure */ + val = json_object_get_value(conf_obj, param_name); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for tx gain lut %i\n", i); + continue; + } + txlut.size++; /* update TX LUT size based on JSON object found in configuration file */ + /* there is an object to configure that TX gain index, let's parse it */ + snprintf(param_name, sizeof param_name, "tx_lut_%i.pa_gain", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONNumber) { + txlut.lut[i].pa_gain = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); + txlut.lut[i].pa_gain = 0; + } + snprintf(param_name, sizeof param_name, "tx_lut_%i.dac_gain", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONNumber) { + txlut.lut[i].dac_gain = (uint8_t)json_value_get_number(val); + } else { + txlut.lut[i].dac_gain = 3; /* This is the only dac_gain supported for now */ + } + snprintf(param_name, sizeof param_name, "tx_lut_%i.dig_gain", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONNumber) { + txlut.lut[i].dig_gain = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); + txlut.lut[i].dig_gain = 0; + } + snprintf(param_name, sizeof param_name, "tx_lut_%i.mix_gain", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONNumber) { + txlut.lut[i].mix_gain = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); + txlut.lut[i].mix_gain = 0; + } + snprintf(param_name, sizeof param_name, "tx_lut_%i.rf_power", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONNumber) { + txlut.lut[i].rf_power = (int8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for %s[%d] seems wrong, please check\n", param_name, i); + txlut.lut[i].rf_power = 0; + } + } + /* all parameters parsed, submitting configuration to the HAL */ + if (txlut.size > 0) { + MSG("INFO: Configuring TX LUT with %u indexes\n", txlut.size); + if (lgw_txgain_setconf(&txlut) != LGW_HAL_SUCCESS) { + MSG("ERROR: Failed to configure concentrator TX Gain LUT\n"); + return -1; + } + } else { + MSG("WARNING: No TX gain LUT defined\n"); + } + + /* set configuration for RF chains */ + for (i = 0; i < LGW_RF_CHAIN_NB; ++i) { + memset(&rfconf, 0, sizeof rfconf); /* initialize configuration structure */ + snprintf(param_name, sizeof param_name, "radio_%i", i); /* compose parameter path inside JSON structure */ + val = json_object_get_value(conf_obj, param_name); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for radio %i\n", i); + continue; + } + /* there is an object to configure that radio, let's parse it */ + snprintf(param_name, sizeof param_name, "radio_%i.enable", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONBoolean) { + rfconf.enable = (bool)json_value_get_boolean(val); + } else { + rfconf.enable = false; + } + if (rfconf.enable == false) { /* radio disabled, nothing else to parse */ + MSG("INFO: radio %i disabled\n", i); + } else { /* radio enabled, will parse the other parameters */ + snprintf(param_name, sizeof param_name, "radio_%i.freq", i); + rfconf.freq_hz = (uint32_t)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.rssi_offset", i); + rfconf.rssi_offset = (float)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.type", i); + str = json_object_dotget_string(conf_obj, param_name); + if (!strncmp(str, "SX1255", 6)) { + rfconf.type = LGW_RADIO_TYPE_SX1255; + } else if (!strncmp(str, "SX1257", 6)) { + rfconf.type = LGW_RADIO_TYPE_SX1257; + } else { + MSG("WARNING: invalid radio type: %s (should be SX1255 or SX1257)\n", str); + } + snprintf(param_name, sizeof param_name, "radio_%i.tx_enable", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONBoolean) { + rfconf.tx_enable = (bool)json_value_get_boolean(val); + if (rfconf.tx_enable == true) { + /* tx is enabled on this rf chain, we need its frequency range */ + snprintf(param_name, sizeof param_name, "radio_%i.tx_freq_min", i); + tx_freq_min[i] = (uint32_t)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.tx_freq_max", i); + tx_freq_max[i] = (uint32_t)json_object_dotget_number(conf_obj, param_name); + if ((tx_freq_min[i] == 0) || (tx_freq_max[i] == 0)) { + MSG("WARNING: no frequency range specified for TX rf chain %d\n", i); + } + /* ... and the notch filter frequency to be set */ + snprintf(param_name, sizeof param_name, "radio_%i.tx_notch_freq", i); + rfconf.tx_notch_freq = (uint32_t)json_object_dotget_number(conf_obj, param_name); + } + } else { + rfconf.tx_enable = false; + } + MSG("INFO: radio %i enabled (type %s), center frequency %u, RSSI offset %f, tx enabled %d, tx_notch_freq %u\n", i, str, rfconf.freq_hz, rfconf.rssi_offset, rfconf.tx_enable, rfconf.tx_notch_freq); + } + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_rxrf_setconf(i, rfconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: invalid configuration for radio %i\n", i); + return -1; + } + } + + /* set configuration for Lora multi-SF channels (bandwidth cannot be set) */ + for (i = 0; i < LGW_MULTI_NB; ++i) { + memset(&ifconf, 0, sizeof ifconf); /* initialize configuration structure */ + snprintf(param_name, sizeof param_name, "chan_multiSF_%i", i); /* compose parameter path inside JSON structure */ + val = json_object_get_value(conf_obj, param_name); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for Lora multi-SF channel %i\n", i); + continue; + } + /* there is an object to configure that Lora multi-SF channel, let's parse it */ + snprintf(param_name, sizeof param_name, "chan_multiSF_%i.enable", i); + val = json_object_dotget_value(conf_obj, param_name); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { /* Lora multi-SF channel disabled, nothing else to parse */ + MSG("INFO: Lora multi-SF channel %i disabled\n", i); + } else { /* Lora multi-SF channel enabled, will parse the other parameters */ + snprintf(param_name, sizeof param_name, "chan_multiSF_%i.radio", i); + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf_obj, param_name); + snprintf(param_name, sizeof param_name, "chan_multiSF_%i.if", i); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf_obj, param_name); + // TODO: handle individual SF enabling and disabling (spread_factor) + MSG("INFO: Lora multi-SF channel %i> radio %i, IF %i Hz, 125 kHz bw, SF 7 to 12\n", i, ifconf.rf_chain, ifconf.freq_hz); + } + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_rxif_setconf(i, ifconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: invalid configuration for Lora multi-SF channel %i\n", i); + return -1; + } + } + + /* set configuration for Lora standard channel */ + memset(&ifconf, 0, sizeof ifconf); /* initialize configuration structure */ + val = json_object_get_value(conf_obj, "chan_Lora_std"); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for Lora standard channel\n"); + } else { + val = json_object_dotget_value(conf_obj, "chan_Lora_std.enable"); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { + MSG("INFO: Lora standard channel %i disabled\n", i); + } else { + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.radio"); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.if"); + bw = (uint32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.bandwidth"); + switch(bw) { + case 500000: ifconf.bandwidth = BW_500KHZ; break; + case 250000: ifconf.bandwidth = BW_250KHZ; break; + case 125000: ifconf.bandwidth = BW_125KHZ; break; + default: ifconf.bandwidth = BW_UNDEFINED; + } + sf = (uint32_t)json_object_dotget_number(conf_obj, "chan_Lora_std.spread_factor"); + switch(sf) { + case 7: ifconf.datarate = DR_LORA_SF7; break; + case 8: ifconf.datarate = DR_LORA_SF8; break; + case 9: ifconf.datarate = DR_LORA_SF9; break; + case 10: ifconf.datarate = DR_LORA_SF10; break; + case 11: ifconf.datarate = DR_LORA_SF11; break; + case 12: ifconf.datarate = DR_LORA_SF12; break; + default: ifconf.datarate = DR_UNDEFINED; + } + MSG("INFO: Lora std channel> radio %i, IF %i Hz, %u Hz bw, SF %u\n", ifconf.rf_chain, ifconf.freq_hz, bw, sf); + } + if (lgw_rxif_setconf(8, ifconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: invalid configuration for Lora standard channel\n"); + return -1; + } + } + + /* set configuration for FSK channel */ + memset(&ifconf, 0, sizeof ifconf); /* initialize configuration structure */ + val = json_object_get_value(conf_obj, "chan_FSK"); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for FSK channel\n"); + } else { + val = json_object_dotget_value(conf_obj, "chan_FSK.enable"); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { + MSG("INFO: FSK channel %i disabled\n", i); + } else { + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.radio"); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf_obj, "chan_FSK.if"); + bw = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.bandwidth"); + fdev = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.freq_deviation"); + ifconf.datarate = (uint32_t)json_object_dotget_number(conf_obj, "chan_FSK.datarate"); + + /* if chan_FSK.bandwidth is set, it has priority over chan_FSK.freq_deviation */ + if ((bw == 0) && (fdev != 0)) { + bw = 2 * fdev + ifconf.datarate; + } + if (bw == 0) ifconf.bandwidth = BW_UNDEFINED; + else if (bw <= 7800) ifconf.bandwidth = BW_7K8HZ; + else if (bw <= 15600) ifconf.bandwidth = BW_15K6HZ; + else if (bw <= 31200) ifconf.bandwidth = BW_31K2HZ; + else if (bw <= 62500) ifconf.bandwidth = BW_62K5HZ; + else if (bw <= 125000) ifconf.bandwidth = BW_125KHZ; + else if (bw <= 250000) ifconf.bandwidth = BW_250KHZ; + else if (bw <= 500000) ifconf.bandwidth = BW_500KHZ; + else ifconf.bandwidth = BW_UNDEFINED; + + MSG("INFO: FSK channel> radio %i, IF %i Hz, %u Hz bw, %u bps datarate\n", ifconf.rf_chain, ifconf.freq_hz, bw, ifconf.datarate); + } + if (lgw_rxif_setconf(9, ifconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: invalid configuration for FSK channel\n"); + return -1; + } + } + json_value_free(root_val); + + return 0; +} + +static int parse_gateway_configuration(const char * conf_file) { + const char conf_obj_name[] = "gateway_conf"; + JSON_Value *root_val; + JSON_Object *conf_obj = NULL; + JSON_Value *val = NULL; /* needed to detect the absence of some fields */ + const char *str; /* pointer to sub-strings in the JSON data */ + unsigned long long ull = 0; + + /* try to parse JSON */ + root_val = json_parse_file_with_comments(conf_file); + if (root_val == NULL) { + MSG("ERROR: %s is not a valid JSON file\n", conf_file); + exit(EXIT_FAILURE); + } + + /* point to the gateway configuration object */ + conf_obj = json_object_get_object(json_value_get_object(root_val), conf_obj_name); + if (conf_obj == NULL) { + MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj_name); + return -1; + } else { + MSG("INFO: %s does contain a JSON object named %s, parsing gateway parameters\n", conf_file, conf_obj_name); + } + + /* gateway unique identifier (aka MAC address) (optional) */ + str = json_object_get_string(conf_obj, "gateway_ID"); + if (str != NULL) { + sscanf(str, "%llx", &ull); + lgwm = ull; + MSG("INFO: gateway MAC address is configured to %016llX\n", ull); + } + + /* server hostname or IP address (optional) */ + str = json_object_get_string(conf_obj, "server_address"); + if (str != NULL) { + strncpy(serv_addr, str, sizeof serv_addr); + MSG("INFO: server hostname or IP address is configured to \"%s\"\n", serv_addr); + } + + /* get up and down ports (optional) */ + val = json_object_get_value(conf_obj, "serv_port_up"); + if (val != NULL) { + snprintf(serv_port_up, sizeof serv_port_up, "%u", (uint16_t)json_value_get_number(val)); + MSG("INFO: upstream port is configured to \"%s\"\n", serv_port_up); + } + val = json_object_get_value(conf_obj, "serv_port_down"); + if (val != NULL) { + snprintf(serv_port_down, sizeof serv_port_down, "%u", (uint16_t)json_value_get_number(val)); + MSG("INFO: downstream port is configured to \"%s\"\n", serv_port_down); + } + + /* get keep-alive interval (in seconds) for downstream (optional) */ + val = json_object_get_value(conf_obj, "keepalive_interval"); + if (val != NULL) { + keepalive_time = (int)json_value_get_number(val); + MSG("INFO: downstream keep-alive interval is configured to %u seconds\n", keepalive_time); + } + + /* get interval (in seconds) for statistics display (optional) */ + val = json_object_get_value(conf_obj, "stat_interval"); + if (val != NULL) { + stat_interval = (unsigned)json_value_get_number(val); + MSG("INFO: statistics display interval is configured to %u seconds\n", stat_interval); + } + + /* get time-out value (in ms) for upstream datagrams (optional) */ + val = json_object_get_value(conf_obj, "push_timeout_ms"); + if (val != NULL) { + push_timeout_half.tv_usec = 500 * (long int)json_value_get_number(val); + MSG("INFO: upstream PUSH_DATA time-out is configured to %u ms\n", (unsigned)(push_timeout_half.tv_usec / 500)); + } + + /* packet filtering parameters */ + val = json_object_get_value(conf_obj, "forward_crc_valid"); + if (json_value_get_type(val) == JSONBoolean) { + fwd_valid_pkt = (bool)json_value_get_boolean(val); + } + MSG("INFO: packets received with a valid CRC will%s be forwarded\n", (fwd_valid_pkt ? "" : " NOT")); + val = json_object_get_value(conf_obj, "forward_crc_error"); + if (json_value_get_type(val) == JSONBoolean) { + fwd_error_pkt = (bool)json_value_get_boolean(val); + } + MSG("INFO: packets received with a CRC error will%s be forwarded\n", (fwd_error_pkt ? "" : " NOT")); + val = json_object_get_value(conf_obj, "forward_crc_disabled"); + if (json_value_get_type(val) == JSONBoolean) { + fwd_nocrc_pkt = (bool)json_value_get_boolean(val); + } + MSG("INFO: packets received with no CRC will%s be forwarded\n", (fwd_nocrc_pkt ? "" : " NOT")); + + /* GPS module TTY path (optional) */ + str = json_object_get_string(conf_obj, "gps_tty_path"); + if (str != NULL) { + strncpy(gps_tty_path, str, sizeof gps_tty_path); + MSG("INFO: GPS serial port path is configured to \"%s\"\n", gps_tty_path); + } + + /* get reference coordinates */ + val = json_object_get_value(conf_obj, "ref_latitude"); + if (val != NULL) { + reference_coord.lat = (double)json_value_get_number(val); + MSG("INFO: Reference latitude is configured to %f deg\n", reference_coord.lat); + } + val = json_object_get_value(conf_obj, "ref_longitude"); + if (val != NULL) { + reference_coord.lon = (double)json_value_get_number(val); + MSG("INFO: Reference longitude is configured to %f deg\n", reference_coord.lon); + } + val = json_object_get_value(conf_obj, "ref_altitude"); + if (val != NULL) { + reference_coord.alt = (short)json_value_get_number(val); + MSG("INFO: Reference altitude is configured to %i meters\n", reference_coord.alt); + } + + /* Gateway GPS coordinates hardcoding (aka. faking) option */ + val = json_object_get_value(conf_obj, "fake_gps"); + if (json_value_get_type(val) == JSONBoolean) { + gps_fake_enable = (bool)json_value_get_boolean(val); + if (gps_fake_enable == true) { + MSG("INFO: fake GPS is enabled\n"); + } else { + MSG("INFO: fake GPS is disabled\n"); + } + } + + /* Beacon signal period (optional) */ + val = json_object_get_value(conf_obj, "beacon_period"); + if (val != NULL) { + beacon_period = (uint32_t)json_value_get_number(val); + if ((beacon_period > 0) && (beacon_period < 6)) { + MSG("ERROR: invalid configuration for Beacon period, must be >= 6s\n"); + return -1; + } else { + MSG("INFO: Beaconing period is configured to %u seconds\n", beacon_period); + } + } + + /* Beacon TX frequency (optional) */ + val = json_object_get_value(conf_obj, "beacon_freq_hz"); + if (val != NULL) { + beacon_freq_hz = (uint32_t)json_value_get_number(val); + MSG("INFO: Beaconing signal will be emitted at %u Hz\n", beacon_freq_hz); + } + + /* Number of beacon channels (optional) */ + val = json_object_get_value(conf_obj, "beacon_freq_nb"); + if (val != NULL) { + beacon_freq_nb = (uint8_t)json_value_get_number(val); + MSG("INFO: Beaconing channel number is set to %u\n", beacon_freq_nb); + } + + /* Frequency step between beacon channels (optional) */ + val = json_object_get_value(conf_obj, "beacon_freq_step"); + if (val != NULL) { + beacon_freq_step = (uint32_t)json_value_get_number(val); + MSG("INFO: Beaconing channel frequency step is set to %uHz\n", beacon_freq_step); + } + + /* Beacon datarate (optional) */ + val = json_object_get_value(conf_obj, "beacon_datarate"); + if (val != NULL) { + beacon_datarate = (uint8_t)json_value_get_number(val); + MSG("INFO: Beaconing datarate is set to SF%d\n", beacon_datarate); + } + + /* Beacon modulation bandwidth (optional) */ + val = json_object_get_value(conf_obj, "beacon_bw_hz"); + if (val != NULL) { + beacon_bw_hz = (uint32_t)json_value_get_number(val); + MSG("INFO: Beaconing modulation bandwidth is set to %dHz\n", beacon_bw_hz); + } + + /* Beacon TX power (optional) */ + val = json_object_get_value(conf_obj, "beacon_power"); + if (val != NULL) { + beacon_power = (int8_t)json_value_get_number(val); + MSG("INFO: Beaconing TX power is set to %ddBm\n", beacon_power); + } + + /* Beacon information descriptor (optional) */ + val = json_object_get_value(conf_obj, "beacon_infodesc"); + if (val != NULL) { + beacon_infodesc = (uint8_t)json_value_get_number(val); + MSG("INFO: Beaconing information descriptor is set to %u\n", beacon_infodesc); + } + + /* Auto-quit threshold (optional) */ + val = json_object_get_value(conf_obj, "autoquit_threshold"); + if (val != NULL) { + autoquit_threshold = (uint32_t)json_value_get_number(val); + MSG("INFO: Auto-quit after %u non-acknowledged PULL_DATA\n", autoquit_threshold); + } + + /* free JSON parsing data structure */ + json_value_free(root_val); + return 0; +} + +static uint16_t crc16(const uint8_t * data, unsigned size) { + const uint16_t crc_poly = 0x1021; + const uint16_t init_val = 0x0000; + uint16_t x = init_val; + unsigned i, j; + + if (data == NULL) { + return 0; + } + + for (i=0; i>32))); + net_mac_l = htonl((uint32_t)(0xFFFFFFFF & lgwm )); + + /* prepare hints to open network sockets */ + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_INET; /* WA: Forcing IPv4 as AF_UNSPEC makes connection on localhost to fail */ + hints.ai_socktype = SOCK_DGRAM; + + /* look for server address w/ upstream port */ + i = getaddrinfo(serv_addr, serv_port_up, &hints, &result); + if (i != 0) { + MSG("ERROR: [up] getaddrinfo on address %s (PORT %s) returned %s\n", serv_addr, serv_port_up, gai_strerror(i)); + exit(EXIT_FAILURE); + } + + /* try to open socket for upstream traffic */ + for (q=result; q!=NULL; q=q->ai_next) { + sock_up = socket(q->ai_family, q->ai_socktype,q->ai_protocol); + if (sock_up == -1) continue; /* try next field */ + else break; /* success, get out of loop */ + } + if (q == NULL) { + MSG("ERROR: [up] failed to open socket to any of server %s addresses (port %s)\n", serv_addr, serv_port_up); + i = 1; + for (q=result; q!=NULL; q=q->ai_next) { + getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); + MSG("INFO: [up] result %i host:%s service:%s\n", i, host_name, port_name); + ++i; + } + exit(EXIT_FAILURE); + } + + /* connect so we can send/receive packet with the server only */ + i = connect(sock_up, q->ai_addr, q->ai_addrlen); + if (i != 0) { + MSG("ERROR: [up] connect returned %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + freeaddrinfo(result); + + /* look for server address w/ downstream port */ + i = getaddrinfo(serv_addr, serv_port_down, &hints, &result); + if (i != 0) { + MSG("ERROR: [down] getaddrinfo on address %s (port %s) returned %s\n", serv_addr, serv_port_up, gai_strerror(i)); + exit(EXIT_FAILURE); + } + + /* try to open socket for downstream traffic */ + for (q=result; q!=NULL; q=q->ai_next) { + sock_down = socket(q->ai_family, q->ai_socktype,q->ai_protocol); + if (sock_down == -1) continue; /* try next field */ + else break; /* success, get out of loop */ + } + if (q == NULL) { + MSG("ERROR: [down] failed to open socket to any of server %s addresses (port %s)\n", serv_addr, serv_port_up); + i = 1; + for (q=result; q!=NULL; q=q->ai_next) { + getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); + MSG("INFO: [down] result %i host:%s service:%s\n", i, host_name, port_name); + ++i; + } + exit(EXIT_FAILURE); + } + + /* connect so we can send/receive packet with the server only */ + i = connect(sock_down, q->ai_addr, q->ai_addrlen); + if (i != 0) { + MSG("ERROR: [down] connect returned %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + freeaddrinfo(result); + + /* starting the concentrator */ + i = lgw_start(); + if (i == LGW_HAL_SUCCESS) { + MSG("INFO: [main] concentrator started, packet can now be received\n"); + } else { + MSG("ERROR: [main] failed to start the concentrator\n"); + exit(EXIT_FAILURE); + } + + /* spawn threads to manage upstream and downstream */ + i = pthread_create( &thrid_up, NULL, (void * (*)(void *))thread_up, NULL); + if (i != 0) { + MSG("ERROR: [main] impossible to create upstream thread\n"); + exit(EXIT_FAILURE); + } + i = pthread_create( &thrid_down, NULL, (void * (*)(void *))thread_down, NULL); + if (i != 0) { + MSG("ERROR: [main] impossible to create downstream thread\n"); + exit(EXIT_FAILURE); + } + i = pthread_create( &thrid_jit, NULL, (void * (*)(void *))thread_jit, NULL); + if (i != 0) { + MSG("ERROR: [main] impossible to create JIT thread\n"); + exit(EXIT_FAILURE); + } + i = pthread_create( &thrid_timersync, NULL, (void * (*)(void *))thread_timersync, NULL); + if (i != 0) { + MSG("ERROR: [main] impossible to create Timer Sync thread\n"); + exit(EXIT_FAILURE); + } + + /* spawn thread to manage GPS */ + if (gps_enabled == true) { + i = pthread_create( &thrid_gps, NULL, (void * (*)(void *))thread_gps, NULL); + if (i != 0) { + MSG("ERROR: [main] impossible to create GPS thread\n"); + exit(EXIT_FAILURE); + } + i = pthread_create( &thrid_valid, NULL, (void * (*)(void *))thread_valid, NULL); + if (i != 0) { + MSG("ERROR: [main] impossible to create validation thread\n"); + exit(EXIT_FAILURE); + } + } + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); /* Ctrl-\ */ + sigaction(SIGINT, &sigact, NULL); /* Ctrl-C */ + sigaction(SIGTERM, &sigact, NULL); /* default "kill" command */ + + /* main loop task : statistics collection */ + while (!exit_sig && !quit_sig) { + /* wait for next reporting interval */ + wait_ms(1000 * stat_interval); + + /* get timestamp for statistics */ + t = time(NULL); + strftime(stat_timestamp, sizeof stat_timestamp, "%F %T %Z", gmtime(&t)); + + /* access upstream statistics, copy and reset them */ + pthread_mutex_lock(&mx_meas_up); + cp_nb_rx_rcv = meas_nb_rx_rcv; + cp_nb_rx_ok = meas_nb_rx_ok; + cp_nb_rx_bad = meas_nb_rx_bad; + cp_nb_rx_nocrc = meas_nb_rx_nocrc; + cp_up_pkt_fwd = meas_up_pkt_fwd; + cp_up_network_byte = meas_up_network_byte; + cp_up_payload_byte = meas_up_payload_byte; + cp_up_dgram_sent = meas_up_dgram_sent; + cp_up_ack_rcv = meas_up_ack_rcv; + meas_nb_rx_rcv = 0; + meas_nb_rx_ok = 0; + meas_nb_rx_bad = 0; + meas_nb_rx_nocrc = 0; + meas_up_pkt_fwd = 0; + meas_up_network_byte = 0; + meas_up_payload_byte = 0; + meas_up_dgram_sent = 0; + meas_up_ack_rcv = 0; + pthread_mutex_unlock(&mx_meas_up); + if (cp_nb_rx_rcv > 0) { + rx_ok_ratio = (float)cp_nb_rx_ok / (float)cp_nb_rx_rcv; + rx_bad_ratio = (float)cp_nb_rx_bad / (float)cp_nb_rx_rcv; + rx_nocrc_ratio = (float)cp_nb_rx_nocrc / (float)cp_nb_rx_rcv; + } else { + rx_ok_ratio = 0.0; + rx_bad_ratio = 0.0; + rx_nocrc_ratio = 0.0; + } + if (cp_up_dgram_sent > 0) { + up_ack_ratio = (float)cp_up_ack_rcv / (float)cp_up_dgram_sent; + } else { + up_ack_ratio = 0.0; + } + + /* access downstream statistics, copy and reset them */ + pthread_mutex_lock(&mx_meas_dw); + cp_dw_pull_sent = meas_dw_pull_sent; + cp_dw_ack_rcv = meas_dw_ack_rcv; + cp_dw_dgram_rcv = meas_dw_dgram_rcv; + cp_dw_network_byte = meas_dw_network_byte; + cp_dw_payload_byte = meas_dw_payload_byte; + cp_nb_tx_ok = meas_nb_tx_ok; + cp_nb_tx_fail = meas_nb_tx_fail; + cp_nb_tx_requested += meas_nb_tx_requested; + cp_nb_tx_rejected_collision_packet += meas_nb_tx_rejected_collision_packet; + cp_nb_tx_rejected_collision_beacon += meas_nb_tx_rejected_collision_beacon; + cp_nb_tx_rejected_too_late += meas_nb_tx_rejected_too_late; + cp_nb_tx_rejected_too_early += meas_nb_tx_rejected_too_early; + cp_nb_beacon_queued += meas_nb_beacon_queued; + cp_nb_beacon_sent += meas_nb_beacon_sent; + cp_nb_beacon_rejected += meas_nb_beacon_rejected; + meas_dw_pull_sent = 0; + meas_dw_ack_rcv = 0; + meas_dw_dgram_rcv = 0; + meas_dw_network_byte = 0; + meas_dw_payload_byte = 0; + meas_nb_tx_ok = 0; + meas_nb_tx_fail = 0; + meas_nb_tx_requested = 0; + meas_nb_tx_rejected_collision_packet = 0; + meas_nb_tx_rejected_collision_beacon = 0; + meas_nb_tx_rejected_too_late = 0; + meas_nb_tx_rejected_too_early = 0; + meas_nb_beacon_queued = 0; + meas_nb_beacon_sent = 0; + meas_nb_beacon_rejected = 0; + pthread_mutex_unlock(&mx_meas_dw); + if (cp_dw_pull_sent > 0) { + dw_ack_ratio = (float)cp_dw_ack_rcv / (float)cp_dw_pull_sent; + } else { + dw_ack_ratio = 0.0; + } + + /* access GPS statistics, copy them */ + if (gps_enabled == true) { + pthread_mutex_lock(&mx_meas_gps); + coord_ok = gps_coord_valid; + cp_gps_coord = meas_gps_coord; + pthread_mutex_unlock(&mx_meas_gps); + } + + /* overwrite with reference coordinates if function is enabled */ + if (gps_fake_enable == true) { + cp_gps_coord = reference_coord; + } + + /* display a report */ + printf("\n##### %s #####\n", stat_timestamp); + printf("### [UPSTREAM] ###\n"); + printf("# RF packets received by concentrator: %u\n", cp_nb_rx_rcv); + printf("# CRC_OK: %.2f%%, CRC_FAIL: %.2f%%, NO_CRC: %.2f%%\n", 100.0 * rx_ok_ratio, 100.0 * rx_bad_ratio, 100.0 * rx_nocrc_ratio); + printf("# RF packets forwarded: %u (%u bytes)\n", cp_up_pkt_fwd, cp_up_payload_byte); + printf("# PUSH_DATA datagrams sent: %u (%u bytes)\n", cp_up_dgram_sent, cp_up_network_byte); + printf("# PUSH_DATA acknowledged: %.2f%%\n", 100.0 * up_ack_ratio); + printf("### [DOWNSTREAM] ###\n"); + printf("# PULL_DATA sent: %u (%.2f%% acknowledged)\n", cp_dw_pull_sent, 100.0 * dw_ack_ratio); + printf("# PULL_RESP(onse) datagrams received: %u (%u bytes)\n", cp_dw_dgram_rcv, cp_dw_network_byte); + printf("# RF packets sent to concentrator: %u (%u bytes)\n", (cp_nb_tx_ok+cp_nb_tx_fail), cp_dw_payload_byte); + printf("# TX errors: %u\n", cp_nb_tx_fail); + if (cp_nb_tx_requested != 0 ) { + printf("# TX rejected (collision packet): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_collision_packet / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_collision_packet); + printf("# TX rejected (collision beacon): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_collision_beacon / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_collision_beacon); + printf("# TX rejected (too late): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_too_late / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_too_late); + printf("# TX rejected (too early): %.2f%% (req:%u, rej:%u)\n", 100.0 * cp_nb_tx_rejected_too_early / cp_nb_tx_requested, cp_nb_tx_requested, cp_nb_tx_rejected_too_early); + } + printf("# BEACON queued: %u\n", cp_nb_beacon_queued); + printf("# BEACON sent so far: %u\n", cp_nb_beacon_sent); + printf("# BEACON rejected: %u\n", cp_nb_beacon_rejected); + printf("### [JIT] ###\n"); + /* get timestamp captured on PPM pulse */ + pthread_mutex_lock(&mx_concent); + i = lgw_get_trigcnt(&trig_tstamp); + pthread_mutex_unlock(&mx_concent); + if (i != LGW_HAL_SUCCESS) { + printf("# SX1301 time (PPS): unknown\n"); + } else { + printf("# SX1301 time (PPS): %u\n", trig_tstamp); + } + jit_print_queue (&jit_queue, false, DEBUG_LOG); + printf("### [GPS] ###\n"); + if (gps_enabled == true) { + /* no need for mutex, display is not critical */ + if (gps_ref_valid == true) { + printf("# Valid time reference (age: %li sec)\n", (long)difftime(time(NULL), time_reference_gps.systime)); + } else { + printf("# Invalid time reference (age: %li sec)\n", (long)difftime(time(NULL), time_reference_gps.systime)); + } + if (coord_ok == true) { + printf("# GPS coordinates: latitude %.5f, longitude %.5f, altitude %i m\n", cp_gps_coord.lat, cp_gps_coord.lon, cp_gps_coord.alt); + } else { + printf("# no valid GPS coordinates available yet\n"); + } + } else if (gps_fake_enable == true) { + printf("# GPS *FAKE* coordinates: latitude %.5f, longitude %.5f, altitude %i m\n", cp_gps_coord.lat, cp_gps_coord.lon, cp_gps_coord.alt); + } else { + printf("# GPS sync is disabled\n"); + } + printf("##### END #####\n"); + + /* generate a JSON report (will be sent to server by upstream thread) */ + pthread_mutex_lock(&mx_stat_rep); + if (((gps_enabled == true) && (coord_ok == true)) || (gps_fake_enable == true)) { + snprintf(status_report, STATUS_SIZE, "\"stat\":{\"time\":\"%s\",\"lati\":%.5f,\"long\":%.5f,\"alti\":%i,\"rxnb\":%u,\"rxok\":%u,\"rxfw\":%u,\"ackr\":%.1f,\"dwnb\":%u,\"txnb\":%u}", stat_timestamp, cp_gps_coord.lat, cp_gps_coord.lon, cp_gps_coord.alt, cp_nb_rx_rcv, cp_nb_rx_ok, cp_up_pkt_fwd, 100.0 * up_ack_ratio, cp_dw_dgram_rcv, cp_nb_tx_ok); + } else { + snprintf(status_report, STATUS_SIZE, "\"stat\":{\"time\":\"%s\",\"rxnb\":%u,\"rxok\":%u,\"rxfw\":%u,\"ackr\":%.1f,\"dwnb\":%u,\"txnb\":%u}", stat_timestamp, cp_nb_rx_rcv, cp_nb_rx_ok, cp_up_pkt_fwd, 100.0 * up_ack_ratio, cp_dw_dgram_rcv, cp_nb_tx_ok); + } + report_ready = true; + pthread_mutex_unlock(&mx_stat_rep); + } + + /* wait for upstream thread to finish (1 fetch cycle max) */ + pthread_join(thrid_up, NULL); + pthread_cancel(thrid_down); /* don't wait for downstream thread */ + pthread_cancel(thrid_jit); /* don't wait for jit thread */ + pthread_cancel(thrid_timersync); /* don't wait for timer sync thread */ + if (gps_enabled == true) { + pthread_cancel(thrid_gps); /* don't wait for GPS thread */ + pthread_cancel(thrid_valid); /* don't wait for validation thread */ + + i = lgw_gps_disable(gps_tty_fd); + if (i == LGW_HAL_SUCCESS) { + MSG("INFO: GPS closed successfully\n"); + } else { + MSG("WARNING: failed to close GPS successfully\n"); + } + } + + /* if an exit signal was received, try to quit properly */ + if (exit_sig) { + /* shut down network sockets */ + shutdown(sock_up, SHUT_RDWR); + shutdown(sock_down, SHUT_RDWR); + /* stop the hardware */ + i = lgw_stop(); + if (i == LGW_HAL_SUCCESS) { + MSG("INFO: concentrator stopped successfully\n"); + } else { + MSG("WARNING: failed to stop concentrator successfully\n"); + } + } + + MSG("INFO: Exiting packet forwarder program\n"); + exit(EXIT_SUCCESS); +} + +/* -------------------------------------------------------------------------- */ +/* --- THREAD 1: RECEIVING PACKETS AND FORWARDING THEM ---------------------- */ + +void thread_up(void) { + int i, j; /* loop variables */ + unsigned pkt_in_dgram; /* nb on Lora packet in the current datagram */ + + /* allocate memory for packet fetching and processing */ + struct lgw_pkt_rx_s rxpkt[NB_PKT_MAX]; /* array containing inbound packets + metadata */ + struct lgw_pkt_rx_s *p; /* pointer on a RX packet */ + int nb_pkt; + + /* local copy of GPS time reference */ + bool ref_ok = false; /* determine if GPS time reference must be used or not */ + struct tref local_ref; /* time reference used for UTC <-> timestamp conversion */ + + /* data buffers */ + uint8_t buff_up[TX_BUFF_SIZE]; /* buffer to compose the upstream packet */ + int buff_index; + uint8_t buff_ack[32]; /* buffer to receive acknowledges */ + + /* protocol variables */ + uint8_t token_h; /* random token for acknowledgement matching */ + uint8_t token_l; /* random token for acknowledgement matching */ + + /* ping measurement variables */ + struct timespec send_time; + struct timespec recv_time; + + /* GPS synchronization variables */ + struct timespec pkt_utc_time; + struct tm * x; /* broken-up UTC time */ + struct timespec pkt_gps_time; + uint64_t pkt_gps_time_ms; + + /* report management variable */ + bool send_report = false; + + /* mote info variables */ + uint32_t mote_addr = 0; + uint16_t mote_fcnt = 0; + + /* set upstream socket RX timeout */ + i = setsockopt(sock_up, SOL_SOCKET, SO_RCVTIMEO, (void *)&push_timeout_half, sizeof push_timeout_half); + if (i != 0) { + MSG("ERROR: [up] setsockopt returned %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + /* pre-fill the data buffer with fixed fields */ + buff_up[0] = PROTOCOL_VERSION; + buff_up[3] = PKT_PUSH_DATA; + *(uint32_t *)(buff_up + 4) = net_mac_h; + *(uint32_t *)(buff_up + 8) = net_mac_l; + + while (!exit_sig && !quit_sig) { + + /* fetch packets */ + pthread_mutex_lock(&mx_concent); + nb_pkt = lgw_receive(NB_PKT_MAX, rxpkt); + pthread_mutex_unlock(&mx_concent); + if (nb_pkt == LGW_HAL_ERROR) { + MSG("ERROR: [up] failed packet fetch, exiting\n"); + exit(EXIT_FAILURE); + } + + /* check if there are status report to send */ + send_report = report_ready; /* copy the variable so it doesn't change mid-function */ + /* no mutex, we're only reading */ + + /* wait a short time if no packets, nor status report */ + if ((nb_pkt == 0) && (send_report == false)) { + wait_ms(FETCH_SLEEP_MS); + continue; + } + + /* get a copy of GPS time reference (avoid 1 mutex per packet) */ + if ((nb_pkt > 0) && (gps_enabled == true)) { + pthread_mutex_lock(&mx_timeref); + ref_ok = gps_ref_valid; + local_ref = time_reference_gps; + pthread_mutex_unlock(&mx_timeref); + } else { + ref_ok = false; + } + + /* start composing datagram with the header */ + token_h = (uint8_t)rand(); /* random token */ + token_l = (uint8_t)rand(); /* random token */ + buff_up[1] = token_h; + buff_up[2] = token_l; + buff_index = 12; /* 12-byte header */ + + /* start of JSON structure */ + memcpy((void *)(buff_up + buff_index), (void *)"{\"rxpk\":[", 9); + buff_index += 9; + + /* serialize Lora packets metadata and payload */ + pkt_in_dgram = 0; + for (i=0; i < nb_pkt; ++i) { + p = &rxpkt[i]; + + /* Get mote information from current packet (addr, fcnt) */ + /* FHDR - DevAddr */ + mote_addr = p->payload[1]; + mote_addr |= p->payload[2] << 8; + mote_addr |= p->payload[3] << 16; + mote_addr |= p->payload[4] << 24; + /* FHDR - FCnt */ + mote_fcnt = p->payload[6]; + mote_fcnt |= p->payload[7] << 8; + + /* basic packet filtering */ + pthread_mutex_lock(&mx_meas_up); + meas_nb_rx_rcv += 1; + switch(p->status) { + case STAT_CRC_OK: + meas_nb_rx_ok += 1; + printf( "\nINFO: Received pkt from mote: %08X (fcnt=%u)\n", mote_addr, mote_fcnt ); + if (!fwd_valid_pkt) { + pthread_mutex_unlock(&mx_meas_up); + continue; /* skip that packet */ + } + break; + case STAT_CRC_BAD: + meas_nb_rx_bad += 1; + if (!fwd_error_pkt) { + pthread_mutex_unlock(&mx_meas_up); + continue; /* skip that packet */ + } + break; + case STAT_NO_CRC: + meas_nb_rx_nocrc += 1; + if (!fwd_nocrc_pkt) { + pthread_mutex_unlock(&mx_meas_up); + continue; /* skip that packet */ + } + break; + default: + MSG("WARNING: [up] received packet with unknown status %u (size %u, modulation %u, BW %u, DR %u, RSSI %.1f)\n", p->status, p->size, p->modulation, p->bandwidth, p->datarate, p->rssi); + pthread_mutex_unlock(&mx_meas_up); + continue; /* skip that packet */ + // exit(EXIT_FAILURE); + } + meas_up_pkt_fwd += 1; + meas_up_payload_byte += p->size; + pthread_mutex_unlock(&mx_meas_up); + + /* Start of packet, add inter-packet separator if necessary */ + if (pkt_in_dgram == 0) { + buff_up[buff_index] = '{'; + ++buff_index; + } else { + buff_up[buff_index] = ','; + buff_up[buff_index+1] = '{'; + buff_index += 2; + } + + /* RAW timestamp, 8-17 useful chars */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, "\"tmst\":%u", p->count_us); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* Packet RX time (GPS based), 37 useful chars */ + if (ref_ok == true) { + /* convert packet timestamp to UTC absolute time */ + j = lgw_cnt2utc(local_ref, p->count_us, &pkt_utc_time); + if (j == LGW_GPS_SUCCESS) { + /* split the UNIX timestamp to its calendar components */ + x = gmtime(&(pkt_utc_time.tv_sec)); + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"time\":\"%04i-%02i-%02iT%02i:%02i:%02i.%06liZ\"", (x->tm_year)+1900, (x->tm_mon)+1, x->tm_mday, x->tm_hour, x->tm_min, x->tm_sec, (pkt_utc_time.tv_nsec)/1000); /* ISO 8601 format */ + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + } + /* convert packet timestamp to GPS absolute time */ + j = lgw_cnt2gps(local_ref, p->count_us, &pkt_gps_time); + if (j == LGW_GPS_SUCCESS) { + pkt_gps_time_ms = pkt_gps_time.tv_sec * 1E3 + pkt_gps_time.tv_nsec / 1E6; + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"tmms\":%llu", + pkt_gps_time_ms); /* GPS time in milliseconds since 06.Jan.1980 */ + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + } + } + + /* Packet concentrator channel, RF chain & RX frequency, 34-36 useful chars */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"chan\":%1u,\"rfch\":%1u,\"freq\":%.6lf", p->if_chain, p->rf_chain, ((double)p->freq_hz / 1e6)); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* Packet status, 9-10 useful chars */ + switch (p->status) { + case STAT_CRC_OK: + memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":1", 9); + buff_index += 9; + break; + case STAT_CRC_BAD: + memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":-1", 10); + buff_index += 10; + break; + case STAT_NO_CRC: + memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":0", 9); + buff_index += 9; + break; + default: + MSG("ERROR: [up] received packet with unknown status\n"); + memcpy((void *)(buff_up + buff_index), (void *)",\"stat\":?", 9); + buff_index += 9; + exit(EXIT_FAILURE); + } + + /* Packet modulation, 13-14 useful chars */ + if (p->modulation == MOD_LORA) { + memcpy((void *)(buff_up + buff_index), (void *)",\"modu\":\"LORA\"", 14); + buff_index += 14; + + /* Lora datarate & bandwidth, 16-19 useful chars */ + switch (p->datarate) { + case DR_LORA_SF7: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF7", 12); + buff_index += 12; + break; + case DR_LORA_SF8: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF8", 12); + buff_index += 12; + break; + case DR_LORA_SF9: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF9", 12); + buff_index += 12; + break; + case DR_LORA_SF10: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF10", 13); + buff_index += 13; + break; + case DR_LORA_SF11: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF11", 13); + buff_index += 13; + break; + case DR_LORA_SF12: + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF12", 13); + buff_index += 13; + break; + default: + MSG("ERROR: [up] lora packet with unknown datarate\n"); + memcpy((void *)(buff_up + buff_index), (void *)",\"datr\":\"SF?", 12); + buff_index += 12; + exit(EXIT_FAILURE); + } + switch (p->bandwidth) { + case BW_125KHZ: + memcpy((void *)(buff_up + buff_index), (void *)"BW125\"", 6); + buff_index += 6; + break; + case BW_250KHZ: + memcpy((void *)(buff_up + buff_index), (void *)"BW250\"", 6); + buff_index += 6; + break; + case BW_500KHZ: + memcpy((void *)(buff_up + buff_index), (void *)"BW500\"", 6); + buff_index += 6; + break; + default: + MSG("ERROR: [up] lora packet with unknown bandwidth\n"); + memcpy((void *)(buff_up + buff_index), (void *)"BW?\"", 4); + buff_index += 4; + exit(EXIT_FAILURE); + } + + /* Packet ECC coding rate, 11-13 useful chars */ + switch (p->coderate) { + case CR_LORA_4_5: + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/5\"", 13); + buff_index += 13; + break; + case CR_LORA_4_6: + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/6\"", 13); + buff_index += 13; + break; + case CR_LORA_4_7: + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/7\"", 13); + buff_index += 13; + break; + case CR_LORA_4_8: + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"4/8\"", 13); + buff_index += 13; + break; + case 0: /* treat the CR0 case (mostly false sync) */ + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"OFF\"", 13); + buff_index += 13; + break; + default: + MSG("ERROR: [up] lora packet with unknown coderate\n"); + memcpy((void *)(buff_up + buff_index), (void *)",\"codr\":\"?\"", 11); + buff_index += 11; + exit(EXIT_FAILURE); + } + + /* Lora SNR, 11-13 useful chars */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"lsnr\":%.1f", p->snr); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + } else if (p->modulation == MOD_FSK) { + memcpy((void *)(buff_up + buff_index), (void *)",\"modu\":\"FSK\"", 13); + buff_index += 13; + + /* FSK datarate, 11-14 useful chars */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"datr\":%u", p->datarate); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + } else { + MSG("ERROR: [up] received packet with unknown modulation\n"); + exit(EXIT_FAILURE); + } + + /* Packet RSSI, payload size, 18-23 useful chars */ + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"rssi\":%.0f,\"size\":%u", p->rssi, p->size); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + + /* Packet base64-encoded payload, 14-350 useful chars */ + memcpy((void *)(buff_up + buff_index), (void *)",\"data\":\"", 9); + buff_index += 9; + j = bin_to_b64(p->payload, p->size, (char *)(buff_up + buff_index), 341); /* 255 bytes = 340 chars in b64 + null char */ + if (j>=0) { + buff_index += j; + } else { + MSG("ERROR: [up] bin_to_b64 failed line %u\n", (__LINE__ - 5)); + exit(EXIT_FAILURE); + } + buff_up[buff_index] = '"'; + ++buff_index; + + /* End of packet serialization */ + buff_up[buff_index] = '}'; + ++buff_index; + ++pkt_in_dgram; + } + + /* restart fetch sequence without sending empty JSON if all packets have been filtered out */ + if (pkt_in_dgram == 0) { + if (send_report == true) { + /* need to clean up the beginning of the payload */ + buff_index -= 8; /* removes "rxpk":[ */ + } else { + /* all packet have been filtered out and no report, restart loop */ + continue; + } + } else { + /* end of packet array */ + buff_up[buff_index] = ']'; + ++buff_index; + /* add separator if needed */ + if (send_report == true) { + buff_up[buff_index] = ','; + ++buff_index; + } + } + + /* add status report if a new one is available */ + if (send_report == true) { + pthread_mutex_lock(&mx_stat_rep); + report_ready = false; + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, "%s", status_report); + pthread_mutex_unlock(&mx_stat_rep); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 5)); + exit(EXIT_FAILURE); + } + } + + /* end of JSON datagram payload */ + buff_up[buff_index] = '}'; + ++buff_index; + buff_up[buff_index] = 0; /* add string terminator, for safety */ + + printf("\nJSON up: %s\n", (char *)(buff_up + 12)); /* DEBUG: display JSON payload */ + + /* send datagram to server */ + send(sock_up, (void *)buff_up, buff_index, 0); + clock_gettime(CLOCK_MONOTONIC, &send_time); + pthread_mutex_lock(&mx_meas_up); + meas_up_dgram_sent += 1; + meas_up_network_byte += buff_index; + + /* wait for acknowledge (in 2 times, to catch extra packets) */ + for (i=0; i<2; ++i) { + j = recv(sock_up, (void *)buff_ack, sizeof buff_ack, 0); + clock_gettime(CLOCK_MONOTONIC, &recv_time); + if (j == -1) { + if (errno == EAGAIN) { /* timeout */ + continue; + } else { /* server connection error */ + break; + } + } else if ((j < 4) || (buff_ack[0] != PROTOCOL_VERSION) || (buff_ack[3] != PKT_PUSH_ACK)) { + //MSG("WARNING: [up] ignored invalid non-ACL packet\n"); + continue; + } else if ((buff_ack[1] != token_h) || (buff_ack[2] != token_l)) { + //MSG("WARNING: [up] ignored out-of sync ACK packet\n"); + continue; + } else { + MSG("INFO: [up] PUSH_ACK received in %i ms\n", (int)(1000 * difftimespec(recv_time, send_time))); + meas_up_ack_rcv += 1; + break; + } + } + pthread_mutex_unlock(&mx_meas_up); + } + MSG("\nINFO: End of upstream thread\n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- THREAD 2: POLLING SERVER AND ENQUEUING PACKETS IN JIT QUEUE ---------- */ + +void thread_down(void) { + int i; /* loop variables */ + + /* configuration and metadata for an outbound packet */ + struct lgw_pkt_tx_s txpkt; + bool sent_immediate = false; /* option to sent the packet immediately */ + + /* local timekeeping variables */ + struct timespec send_time; /* time of the pull request */ + struct timespec recv_time; /* time of return from recv socket call */ + + /* data buffers */ + uint8_t buff_down[1000]; /* buffer to receive downstream packets */ + uint8_t buff_req[12]; /* buffer to compose pull requests */ + int msg_len; + + /* protocol variables */ + uint8_t token_h; /* random token for acknowledgement matching */ + uint8_t token_l; /* random token for acknowledgement matching */ + bool req_ack = false; /* keep track of whether PULL_DATA was acknowledged or not */ + + /* JSON parsing variables */ + JSON_Value *root_val = NULL; + JSON_Object *txpk_obj = NULL; + JSON_Value *val = NULL; /* needed to detect the absence of some fields */ + const char *str; /* pointer to sub-strings in the JSON data */ + short x0, x1; + uint64_t x2; + double x3, x4; + + /* variables to send on GPS timestamp */ + struct tref local_ref; /* time reference used for GPS <-> timestamp conversion */ + struct timespec gps_tx; /* GPS time that needs to be converted to timestamp */ + + /* beacon variables */ + struct lgw_pkt_tx_s beacon_pkt; + uint8_t beacon_chan; + uint8_t beacon_loop; + size_t beacon_RFU1_size = 0; + size_t beacon_RFU2_size = 0; + uint8_t beacon_pyld_idx = 0; + time_t diff_beacon_time; + struct timespec next_beacon_gps_time; /* gps time of next beacon packet */ + struct timespec last_beacon_gps_time; /* gps time of last enqueued beacon packet */ + int retry; + + /* beacon data fields, byte 0 is Least Significant Byte */ + int32_t field_latitude; /* 3 bytes, derived from reference latitude */ + int32_t field_longitude; /* 3 bytes, derived from reference longitude */ + uint16_t field_crc1, field_crc2; + + /* auto-quit variable */ + uint32_t autoquit_cnt = 0; /* count the number of PULL_DATA sent since the latest PULL_ACK */ + + /* Just In Time downlink */ + struct timeval current_unix_time; + struct timeval current_concentrator_time; + enum jit_error_e jit_result = JIT_ERROR_OK; + enum jit_pkt_type_e downlink_type; + + /* set downstream socket RX timeout */ + i = setsockopt(sock_down, SOL_SOCKET, SO_RCVTIMEO, (void *)&pull_timeout, sizeof pull_timeout); + if (i != 0) { + MSG("ERROR: [down] setsockopt returned %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + + /* pre-fill the pull request buffer with fixed fields */ + buff_req[0] = PROTOCOL_VERSION; + buff_req[3] = PKT_PULL_DATA; + *(uint32_t *)(buff_req + 4) = net_mac_h; + *(uint32_t *)(buff_req + 8) = net_mac_l; + + /* beacon variables initialization */ + last_beacon_gps_time.tv_sec = 0; + last_beacon_gps_time.tv_nsec = 0; + + /* beacon packet parameters */ + beacon_pkt.tx_mode = ON_GPS; /* send on PPS pulse */ + beacon_pkt.rf_chain = 0; /* antenna A */ + beacon_pkt.rf_power = beacon_power; + beacon_pkt.modulation = MOD_LORA; + switch (beacon_bw_hz) { + case 125000: + beacon_pkt.bandwidth = BW_125KHZ; + break; + case 500000: + beacon_pkt.bandwidth = BW_500KHZ; + break; + default: + /* should not happen */ + MSG("ERROR: unsupported bandwidth for beacon\n"); + exit(EXIT_FAILURE); + } + switch (beacon_datarate) { + case 8: + beacon_pkt.datarate = DR_LORA_SF8; + beacon_RFU1_size = 1; + beacon_RFU2_size = 3; + break; + case 9: + beacon_pkt.datarate = DR_LORA_SF9; + beacon_RFU1_size = 2; + beacon_RFU2_size = 0; + break; + case 10: + beacon_pkt.datarate = DR_LORA_SF10; + beacon_RFU1_size = 3; + beacon_RFU2_size = 1; + break; + case 12: + beacon_pkt.datarate = DR_LORA_SF12; + beacon_RFU1_size = 5; + beacon_RFU2_size = 3; + break; + default: + /* should not happen */ + MSG("ERROR: unsupported datarate for beacon\n"); + exit(EXIT_FAILURE); + } + beacon_pkt.size = beacon_RFU1_size + 4 + 2 + 7 + beacon_RFU2_size + 2; + beacon_pkt.coderate = CR_LORA_4_5; + beacon_pkt.invert_pol = false; + beacon_pkt.preamble = 10; + beacon_pkt.no_crc = true; + beacon_pkt.no_header = true; + + /* network common part beacon fields (little endian) */ + for (i = 0; i < (int)beacon_RFU1_size; i++) { + beacon_pkt.payload[beacon_pyld_idx++] = 0x0; + } + + /* network common part beacon fields (little endian) */ + beacon_pyld_idx += 4; /* time (variable), filled later */ + beacon_pyld_idx += 2; /* crc1 (variable), filled later */ + + /* calculate the latitude and longitude that must be publicly reported */ + field_latitude = (int32_t)((reference_coord.lat / 90.0) * (double)(1<<23)); + if (field_latitude > (int32_t)0x007FFFFF) { + field_latitude = (int32_t)0x007FFFFF; /* +90 N is represented as 89.99999 N */ + } else if (field_latitude < (int32_t)0xFF800000) { + field_latitude = (int32_t)0xFF800000; + } + field_longitude = (int32_t)((reference_coord.lon / 180.0) * (double)(1<<23)); + if (field_longitude > (int32_t)0x007FFFFF) { + field_longitude = (int32_t)0x007FFFFF; /* +180 E is represented as 179.99999 E */ + } else if (field_longitude < (int32_t)0xFF800000) { + field_longitude = (int32_t)0xFF800000; + } + + /* gateway specific beacon fields */ + beacon_pkt.payload[beacon_pyld_idx++] = beacon_infodesc; + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_latitude; + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_latitude >> 8); + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_latitude >> 16); + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_longitude; + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_longitude >> 8); + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_longitude >> 16); + + /* RFU */ + for (i = 0; i < (int)beacon_RFU2_size; i++) { + beacon_pkt.payload[beacon_pyld_idx++] = 0x0; + } + + /* CRC of the beacon gateway specific part fields */ + field_crc2 = crc16((beacon_pkt.payload + 6 + beacon_RFU1_size), 7 + beacon_RFU2_size); + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_crc2; + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_crc2 >> 8); + + /* JIT queue initialization */ + jit_queue_init(&jit_queue); + + while (!exit_sig && !quit_sig) { + + /* auto-quit if the threshold is crossed */ + if ((autoquit_threshold > 0) && (autoquit_cnt >= autoquit_threshold)) { + exit_sig = true; + MSG("INFO: [down] the last %u PULL_DATA were not ACKed, exiting application\n", autoquit_threshold); + break; + } + + /* generate random token for request */ + token_h = (uint8_t)rand(); /* random token */ + token_l = (uint8_t)rand(); /* random token */ + buff_req[1] = token_h; + buff_req[2] = token_l; + + /* send PULL request and record time */ + send(sock_down, (void *)buff_req, sizeof buff_req, 0); + clock_gettime(CLOCK_MONOTONIC, &send_time); + pthread_mutex_lock(&mx_meas_dw); + meas_dw_pull_sent += 1; + pthread_mutex_unlock(&mx_meas_dw); + req_ack = false; + autoquit_cnt++; + + /* listen to packets and process them until a new PULL request must be sent */ + recv_time = send_time; + while ((int)difftimespec(recv_time, send_time) < keepalive_time) { + + /* try to receive a datagram */ + msg_len = recv(sock_down, (void *)buff_down, (sizeof buff_down)-1, 0); + clock_gettime(CLOCK_MONOTONIC, &recv_time); + + /* Pre-allocate beacon slots in JiT queue, to check downlink collisions */ + beacon_loop = JIT_NUM_BEACON_IN_QUEUE - jit_queue.num_beacon; + retry = 0; + while (beacon_loop && (beacon_period != 0)) { + pthread_mutex_lock(&mx_timeref); + /* Wait for GPS to be ready before inserting beacons in JiT queue */ + if ((gps_ref_valid == true) && (xtal_correct_ok == true)) { + + /* compute GPS time for next beacon to come */ + /* LoRaWAN: T = k*beacon_period + TBeaconDelay */ + /* with TBeaconDelay = [1.5ms +/- 1µs]*/ + if (last_beacon_gps_time.tv_sec == 0) { + /* if no beacon has been queued, get next slot from current GPS time */ + diff_beacon_time = time_reference_gps.gps.tv_sec % ((time_t)beacon_period); + next_beacon_gps_time.tv_sec = time_reference_gps.gps.tv_sec + + ((time_t)beacon_period - diff_beacon_time); + } else { + /* if there is already a beacon, take it as reference */ + next_beacon_gps_time.tv_sec = last_beacon_gps_time.tv_sec + beacon_period; + } + /* now we can add a beacon_period to the reference to get next beacon GPS time */ + next_beacon_gps_time.tv_sec += (retry * beacon_period); + next_beacon_gps_time.tv_nsec = 0; + +#if DEBUG_BEACON + { + time_t time_unix; + + time_unix = time_reference_gps.gps.tv_sec + UNIX_GPS_EPOCH_OFFSET; + MSG_DEBUG(DEBUG_BEACON, "GPS-now : %s", ctime(&time_unix)); + time_unix = last_beacon_gps_time.tv_sec + UNIX_GPS_EPOCH_OFFSET; + MSG_DEBUG(DEBUG_BEACON, "GPS-last: %s", ctime(&time_unix)); + time_unix = next_beacon_gps_time.tv_sec + UNIX_GPS_EPOCH_OFFSET; + MSG_DEBUG(DEBUG_BEACON, "GPS-next: %s", ctime(&time_unix)); + } +#endif + + /* convert GPS time to concentrator time, and set packet counter for JiT trigger */ + lgw_gps2cnt(time_reference_gps, next_beacon_gps_time, &(beacon_pkt.count_us)); + pthread_mutex_unlock(&mx_timeref); + + /* apply frequency correction to beacon TX frequency */ + if (beacon_freq_nb > 1) { + beacon_chan = (next_beacon_gps_time.tv_sec / beacon_period) % beacon_freq_nb; /* floor rounding */ + } else { + beacon_chan = 0; + } + /* Compute beacon frequency */ + beacon_pkt.freq_hz = beacon_freq_hz + (beacon_chan * beacon_freq_step); + + /* load time in beacon payload */ + beacon_pyld_idx = beacon_RFU1_size; + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & next_beacon_gps_time.tv_sec; + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 8); + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 16); + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (next_beacon_gps_time.tv_sec >> 24); + + /* calculate CRC */ + field_crc1 = crc16(beacon_pkt.payload, 4 + beacon_RFU1_size); /* CRC for the network common part */ + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & field_crc1; + beacon_pkt.payload[beacon_pyld_idx++] = 0xFF & (field_crc1 >> 8); + + /* Insert beacon packet in JiT queue */ + gettimeofday(¤t_unix_time, NULL); + get_concentrator_time(¤t_concentrator_time, current_unix_time); + jit_result = jit_enqueue(&jit_queue, ¤t_concentrator_time, &beacon_pkt, JIT_PKT_TYPE_BEACON); + if (jit_result == JIT_ERROR_OK) { + /* update stats */ + pthread_mutex_lock(&mx_meas_dw); + meas_nb_beacon_queued += 1; + pthread_mutex_unlock(&mx_meas_dw); + + /* One more beacon in the queue */ + beacon_loop--; + retry = 0; + last_beacon_gps_time.tv_sec = next_beacon_gps_time.tv_sec; /* keep this beacon time as reference for next one to be programmed */ + + /* display beacon payload */ + MSG("INFO: Beacon queued (count_us=%u, freq_hz=%u, size=%u):\n", beacon_pkt.count_us, beacon_pkt.freq_hz, beacon_pkt.size); + printf( " => " ); + for (i = 0; i < beacon_pkt.size; ++i) { + MSG("%02X ", beacon_pkt.payload[i]); + } + MSG("\n"); + } else { + MSG_DEBUG(DEBUG_BEACON, "--> beacon queuing failed with %d\n", jit_result); + /* update stats */ + pthread_mutex_lock(&mx_meas_dw); + if (jit_result != JIT_ERROR_COLLISION_BEACON) { + meas_nb_beacon_rejected += 1; + } + pthread_mutex_unlock(&mx_meas_dw); + /* In case previous enqueue failed, we retry one period later until it succeeds */ + /* Note: In case the GPS has been unlocked for a while, there can be lots of retries */ + /* to be done from last beacon time to a new valid one */ + retry++; + MSG_DEBUG(DEBUG_BEACON, "--> beacon queuing retry=%d\n", retry); + } + } else { + pthread_mutex_unlock(&mx_timeref); + break; + } + } + + /* if no network message was received, got back to listening sock_down socket */ + if (msg_len == -1) { + //MSG("WARNING: [down] recv returned %s\n", strerror(errno)); /* too verbose */ + continue; + } + + /* if the datagram does not respect protocol, just ignore it */ + if ((msg_len < 4) || (buff_down[0] != PROTOCOL_VERSION) || ((buff_down[3] != PKT_PULL_RESP) && (buff_down[3] != PKT_PULL_ACK))) { + MSG("WARNING: [down] ignoring invalid packet len=%d, protocol_version=%d, id=%d\n", + msg_len, buff_down[0], buff_down[3]); + continue; + } + + /* if the datagram is an ACK, check token */ + if (buff_down[3] == PKT_PULL_ACK) { + if ((buff_down[1] == token_h) && (buff_down[2] == token_l)) { + if (req_ack) { + MSG("INFO: [down] duplicate ACK received :)\n"); + } else { /* if that packet was not already acknowledged */ + req_ack = true; + autoquit_cnt = 0; + pthread_mutex_lock(&mx_meas_dw); + meas_dw_ack_rcv += 1; + pthread_mutex_unlock(&mx_meas_dw); + MSG("INFO: [down] PULL_ACK received in %i ms\n", (int)(1000 * difftimespec(recv_time, send_time))); + } + } else { /* out-of-sync token */ + MSG("INFO: [down] received out-of-sync ACK\n"); + } + continue; + } + + /* the datagram is a PULL_RESP */ + buff_down[msg_len] = 0; /* add string terminator, just to be safe */ + MSG("INFO: [down] PULL_RESP received - token[%d:%d] :)\n", buff_down[1], buff_down[2]); /* very verbose */ + printf("\nJSON down: %s\n", (char *)(buff_down + 4)); /* DEBUG: display JSON payload */ + + /* initialize TX struct and try to parse JSON */ + memset(&txpkt, 0, sizeof txpkt); + root_val = json_parse_string_with_comments((const char *)(buff_down + 4)); /* JSON offset */ + if (root_val == NULL) { + MSG("WARNING: [down] invalid JSON, TX aborted\n"); + continue; + } + + /* look for JSON sub-object 'txpk' */ + txpk_obj = json_object_get_object(json_value_get_object(root_val), "txpk"); + if (txpk_obj == NULL) { + MSG("WARNING: [down] no \"txpk\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + + /* Parse "immediate" tag, or target timestamp, or UTC time to be converted by GPS (mandatory) */ + i = json_object_get_boolean(txpk_obj,"imme"); /* can be 1 if true, 0 if false, or -1 if not a JSON boolean */ + if (i == 1) { + /* TX procedure: send immediately */ + sent_immediate = true; + downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_C; + MSG("INFO: [down] a packet will be sent in \"immediate\" mode\n"); + } else { + sent_immediate = false; + val = json_object_get_value(txpk_obj,"tmst"); + if (val != NULL) { + /* TX procedure: send on timestamp value */ + txpkt.count_us = (uint32_t)json_value_get_number(val); + + /* Concentrator timestamp is given, we consider it is a Class A downlink */ + downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_A; + } else { + /* TX procedure: send on GPS time (converted to timestamp value) */ + val = json_object_get_value(txpk_obj, "tmms"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.tmst\" or \"txpk.tmms\" objects in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + if (gps_enabled == true) { + pthread_mutex_lock(&mx_timeref); + if (gps_ref_valid == true) { + local_ref = time_reference_gps; + pthread_mutex_unlock(&mx_timeref); + } else { + pthread_mutex_unlock(&mx_timeref); + MSG("WARNING: [down] no valid GPS time reference yet, impossible to send packet on specific GPS time, TX aborted\n"); + json_value_free(root_val); + + /* send acknoledge datagram to server */ + send_tx_ack(buff_down[1], buff_down[2], JIT_ERROR_GPS_UNLOCKED); + continue; + } + } else { + MSG("WARNING: [down] GPS disabled, impossible to send packet on specific GPS time, TX aborted\n"); + json_value_free(root_val); + + /* send acknoledge datagram to server */ + send_tx_ack(buff_down[1], buff_down[2], JIT_ERROR_GPS_UNLOCKED); + continue; + } + + /* Get GPS time from JSON */ + x2 = (uint64_t)json_value_get_number(val); + + /* Convert GPS time from milliseconds to timespec */ + x3 = modf((double)x2/1E3, &x4); + gps_tx.tv_sec = (time_t)x4; /* get seconds from integer part */ + gps_tx.tv_nsec = (long)(x3 * 1E9); /* get nanoseconds from fractional part */ + + /* transform GPS time to timestamp */ + i = lgw_gps2cnt(local_ref, gps_tx, &(txpkt.count_us)); + if (i != LGW_GPS_SUCCESS) { + MSG("WARNING: [down] could not convert GPS time to timestamp, TX aborted\n"); + json_value_free(root_val); + continue; + } else { + MSG("INFO: [down] a packet will be sent on timestamp value %u (calculated from GPS time)\n", txpkt.count_us); + } + + /* GPS timestamp is given, we consider it is a Class B downlink */ + downlink_type = JIT_PKT_TYPE_DOWNLINK_CLASS_B; + } + } + + /* Parse "No CRC" flag (optional field) */ + val = json_object_get_value(txpk_obj,"ncrc"); + if (val != NULL) { + txpkt.no_crc = (bool)json_value_get_boolean(val); + } + + /* parse target frequency (mandatory) */ + val = json_object_get_value(txpk_obj,"freq"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.freq\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + txpkt.freq_hz = (uint32_t)((double)(1.0e6) * json_value_get_number(val)); + + /* parse RF chain used for TX (mandatory) */ + val = json_object_get_value(txpk_obj,"rfch"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.rfch\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + txpkt.rf_chain = (uint8_t)json_value_get_number(val); + + /* parse TX power (optional field) */ + val = json_object_get_value(txpk_obj,"powe"); + if (val != NULL) { + txpkt.rf_power = (int8_t)json_value_get_number(val) - antenna_gain; + } + + /* Parse modulation (mandatory) */ + str = json_object_get_string(txpk_obj, "modu"); + if (str == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.modu\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + if (strcmp(str, "LORA") == 0) { + /* Lora modulation */ + txpkt.modulation = MOD_LORA; + + /* Parse Lora spreading-factor and modulation bandwidth (mandatory) */ + str = json_object_get_string(txpk_obj, "datr"); + if (str == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.datr\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + i = sscanf(str, "SF%2hdBW%3hd", &x0, &x1); + if (i != 2) { + MSG("WARNING: [down] format error in \"txpk.datr\", TX aborted\n"); + json_value_free(root_val); + continue; + } + switch (x0) { + case 7: txpkt.datarate = DR_LORA_SF7; break; + case 8: txpkt.datarate = DR_LORA_SF8; break; + case 9: txpkt.datarate = DR_LORA_SF9; break; + case 10: txpkt.datarate = DR_LORA_SF10; break; + case 11: txpkt.datarate = DR_LORA_SF11; break; + case 12: txpkt.datarate = DR_LORA_SF12; break; + default: + MSG("WARNING: [down] format error in \"txpk.datr\", invalid SF, TX aborted\n"); + json_value_free(root_val); + continue; + } + switch (x1) { + case 125: txpkt.bandwidth = BW_125KHZ; break; + case 250: txpkt.bandwidth = BW_250KHZ; break; + case 500: txpkt.bandwidth = BW_500KHZ; break; + default: + MSG("WARNING: [down] format error in \"txpk.datr\", invalid BW, TX aborted\n"); + json_value_free(root_val); + continue; + } + + /* Parse ECC coding rate (optional field) */ + str = json_object_get_string(txpk_obj, "codr"); + if (str == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.codr\" object in json, TX aborted\n"); + json_value_free(root_val); + continue; + } + if (strcmp(str, "4/5") == 0) txpkt.coderate = CR_LORA_4_5; + else if (strcmp(str, "4/6") == 0) txpkt.coderate = CR_LORA_4_6; + else if (strcmp(str, "2/3") == 0) txpkt.coderate = CR_LORA_4_6; + else if (strcmp(str, "4/7") == 0) txpkt.coderate = CR_LORA_4_7; + else if (strcmp(str, "4/8") == 0) txpkt.coderate = CR_LORA_4_8; + else if (strcmp(str, "1/2") == 0) txpkt.coderate = CR_LORA_4_8; + else { + MSG("WARNING: [down] format error in \"txpk.codr\", TX aborted\n"); + json_value_free(root_val); + continue; + } + + /* Parse signal polarity switch (optional field) */ + val = json_object_get_value(txpk_obj,"ipol"); + if (val != NULL) { + txpkt.invert_pol = (bool)json_value_get_boolean(val); + } + + /* parse Lora preamble length (optional field, optimum min value enforced) */ + val = json_object_get_value(txpk_obj,"prea"); + if (val != NULL) { + i = (int)json_value_get_number(val); + if (i >= MIN_LORA_PREAMB) { + txpkt.preamble = (uint16_t)i; + } else { + txpkt.preamble = (uint16_t)MIN_LORA_PREAMB; + } + } else { + txpkt.preamble = (uint16_t)STD_LORA_PREAMB; + } + + } else if (strcmp(str, "FSK") == 0) { + /* FSK modulation */ + txpkt.modulation = MOD_FSK; + + /* parse FSK bitrate (mandatory) */ + val = json_object_get_value(txpk_obj,"datr"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.datr\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + txpkt.datarate = (uint32_t)(json_value_get_number(val)); + + /* parse frequency deviation (mandatory) */ + val = json_object_get_value(txpk_obj,"fdev"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.fdev\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + txpkt.f_dev = (uint8_t)(json_value_get_number(val) / 1000.0); /* JSON value in Hz, txpkt.f_dev in kHz */ + + /* parse FSK preamble length (optional field, optimum min value enforced) */ + val = json_object_get_value(txpk_obj,"prea"); + if (val != NULL) { + i = (int)json_value_get_number(val); + if (i >= MIN_FSK_PREAMB) { + txpkt.preamble = (uint16_t)i; + } else { + txpkt.preamble = (uint16_t)MIN_FSK_PREAMB; + } + } else { + txpkt.preamble = (uint16_t)STD_FSK_PREAMB; + } + + } else { + MSG("WARNING: [down] invalid modulation in \"txpk.modu\", TX aborted\n"); + json_value_free(root_val); + continue; + } + + /* Parse payload length (mandatory) */ + val = json_object_get_value(txpk_obj,"size"); + if (val == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.size\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + txpkt.size = (uint16_t)json_value_get_number(val); + + /* Parse payload data (mandatory) */ + str = json_object_get_string(txpk_obj, "data"); + if (str == NULL) { + MSG("WARNING: [down] no mandatory \"txpk.data\" object in JSON, TX aborted\n"); + json_value_free(root_val); + continue; + } + i = b64_to_bin(str, strlen(str), txpkt.payload, sizeof txpkt.payload); + if (i != txpkt.size) { + MSG("WARNING: [down] mismatch between .size and .data size once converter to binary\n"); + } + + /* free the JSON parse tree from memory */ + json_value_free(root_val); + + /* select TX mode */ + if (sent_immediate) { + txpkt.tx_mode = IMMEDIATE; + } else { + txpkt.tx_mode = TIMESTAMPED; + } + + /* record measurement data */ + pthread_mutex_lock(&mx_meas_dw); + meas_dw_dgram_rcv += 1; /* count only datagrams with no JSON errors */ + meas_dw_network_byte += msg_len; /* meas_dw_network_byte */ + meas_dw_payload_byte += txpkt.size; + pthread_mutex_unlock(&mx_meas_dw); + + /* check TX parameter before trying to queue packet */ + jit_result = JIT_ERROR_OK; + if ((txpkt.freq_hz < tx_freq_min[txpkt.rf_chain]) || (txpkt.freq_hz > tx_freq_max[txpkt.rf_chain])) { + jit_result = JIT_ERROR_TX_FREQ; + MSG("ERROR: Packet REJECTED, unsupported frequency - %u (min:%u,max:%u)\n", txpkt.freq_hz, tx_freq_min[txpkt.rf_chain], tx_freq_max[txpkt.rf_chain]); + } + if (jit_result == JIT_ERROR_OK) { + for (i=0; i -1) { + jit_result = jit_dequeue(&jit_queue, pkt_index, &pkt, &pkt_type); + if (jit_result == JIT_ERROR_OK) { + /* update beacon stats */ + if (pkt_type == JIT_PKT_TYPE_BEACON) { + /* Compensate breacon frequency with xtal error */ + pthread_mutex_lock(&mx_xcorr); + pkt.freq_hz = (uint32_t)(xtal_correct * (double)pkt.freq_hz); + MSG_DEBUG(DEBUG_BEACON, "beacon_pkt.freq_hz=%u (xtal_correct=%.15lf)\n", pkt.freq_hz, xtal_correct); + pthread_mutex_unlock(&mx_xcorr); + + /* Update statistics */ + pthread_mutex_lock(&mx_meas_dw); + meas_nb_beacon_sent += 1; + pthread_mutex_unlock(&mx_meas_dw); + MSG("INFO: Beacon dequeued (count_us=%u)\n", pkt.count_us); + } + + /* check if concentrator is free for sending new packet */ + pthread_mutex_lock(&mx_concent); /* may have to wait for a fetch to finish */ + result = lgw_status(TX_STATUS, &tx_status); + pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */ + if (result == LGW_HAL_ERROR) { + MSG("WARNING: [jit] lgw_status failed\n"); + } else { + if (tx_status == TX_EMITTING) { + MSG("ERROR: concentrator is currently emitting\n"); + print_tx_status(tx_status); + continue; + } else if (tx_status == TX_SCHEDULED) { + MSG("WARNING: a downlink was already scheduled, overwritting it...\n"); + print_tx_status(tx_status); + } else { + /* Nothing to do */ + } + } + + /* send packet to concentrator */ + pthread_mutex_lock(&mx_concent); /* may have to wait for a fetch to finish */ + result = lgw_send(pkt); + pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */ + if (result == LGW_HAL_ERROR) { + pthread_mutex_lock(&mx_meas_dw); + meas_nb_tx_fail += 1; + pthread_mutex_unlock(&mx_meas_dw); + MSG("WARNING: [jit] lgw_send failed\n"); + continue; + } else { + pthread_mutex_lock(&mx_meas_dw); + meas_nb_tx_ok += 1; + pthread_mutex_unlock(&mx_meas_dw); + MSG_DEBUG(DEBUG_PKT_FWD, "lgw_send done: count_us=%u\n", pkt.count_us); + } + } else { + MSG("ERROR: jit_dequeue failed with %d\n", jit_result); + } + } + } else if (jit_result == JIT_ERROR_EMPTY) { + /* Do nothing, it can happen */ + } else { + MSG("ERROR: jit_peek failed with %d\n", jit_result); + } + } +} + +/* -------------------------------------------------------------------------- */ +/* --- THREAD 4: PARSE GPS MESSAGE AND KEEP GATEWAY IN SYNC ----------------- */ + +static void gps_process_sync(void) { + struct timespec gps_time; + struct timespec utc; + uint32_t trig_tstamp; /* concentrator timestamp associated with PPM pulse */ + int i = lgw_gps_get(&utc, &gps_time, NULL, NULL); + + /* get GPS time for synchronization */ + if (i != LGW_GPS_SUCCESS) { + MSG("WARNING: [gps] could not get GPS time from GPS\n"); + return; + } + + /* get timestamp captured on PPM pulse */ + pthread_mutex_lock(&mx_concent); + i = lgw_get_trigcnt(&trig_tstamp); + pthread_mutex_unlock(&mx_concent); + if (i != LGW_HAL_SUCCESS) { + MSG("WARNING: [gps] failed to read concentrator timestamp\n"); + return; + } + + /* try to update time reference with the new GPS time & timestamp */ + pthread_mutex_lock(&mx_timeref); + i = lgw_gps_sync(&time_reference_gps, trig_tstamp, utc, gps_time); + pthread_mutex_unlock(&mx_timeref); + if (i != LGW_GPS_SUCCESS) { + MSG("WARNING: [gps] GPS out of sync, keeping previous time reference\n"); + } +} + +static void gps_process_coords(void) { + /* position variable */ + struct coord_s coord; + struct coord_s gpserr; + int i = lgw_gps_get(NULL, NULL, &coord, &gpserr); + + /* update gateway coordinates */ + pthread_mutex_lock(&mx_meas_gps); + if (i == LGW_GPS_SUCCESS) { + gps_coord_valid = true; + meas_gps_coord = coord; + meas_gps_err = gpserr; + // TODO: report other GPS statistics (typ. signal quality & integrity) + } else { + gps_coord_valid = false; + } + pthread_mutex_unlock(&mx_meas_gps); +} + +void thread_gps(void) { + /* serial variables */ + char serial_buff[128]; /* buffer to receive GPS data */ + size_t wr_idx = 0; /* pointer to end of chars in buffer */ + + /* variables for PPM pulse GPS synchronization */ + enum gps_msg latest_msg; /* keep track of latest NMEA message parsed */ + + /* initialize some variables before loop */ + memset(serial_buff, 0, sizeof serial_buff); + + while (!exit_sig && !quit_sig) { + size_t rd_idx = 0; + size_t frame_end_idx = 0; + + /* blocking non-canonical read on serial port */ + ssize_t nb_char = read(gps_tty_fd, serial_buff + wr_idx, LGW_GPS_MIN_MSG_SIZE); + if (nb_char <= 0) { + MSG("WARNING: [gps] read() returned value %d\n", nb_char); + continue; + } + wr_idx += (size_t)nb_char; + + /******************************************* + * Scan buffer for UBX/NMEA sync chars and * + * attempt to decode frame if one is found * + *******************************************/ + while(rd_idx < wr_idx) { + size_t frame_size = 0; + + /* Scan buffer for UBX sync char */ + if(serial_buff[rd_idx] == (char)LGW_GPS_UBX_SYNC_CHAR) { + + /*********************** + * Found UBX sync char * + ***********************/ + latest_msg = lgw_parse_ubx(&serial_buff[rd_idx], (wr_idx - rd_idx), &frame_size); + + if (frame_size > 0) { + if (latest_msg == INCOMPLETE) { + /* UBX header found but frame appears to be missing bytes */ + frame_size = 0; + } else if (latest_msg == INVALID) { + /* message header received but message appears to be corrupted */ + MSG("WARNING: [gps] could not get a valid message from GPS (no time)\n"); + frame_size = 0; + } else if (latest_msg == UBX_NAV_TIMEGPS) { + gps_process_sync(); + } + } + } else if(serial_buff[rd_idx] == LGW_GPS_NMEA_SYNC_CHAR) { + /************************ + * Found NMEA sync char * + ************************/ + /* scan for NMEA end marker (LF = 0x0a) */ + char* nmea_end_ptr = memchr(&serial_buff[rd_idx],(int)0x0a, (wr_idx - rd_idx)); + + if(nmea_end_ptr) { + /* found end marker */ + frame_size = nmea_end_ptr - &serial_buff[rd_idx] + 1; + latest_msg = lgw_parse_nmea(&serial_buff[rd_idx], frame_size); + + if(latest_msg == INVALID || latest_msg == UNKNOWN) { + /* checksum failed */ + frame_size = 0; + } else if (latest_msg == NMEA_RMC) { /* Get location from RMC frames */ + gps_process_coords(); + } + } + } + + if(frame_size > 0) { + /* At this point message is a checksum verified frame + we're processed or ignored. Remove frame from buffer */ + rd_idx += frame_size; + frame_end_idx = rd_idx; + } else { + rd_idx++; + } + } /* ...for(rd_idx = 0... */ + + if(frame_end_idx) { + /* Frames have been processed. Remove bytes to end of last processed frame */ + memcpy(serial_buff, &serial_buff[frame_end_idx], wr_idx - frame_end_idx); + wr_idx -= frame_end_idx; + } /* ...for(rd_idx = 0... */ + + /* Prevent buffer overflow */ + if((sizeof(serial_buff) - wr_idx) < LGW_GPS_MIN_MSG_SIZE) { + memcpy(serial_buff, &serial_buff[LGW_GPS_MIN_MSG_SIZE], wr_idx - LGW_GPS_MIN_MSG_SIZE); + wr_idx -= LGW_GPS_MIN_MSG_SIZE; + } + } + MSG("\nINFO: End of GPS thread\n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- THREAD 5: CHECK TIME REFERENCE AND CALCULATE XTAL CORRECTION --------- */ + +void thread_valid(void) { + + /* GPS reference validation variables */ + long gps_ref_age = 0; + bool ref_valid_local = false; + double xtal_err_cpy; + + /* variables for XTAL correction averaging */ + unsigned init_cpt = 0; + double init_acc = 0.0; + double x; + + /* correction debug */ + // FILE * log_file = NULL; + // time_t now_time; + // char log_name[64]; + + /* initialization */ + // time(&now_time); + // strftime(log_name,sizeof log_name,"xtal_err_%Y%m%dT%H%M%SZ.csv",localtime(&now_time)); + // log_file = fopen(log_name, "w"); + // setbuf(log_file, NULL); + // fprintf(log_file,"\"xtal_correct\",\"XERR_INIT_AVG %u XERR_FILT_COEF %u\"\n", XERR_INIT_AVG, XERR_FILT_COEF); // DEBUG + + /* main loop task */ + while (!exit_sig && !quit_sig) { + wait_ms(1000); + + /* calculate when the time reference was last updated */ + pthread_mutex_lock(&mx_timeref); + gps_ref_age = (long)difftime(time(NULL), time_reference_gps.systime); + if ((gps_ref_age >= 0) && (gps_ref_age <= GPS_REF_MAX_AGE)) { + /* time ref is ok, validate and */ + gps_ref_valid = true; + ref_valid_local = true; + xtal_err_cpy = time_reference_gps.xtal_err; + //printf("XTAL err: %.15lf (1/XTAL_err:%.15lf)\n", xtal_err_cpy, 1/xtal_err_cpy); // DEBUG + } else { + /* time ref is too old, invalidate */ + gps_ref_valid = false; + ref_valid_local = false; + } + pthread_mutex_unlock(&mx_timeref); + + /* manage XTAL correction */ + if (ref_valid_local == false) { + /* couldn't sync, or sync too old -> invalidate XTAL correction */ + pthread_mutex_lock(&mx_xcorr); + xtal_correct_ok = false; + xtal_correct = 1.0; + pthread_mutex_unlock(&mx_xcorr); + init_cpt = 0; + init_acc = 0.0; + } else { + if (init_cpt < XERR_INIT_AVG) { + /* initial accumulation */ + init_acc += xtal_err_cpy; + ++init_cpt; + } else if (init_cpt == XERR_INIT_AVG) { + /* initial average calculation */ + pthread_mutex_lock(&mx_xcorr); + xtal_correct = (double)(XERR_INIT_AVG) / init_acc; + //printf("XERR_INIT_AVG=%d, init_acc=%.15lf\n", XERR_INIT_AVG, init_acc); + xtal_correct_ok = true; + pthread_mutex_unlock(&mx_xcorr); + ++init_cpt; + // fprintf(log_file,"%.18lf,\"average\"\n", xtal_correct); // DEBUG + } else { + /* tracking with low-pass filter */ + x = 1 / xtal_err_cpy; + pthread_mutex_lock(&mx_xcorr); + xtal_correct = xtal_correct - xtal_correct/XERR_FILT_COEF + x/XERR_FILT_COEF; + pthread_mutex_unlock(&mx_xcorr); + // fprintf(log_file,"%.18lf,\"track\"\n", xtal_correct); // DEBUG + } + } + // printf("Time ref: %s, XTAL correct: %s (%.15lf)\n", ref_valid_local?"valid":"invalid", xtal_correct_ok?"valid":"invalid", xtal_correct); // DEBUG + } + MSG("\nINFO: End of validation thread\n"); +} + +/* --- EOF ------------------------------------------------------------------ */ -- cgit v1.2.3