/* / _____) _ | | ( (____ _____ ____ _| |_ _____ ____| |__ \____ \| ___ | (_ _) ___ |/ ___) _ \ _____) ) ____| | | || |_| ____( (___| | | | (______/|_____)_|_|_| \__)_____)\____)_| |_| (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 #include #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" #include "loragw_radio.h" #include "loragw_fpga.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 PKT_SPEC_SCAN 6 #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 50000 #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 #define DEFAULT_START 867000000 /* start frequency, Hz */ #define DEFAULT_STOP 870000000 /* stop frequency, Hz */ #define DEFAULT_STEP 1000000 /* frequency step, Hz */ #define DEFAULT_SAMPLES 65535 /* number of RSSI reads */ #define DEFAULT_CHAN_BW LGW_SX127X_RXBW_62K5_HZ /* channel bandwidth */ #define DEFAULT_SX127X_RSSI_OFFSET -120 #define RSSI_RANGE 256 #define MAX_FREQ 1000000000 #define MIN_FREQ 800000000 #define MIN_STEP 5000 #define FPGA_FEATURE_SPECTRAL_SCAN 1 #define FPGA_FEATURE_LBT 2 /* When FPGA supports LBT, there are few more constraints on above constants */ #define LBT_DEFAULT_SAMPLES 129*129 /* number of RSSI reads, hard-coded in FPGA*/ #define LBT_MIN_STEP 100000 #define HISTOGRAM_CLEAN_TIMEOUT 10000 #define HISTOGRAM_READY_TIMEOUT 1000 #define LGW_RECEIVE_TIMEOUT 100 #define MTS_IO_SYS "mts-io-sysfs store" #define LORA_PIN_LOW "/reset 0" #define LORA_PIN_HIGH "/reset 1" #define LORA_PATH_AP1 "/dev/spidev32766.2" #define LORA_PATH_AP2 "/dev/spidev32765.2" /* -------------------------------------------------------------------------- */ /* --- 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 */ static bool fwd_best_pkt = true; /* duplicate packets with low SNR 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 spi_device_path[64] = {0} ; /* custom SPI device path */ 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 */ static bool duty_cycle_enabled = false; static uint32_t duty_cycle_time_avail = 0; static uint32_t duty_cycle_period = 3600; // seconds in one hour static double duty_cycle_ratio = 0.10; // 10% static uint32_t duty_cycle_time_max = 3600 * 0.10 * 1000u; // max time-on-air in window static int max_tx_power = -99; // limit tx power sent from a network server /* 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 bool use_gps = false; /* Use the GPSD stream */ static bool gps_enabled = false; /* is GPS enabled on that gateway ? */ static struct gps_data_t gpsdata; static struct fixsource_t source; /* 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 */ static bool lbt_enabled = false; /* 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 */ static char serialized_string[STATUS_SIZE]; /* spectral scan parameters */ static pthread_mutex_t mx_scan_config = PTHREAD_MUTEX_INITIALIZER; /* control access to the spectral scan config */ static pthread_mutex_t mx_scan_report = PTHREAD_MUTEX_INITIALIZER; /* control access to the spectral scan config */ static bool scan_ready = false; /* true when there is a new report to send to the server */ struct scan_config_s { uint32_t init; uint32_t start; uint32_t stop; uint32_t step; uint16_t samples; int8_t rssi_floor; int8_t offset; uint32_t bandwidth; bool read; } scan_config, scan_config_new, scan_config_update; /* 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; #define TEMP_ADJ_MAX 4 #define DEFAULT_TEMP_COMP_FILE "/var/run/lora/current_temp" static int16_t temp_comp_table[TEMP_ADJ_MAX][2] = { 0 }; static char temp_comp_file[64] = {0}; static int16_t temp_comp_value = 0; static int16_t temp_comp_adj = 0; static bool temp_comp_enabled = false; /* 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 */ static uint32_t rx_rf_freq[LGW_RF_CHAIN_NB]; /* center frequency of the radio in Hz */ /* -------------------------------------------------------------------------- */ /* --- 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); void thread_spectralscan(void); /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ static void update_temp_comp_value() { if (!temp_comp_enabled) { return; } /* try to open file to read */ FILE *filePointer; if (filePointer = fopen(temp_comp_file, "r")) { int bufferLength = 4; char buffer[bufferLength]; fgets(buffer, bufferLength, filePointer); temp_comp_value = atoi(buffer); int16_t adj_val = 0; for (int i = 0; i < TEMP_ADJ_MAX; i++) { if (temp_comp_table[i][0] <= temp_comp_value) { adj_val = temp_comp_table[i][1]; } } temp_comp_adj = adj_val; fclose(filePointer); } } static enum lgw_sx127x_rxbw_e map_bandwidth(uint32_t bandwidth) { switch (bandwidth) { case 25: return LGW_SX127X_RXBW_12K5_HZ; case 50: return LGW_SX127X_RXBW_25K_HZ; case 100: return LGW_SX127X_RXBW_50K_HZ; case 125: return LGW_SX127X_RXBW_62K5_HZ; case 200: return LGW_SX127X_RXBW_100K_HZ; case 250: return LGW_SX127X_RXBW_125K_HZ; case 500: return LGW_SX127X_RXBW_250K_HZ; default: printf("ERROR: Failed to parse spectral scan bandwidth.\n"); return EXIT_FAILURE; } } static int reset_fpga(bool lbt_support, uint16_t samples) { int x; /* Reconnect to FPGA with sw reset and configure */ x = lgw_disconnect(); if(x != 0) { printf("ERROR: Failed to disconnect from FPGA\n"); return EXIT_FAILURE; } x = lgw_connect(false, LGW_DEFAULT_NOTCH_FREQ); /* FPGA reset/configure */ if(x != 0) { printf("ERROR: Failed to connect to FPGA\n"); return EXIT_FAILURE; } if (lbt_support == false) { x = lgw_fpga_reg_w(LGW_FPGA_HISTO_NB_READ, samples-1); if( x != LGW_REG_SUCCESS ) { printf( "ERROR: Failed to configure FPGA\n" ); return EXIT_FAILURE; } } return EXIT_SUCCESS; } static int setup_spectral_scan(bool *lbt_support, uint64_t *freq_reg, uint32_t *freq_nb) { int x; /* return code for functions */ int32_t reg_val; /* Check if FPGA supports Spectral Scan */ lgw_fpga_reg_r(LGW_FPGA_FEATURE, ®_val); if (TAKE_N_BITS_FROM((uint8_t)reg_val, FPGA_FEATURE_SPECTRAL_SCAN, 1) != true) { printf("ERROR: Spectral Scan is not supported (0x%x)\n", (uint8_t)reg_val); return EXIT_FAILURE; } /* Check if FPGA supports LBT, in order to apply proper constraints on spectral scan parameters */ lgw_fpga_reg_r(LGW_FPGA_FEATURE, ®_val); if (TAKE_N_BITS_FROM((uint8_t)reg_val, FPGA_FEATURE_LBT, 1) == true) { printf("WARNING: The FPGA supports LBT, so running spectral scan with specific constraints\n"); printf(" => Check the parameters summary below\n"); /* Get start frequency from FPGA */ lgw_fpga_reg_r(LGW_FPGA_LBT_INITIAL_FREQ, ®_val); switch (reg_val) { case 0: scan_config.init = 915000000; break; case 1: scan_config.init = 863000000; break; default: printf("ERROR: init frequency %d is not supported\n", reg_val); return EXIT_FAILURE; } /* Check parameters based on LBT constraints */ if (scan_config.start < scan_config.init) { printf("ERROR: start frequency %d is not supported, should be >=%d\n", scan_config.start, scan_config.init); return EXIT_FAILURE; } if (scan_config.stop > (scan_config.init + 255*LBT_MIN_STEP)) { printf("ERROR: stop frequency %d is not supported, should be <%d\n", scan_config.stop, scan_config.init + 255*LBT_MIN_STEP); return EXIT_FAILURE; } if (scan_config.step < LBT_MIN_STEP) { printf("ERROR: step frequency %d is not supported, should be >=%d\n", scan_config.step, LBT_MIN_STEP); return EXIT_FAILURE; } else { /* Ensure the given step is a multiple of LBT_MIN_STEP */ scan_config.step = (scan_config.step / LBT_MIN_STEP) * LBT_MIN_STEP; } /* Overload hard-coded spectral scan parameters */ scan_config.samples = LBT_DEFAULT_SAMPLES; /* Spectral scan sequence is slightly different depending if LBT is there or not */ *lbt_support = true; } else { /* Reconnect to FPGA with sw reset and configure */ x = lgw_disconnect(); if(x != 0) { printf("ERROR: Failed to disconnect from FPGA\n"); return EXIT_FAILURE; } x = lgw_connect(false, LGW_DEFAULT_NOTCH_FREQ); /* FPGA reset/configure */ if(x != 0) { printf("ERROR: Failed to connect to FPGA\n"); return EXIT_FAILURE; } /* Some spectral scan options are only available when there is no LBT support */ x = lgw_fpga_reg_w(LGW_FPGA_HISTO_NB_READ, scan_config.samples-1); if( x != LGW_REG_SUCCESS ) { printf( "ERROR: Failed to configure FPGA\n" ); return EXIT_FAILURE; } /* Initialize frequency */ *freq_reg = ((uint64_t)scan_config.start << 19) / (uint64_t)32000000; lgw_fpga_reg_w(LGW_FPGA_HISTO_SCAN_FREQ, (int32_t)freq_reg); } *freq_nb = (uint32_t)((scan_config.stop - scan_config.start) / scan_config.step) + 1; return 0; } 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_temp_comp_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; } if (fpga_supports_attenuator()) { val = json_object_get_value(conf_obj, "max_tx_power"); /* fetch value (if possible) */ if (json_value_get_type(val) == JSONNumber) { boardconf.max_tx_power = (uint8_t)json_value_get_number(val); } else { MSG("WARNING: Data for max_tx_power is invalid, must be an integer\n"); boardconf.max_tx_power = 32; } } /* 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) { lbt_enabled = 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); conf_temp_comp_obj = json_object_get_object(conf_obj, "temperature_comp"); /* fetch value (if possible) */ if (conf_temp_comp_obj == NULL) { MSG("INFO: no configuration for Temperature Compensation\n"); } else { val = json_object_get_value(conf_temp_comp_obj, "enable"); /* fetch value (if possible) */ if (json_value_get_type(val) == JSONBoolean) { temp_comp_enabled = (bool)json_value_get_boolean(val); } else { temp_comp_enabled = false; } if (temp_comp_enabled) { MSG("INFO: Temperature Compensation enabled\n"); /* Current temperature path (optional) */ str = json_object_get_string(conf_temp_comp_obj, "current_temp_file"); if (str != NULL) { strncpy(temp_comp_file, str, sizeof(temp_comp_file)-1); MSG("INFO: Current temperature file is configured to \"%s\"\n", temp_comp_file); } else { strncpy(temp_comp_file, DEFAULT_TEMP_COMP_FILE, sizeof(temp_comp_file)-1); } /* load temp adj table */ int adj_count = 0; conf_array = json_object_get_array(conf_temp_comp_obj, "values"); if (conf_array != NULL) { adj_count = json_array_get_count( conf_array ); JSON_Array *temp_values = NULL; for (i = 0; i < (int)adj_count; i++) { /* Sanity check */ if (i >= TEMP_ADJ_MAX) { MSG("ERROR: Temp adj %d not supported, skip it\n", i ); break; } /* Get LBT channel configuration object from array */ temp_values = json_array_get_array(conf_array, i); size_t cnt = json_array_get_count(temp_values); if (cnt == 2) { temp_comp_table[i][0] = (int16_t)json_array_get_number(temp_values, 0); temp_comp_table[i][1] = (int16_t)json_array_get_number(temp_values, 1); } } } update_temp_comp_value(); } else { MSG("INFO: Temperature Compensation disabled\n"); } } /* 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 = rx_rf_freq[i] = (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 %sabled (type %s), center frequency %u, RSSI offset %f, tx enabled %d, tx_notch_freq %u\n", i, (rfconf.enable?"en":"dis"), 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); } /* spi device path (optional) */ str = json_object_get_string(conf_obj, "spi_device"); if (str != NULL) { strncpy(spi_device_path, str, sizeof(spi_device_path)-1); MSG("INFO: SPI device is configured to \"%s\"\n", spi_device_path); } /* 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)); } /* duty-cycle limiting */ val = json_object_get_value(conf_obj, "duty_cycle_enabled"); if (json_value_get_type(val) == JSONBoolean) { duty_cycle_enabled = (bool)json_value_get_boolean(val); } MSG("INFO: duty cycle will%s be enforced\n", (duty_cycle_enabled ? "" : " NOT")); if (duty_cycle_enabled) { val = json_object_get_value(conf_obj, "duty_cycle_period"); if (val != NULL) { duty_cycle_period = (unsigned)json_value_get_number(val); } MSG("INFO: duty cycle period %u s\n", (duty_cycle_period)); val = json_object_get_value(conf_obj, "duty_cycle_ratio"); if (val != NULL) { duty_cycle_ratio = (double)json_value_get_number(val); } MSG("INFO: duty cycle %f %%\n", (duty_cycle_ratio * 100)); duty_cycle_time_max = duty_cycle_period * 1000u * duty_cycle_ratio; } /* max tx power */ val = json_object_get_value(conf_obj, "max_tx_power"); if (val != NULL) { max_tx_power = (int)json_value_get_number(val); MSG("INFO: max tx power %d\n", (max_tx_power)); } /* packet filtering parameters */ val = json_object_get_value(conf_obj, "best_packet_filter"); if (json_value_get_type(val) == JSONBoolean) { fwd_best_pkt = (bool)json_value_get_boolean(val); } MSG("INFO: duplicate packets received with low SNR will%s be forwarded\n", (!fwd_best_pkt ? "" : " NOT")); 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")); /* 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, "gps"); if (json_value_get_type(val) == JSONBoolean) { use_gps = (bool)json_value_get_boolean(val); if (use_gps == true) { MSG("INFO: GPS is enabled\n"); } else { MSG("INFO: GPS is disabled\n"); } } /* 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= 0) { switch(i) { case 0: break; case 'l': logfile_path = strdup(optarg); if (logfile_path == NULL) { printf("Error: can't save logfile name\n"); exit(1); } break; default: usage(proc_name); break; } } /* redirect stdout, stderr to logfile if specified */ int logfile_fd; FILE *logfile = NULL; if (logfile_path) { logfile = fopen(logfile_path, "a"); if (logfile) { logfile_fd = fileno(logfile); dup2(logfile_fd, STDOUT_FILENO); dup2(logfile_fd, STDERR_FILENO); } else { printf("Error opening log file %s\n", logfile_path); exit(1); } } /* network socket creation */ struct addrinfo hints; struct addrinfo *result; /* store result of getaddrinfo */ struct addrinfo *q; /* pointer to move into *result data */ char host_name[64]; char port_name[64]; /* variables to get local copies of measurements */ uint32_t cp_nb_rx_rcv; uint32_t cp_nb_rx_ok; uint32_t cp_nb_rx_bad; uint32_t cp_nb_rx_nocrc; uint32_t cp_up_pkt_fwd; uint32_t cp_up_network_byte; uint32_t cp_up_payload_byte; uint32_t cp_up_dgram_sent; uint32_t cp_up_ack_rcv; uint32_t cp_dw_pull_sent; uint32_t cp_dw_ack_rcv; uint32_t cp_dw_dgram_rcv; uint32_t cp_dw_network_byte; uint32_t cp_dw_payload_byte; uint32_t cp_nb_tx_ok; uint32_t cp_nb_tx_fail; uint32_t cp_nb_tx_requested = 0; uint32_t cp_nb_tx_rejected_collision_packet = 0; uint32_t cp_nb_tx_rejected_collision_beacon = 0; uint32_t cp_nb_tx_rejected_too_late = 0; uint32_t cp_nb_tx_rejected_too_early = 0; uint32_t cp_nb_beacon_queued = 0; uint32_t cp_nb_beacon_sent = 0; uint32_t cp_nb_beacon_rejected = 0; /* GPS coordinates variables */ bool coord_ok = false; struct coord_s cp_gps_coord = {0.0, 0.0, 0}; /* SX1301 data variables */ uint32_t trig_tstamp; /* statistics variable */ time_t t; char stat_timestamp[24]; float rx_ok_ratio; float rx_bad_ratio; float rx_nocrc_ratio; float up_ack_ratio; float dw_ack_ratio; /* display version informations */ MSG("*** Beacon Packet Forwarder for Lora Gateway ***\nVersion: " VERSION_STRING "\n"); MSG("*** Lora concentrator HAL library version info ***\n%s\n***\n", lgw_version_info()); /* display host endianness */ #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ MSG("INFO: Little endian host\n"); #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ MSG("INFO: Big endian host\n"); #else MSG("INFO: Host endianness unknown\n"); #endif /* load configuration files */ if (access(debug_cfg_path, R_OK) == 0) { /* if there is a debug conf, parse only the debug conf */ MSG("INFO: found debug configuration file %s, parsing it\n", debug_cfg_path); MSG("INFO: other configuration files will be ignored\n"); x = parse_SX1301_configuration(debug_cfg_path); if (x != 0) { exit(EXIT_FAILURE); } x = parse_gateway_configuration(debug_cfg_path); if (x != 0) { exit(EXIT_FAILURE); } } else if (access(global_cfg_path, R_OK) == 0) { /* if there is a global conf, parse it and then try to parse local conf */ MSG("INFO: found global configuration file %s, parsing it\n", global_cfg_path); x = parse_SX1301_configuration(global_cfg_path); if (x != 0) { exit(EXIT_FAILURE); } x = parse_gateway_configuration(global_cfg_path); if (x != 0) { exit(EXIT_FAILURE); } if (access(local_cfg_path, R_OK) == 0) { MSG("INFO: found local configuration file %s, parsing it\n", local_cfg_path); MSG("INFO: redefined parameters will overwrite global parameters\n"); parse_SX1301_configuration(local_cfg_path); parse_gateway_configuration(local_cfg_path); } } else if (access(local_cfg_path, R_OK) == 0) { /* if there is only a local conf, parse it and that's all */ MSG("INFO: found local configuration file %s, parsing it\n", local_cfg_path); x = parse_SX1301_configuration(local_cfg_path); if (x != 0) { exit(EXIT_FAILURE); } x = parse_gateway_configuration(local_cfg_path); if (x != 0) { exit(EXIT_FAILURE); } } else { MSG("ERROR: [main] failed to find any configuration file named %s, %s OR %s\n", global_cfg_path, local_cfg_path, debug_cfg_path); exit(EXIT_FAILURE); } /* Start GPS a.s.a.p., to allow it to lock */ if (use_gps == true) { int i = lgw_gps_enable(&gpsdata, &source); if (i != LGW_GPS_SUCCESS) { printf("WARNING: [main] impossible to open for GPS sync (Check GPSD)\n"); gps_enabled = false; gps_ref_valid = false; } else { printf("INFO: [main] GPSD polling open for GPS synchronization\n"); gps_enabled = true; gps_ref_valid = false; } } /* get timezone info */ tzset(); /* sanity check on configuration variables */ // TODO /* process some of the configuration variables */ net_mac_h = htonl((uint32_t)(0xFFFFFFFF & (lgwm>>32))); net_mac_l = htonl((uint32_t)(0xFFFFFFFF & lgwm )); /* prepare hints to open network sockets */ memset(&hints, 0, sizeof hints); hints.ai_family = AF_INET; /* WA: Forcing IPv4 as AF_UNSPEC makes connection on localhost to fail */ hints.ai_socktype = SOCK_DGRAM; /* look for server address w/ upstream port */ i = getaddrinfo(serv_addr, serv_port_up, &hints, &result); if (i != 0) { MSG("ERROR: [up] getaddrinfo on address %s (PORT %s) returned %s\n", serv_addr, serv_port_up, gai_strerror(i)); exit(EXIT_FAILURE); } /* try to open socket for upstream traffic */ for (q=result; q!=NULL; q=q->ai_next) { sock_up = socket(q->ai_family, q->ai_socktype,q->ai_protocol); if (sock_up == -1) continue; /* try next field */ else break; /* success, get out of loop */ } if (q == NULL) { MSG("ERROR: [up] failed to open socket to any of server %s addresses (port %s)\n", serv_addr, serv_port_up); i = 1; for (q=result; q!=NULL; q=q->ai_next) { getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); MSG("INFO: [up] result %i host:%s service:%s\n", i, host_name, port_name); ++i; } exit(EXIT_FAILURE); } /* connect so we can send/receive packet with the server only */ i = connect(sock_up, q->ai_addr, q->ai_addrlen); if (i != 0) { MSG("ERROR: [up] connect returned %s\n", strerror(errno)); exit(EXIT_FAILURE); } freeaddrinfo(result); /* look for server address w/ downstream port */ i = getaddrinfo(serv_addr, serv_port_down, &hints, &result); if (i != 0) { MSG("ERROR: [down] getaddrinfo on address %s (port %s) returned %s\n", serv_addr, serv_port_up, gai_strerror(i)); exit(EXIT_FAILURE); } /* try to open socket for downstream traffic */ for (q=result; q!=NULL; q=q->ai_next) { sock_down = socket(q->ai_family, q->ai_socktype,q->ai_protocol); if (sock_down == -1) continue; /* try next field */ else break; /* success, get out of loop */ } if (q == NULL) { MSG("ERROR: [down] failed to open socket to any of server %s addresses (port %s)\n", serv_addr, serv_port_up); i = 1; for (q=result; q!=NULL; q=q->ai_next) { getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); MSG("INFO: [down] result %i host:%s service:%s\n", i, host_name, port_name); ++i; } exit(EXIT_FAILURE); } /* connect so we can send/receive packet with the server only */ i = connect(sock_down, q->ai_addr, q->ai_addrlen); if (i != 0) { MSG("ERROR: [down] connect returned %s\n", strerror(errno)); exit(EXIT_FAILURE); } freeaddrinfo(result); char lora_port[5] = "lora"; /* path mapping for mts-io */ /* set custom SPI device path if configured */ if (strlen(spi_device_path) > 0) { if (strcmp(spi_device_path, LORA_PATH_AP1) == 0) { snprintf(lora_port, sizeof lora_port, "%s", "ap1"); } else if (strcmp(spi_device_path, LORA_PATH_AP2) == 0) { snprintf(lora_port, sizeof lora_port, "%s", "ap2"); } lgw_spi_set_path(spi_device_path); } /* 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, resetting %s and trying again\n", lora_port); char cmd_pin_low[64]; char cmd_pin_high[64]; snprintf(cmd_pin_low, sizeof cmd_pin_low, "%s %s%s", MTS_IO_SYS, lora_port, LORA_PIN_LOW); snprintf(cmd_pin_high, sizeof cmd_pin_high, "%s %s%s", MTS_IO_SYS, lora_port, LORA_PIN_HIGH); system(cmd_pin_low); wait_ms(250); system(cmd_pin_high); i = lgw_start(); if (i != LGW_HAL_SUCCESS) { MSG("ERROR: [main] concentrator cannot be started, exiting\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); } i = pthread_create( &thrid_spectralscan, NULL, (void * (*)(void *))thread_spectralscan, NULL); if (i != 0) { MSG("ERROR: [main] impossible to create spectral scan 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 */ sigemptyset(&sighupact.sa_mask); sighupact.sa_flags = 0; sighupact.sa_handler = sighup_handler; sigaction(SIGHUP, &sighupact, NULL); /* rotate logfile on HUP */ signal(SIGPIPE, SIG_IGN); /* ignore writes after closing socket */ /* 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"); if (duty_cycle_enabled) printf("# TIME ON AIR available: %u ms\n", duty_cycle_time_avail); 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); uint8_t page = 0; lgw_get_cur_page(&page); if (page != 2) { //quit rather than reset the page. We don't know if this was on purpose or not. exit_sig = true; MSG("WARNING: lgw page was unexpectedly changed, process is exiting.\n"); break; } /* read the currrent temperature */ pthread_mutex_unlock(&mx_concent); if (i != LGW_HAL_SUCCESS) { printf("# SX1301 time (PPS): unknown\n"); } else { printf("# SX1301 time (PPS): %u %u\n", trig_tstamp, page); } if (temp_comp_enabled) { update_temp_comp_value(); printf("# Temperature: %i C\n", temp_comp_value); printf("# Temp TxPow Adj: %i dB\n", temp_comp_adj); } 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 */ pthread_cancel(thrid_valid); /* don't wait for validation thread */ if (gps_enabled == true) { pthread_cancel(thrid_gps); /* don't wait for GPS thread */ i = lgw_gps_disable(&gpsdata); if (i == LGW_HAL_SUCCESS) { MSG("INFO: GPS closed successfully\n"); } else { MSG("WARNING: failed to close GPS successfully\n"); } } pthread_join(thrid_spectralscan, NULL); /* wait for spec scan thread */ /* if an exit signal was received, try to quit properly */ if (exit_sig) { /* shut down network sockets */ shutdown(sock_up, SHUT_RDWR); shutdown(sock_down, SHUT_RDWR); /* stop the hardware */ i = lgw_stop(); if (i == LGW_HAL_SUCCESS) { MSG("INFO: concentrator stopped successfully\n"); } else { MSG("WARNING: failed to stop concentrator successfully\n"); } } 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; uint8_t lgw_receive_counter = 0; /* 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) { if(lgw_receive_counter > LGW_RECEIVE_TIMEOUT) { MSG("ERROR: [up] lgw_receive_counter limit reached, exiting\n"); exit(EXIT_FAILURE); } /* 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, retrying\n"); lgw_receive_counter++; wait_ms(1000); } /* 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; if (fwd_best_pkt && nb_pkt > 1) { uint32_t check_addr = 0; uint32_t check_mic = 0; uint16_t check_fcnt = 0; float check_snr = -30.0; for (i=0; i < nb_pkt; ++i) { p = &rxpkt[i]; if (p->size < 12) continue; memcpy(&check_addr, p->payload + 1, 4); memcpy(&check_fcnt, p->payload + 6, 2); memcpy(&check_mic, p->payload + p->size - 4, 4); check_snr = p->snr; for (j=0; j < nb_pkt; ++j) { p = &rxpkt[j]; if (p->size >= 12 && memcmp(&check_addr, p->payload + 1, 4) == 0 && memcmp(&check_fcnt, p->payload + 6, 2) == 0 && memcmp(&check_mic, p->payload + p->size - 4, 4) == 0 && p->snr < check_snr) { // set status of duplicate packets rx'd on wrong channel p->status = 1; } } } } /* 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); } } if (scan_ready == true) { pthread_mutex_lock(&mx_scan_report); j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"spectral_scan\":%s", serialized_string); scan_ready = false; pthread_mutex_unlock(&mx_scan_report); if (j > 0) { buff_index += j; } else { MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 5)); exit(EXIT_FAILURE); } } else { pthread_mutex_unlock(&mx_scan_report); } /* 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_Object *specscan_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; time_t time_us = 0; 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)) { if (time_reference_gps.gps.tv_sec < last_beacon_gps_time.tv_sec + 128 || (time_reference_gps.gps.tv_sec - last_beacon_gps_time.tv_sec) > (time_t)(beacon_period * (JIT_NUM_BEACON_IN_QUEUE+1))) { // Incase system time reference has moved last_beacon_gps_time.tv_sec = 0; } /* 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); time_us = (current_concentrator_time.tv_sec * 1000000UL + current_concentrator_time.tv_usec); if (beacon_pkt.count_us == 0 || beacon_pkt.count_us < time_us || beacon_pkt.count_us - time_us < 1e6) { break; } 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) && (buff_down[3] != PKT_SPEC_SCAN))) { 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; } if (buff_down[3] == PKT_PULL_RESP || buff_down[3] == PKT_SPEC_SCAN) { /* 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"); specscan_obj = json_object_get_object(json_value_get_object(root_val), "scan"); if (txpk_obj == NULL && specscan_obj == NULL) { MSG("WARNING: [down] no \"txpk\" or \"scan\"object in JSON, TX aborted\n"); json_value_free(root_val); continue; } if (specscan_obj != NULL && read_fpga_version() >= 31) { bool valid_config = true; val = json_object_get_value(specscan_obj, "start"); /* fetch value (if possible) */ if (json_value_get_type(val) == JSONNumber) { scan_config_update.start = (uint32_t)json_value_get_number(val); } else { MSG("INFO: [down] start is not a number, ignoring scan config\n"); valid_config = false; } val = json_object_get_value(specscan_obj, "stop"); /* fetch value (if possible) */ if (json_value_get_type(val) == JSONNumber) { scan_config_update.stop = (uint32_t)json_value_get_number(val); } else { MSG("INFO: [down] stop is not a number, ignoring scan config\n"); valid_config = false; } val = json_object_get_value(specscan_obj, "step"); /* fetch value (if possible) */ if (json_value_get_type(val) == JSONNumber) { scan_config_update.step = (uint32_t)json_value_get_number(val); } else { MSG("INFO: [down] step is not a number, ignoring scan config\n"); valid_config = false; } val = json_object_get_value(specscan_obj, "samples"); /* fetch value (if possible) */ if (json_value_get_type(val) == JSONNumber) { scan_config_update.samples = (uint32_t)json_value_get_number(val); } else { MSG("INFO: [down] step is not a number, ignoring scan config\n"); valid_config = false; } val = json_object_get_value(specscan_obj, "offset"); /* fetch value (if possible) */ if (json_value_get_type(val) == JSONNumber) { scan_config_update.offset = (uint32_t)json_value_get_number(val); } else { MSG("INFO: [down] step is not a number, ignoring scan config\n"); valid_config = false; } val = json_object_get_value(specscan_obj, "bandwidth"); /* fetch value (if possible) */ if (json_value_get_type(val) == JSONNumber) { scan_config_update.bandwidth = (uint32_t)json_value_get_number(val); } else { MSG("INFO: [down] bandwidth is not a number, ignoring scan config\n"); valid_config = false; } scan_config_update.read = false; pthread_mutex_lock(&mx_scan_config); if (valid_config) { scan_config_new = scan_config_update; } pthread_mutex_unlock(&mx_scan_config); } if (txpk_obj == NULL) { 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 "No Header" flag (optional field) */ val = json_object_get_value(txpk_obj,"nhdr"); if (val != NULL) { txpkt.no_header = (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; if (max_tx_power != -99) { if (txpkt.rf_power > max_tx_power - antenna_gain) { MSG("INFO: [down] tx power reduced tx power: % dBm attn gain: %d dBi\n", max_tx_power, antenna_gain); txpkt.rf_power = max_tx_power - antenna_gain; } } } if (temp_comp_enabled) { txpkt.rf_power += (int8_t)temp_comp_adj; MSG("INFO: Tx power temp adjusted to %d\n", (int)txpkt.rf_power); } /* 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]); } /* insert packet to be sent into JIT queue */ if (jit_result == JIT_ERROR_OK) { gettimeofday(¤t_unix_time, NULL); get_concentrator_time(¤t_concentrator_time, current_unix_time); jit_result = jit_enqueue(&jit_queue, ¤t_concentrator_time, &txpkt, downlink_type); if (jit_result != JIT_ERROR_OK) { printf("ERROR: Packet REJECTED (jit error=%d)\n", jit_result); } pthread_mutex_lock(&mx_meas_dw); meas_nb_tx_requested += 1; pthread_mutex_unlock(&mx_meas_dw); } /* Send acknoledge datagram to server */ send_tx_ack(buff_down[1], buff_down[2], jit_result); } } MSG("\nINFO: End of downstream thread\n"); } void print_tx_status(uint8_t tx_status) { switch (tx_status) { case TX_OFF: MSG("INFO: [jit] lgw_status returned TX_OFF\n"); break; case TX_FREE: MSG("INFO: [jit] lgw_status returned TX_FREE\n"); break; case TX_EMITTING: MSG("INFO: [jit] lgw_status returned TX_EMITTING\n"); break; case TX_SCHEDULED: MSG("INFO: [jit] lgw_status returned TX_SCHEDULED\n"); break; default: MSG("INFO: [jit] lgw_status returned UNKNOWN (%d)\n", tx_status); break; } } /* -------------------------------------------------------------------------- */ /* --- THREAD 3: CHECKING PACKETS TO BE SENT FROM JIT QUEUE AND SEND THEM --- */ void thread_jit(void) { int result = LGW_HAL_SUCCESS; struct lgw_pkt_tx_s pkt; int pkt_index = -1; struct timeval current_unix_time; struct timeval current_concentrator_time; enum jit_error_e jit_result; enum jit_pkt_type_e pkt_type; uint8_t tx_status; uint32_t time_on_air = 0; while (!exit_sig && !quit_sig) { wait_ms(10); /* transfer data and metadata to the concentrator, and schedule TX */ gettimeofday(¤t_unix_time, NULL); get_concentrator_time(¤t_concentrator_time, current_unix_time); jit_result = jit_peek(&jit_queue, ¤t_concentrator_time, &pkt_index); if (jit_result == JIT_ERROR_OK) { if (pkt_index > -1) { jit_result = jit_dequeue(&jit_queue, pkt_index, &pkt, &pkt_type); if (jit_result == JIT_ERROR_OK) { /* update beacon stats */ if (pkt_type == JIT_PKT_TYPE_BEACON) { /* Compensate breacon frequency with xtal error */ pthread_mutex_lock(&mx_xcorr); pkt.freq_hz = (uint32_t)(xtal_correct * (double)pkt.freq_hz); MSG_DEBUG(DEBUG_BEACON, "beacon_pkt.freq_hz=%u (xtal_correct=%.15lf)\n", pkt.freq_hz, xtal_correct); pthread_mutex_unlock(&mx_xcorr); /* Update statistics */ pthread_mutex_lock(&mx_meas_dw); meas_nb_beacon_sent += 1; pthread_mutex_unlock(&mx_meas_dw); MSG("INFO: Beacon dequeued (count_us=%u)\n", pkt.count_us); } if (duty_cycle_enabled) { time_on_air = (uint32_t)lgw_time_on_air(&pkt); /* in ms */ if (duty_cycle_time_avail >= time_on_air) { duty_cycle_time_avail -= time_on_air; } else { printf( "WARNING: DUTY-CYCLE-LIMIT | Not enough time-on-air available to allow transmission PKT: %lu ms AVAILABLE: %lu ms\n", time_on_air, duty_cycle_time_avail); continue; } } /* 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) { char serial_buff[128]; /* buffer to receive GPS data */ enum gps_msg latest_msg; /* keep track of latest NMEA message parsed */ memset(serial_buff, 0, sizeof serial_buff); /* initialize some variables before loop */ fd_set fds; char delim[4] = "$"; char *token[254]; while (!exit_sig && !quit_sig) { int r = 0; struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 100000; FD_ZERO(&fds); FD_SET(gpsdata.gps_fd, &fds); errno = 0; r = select(gpsdata.gps_fd+1, &fds, NULL, NULL, &tv); if (r == -1 && errno != EINTR) { MSG("gpspipe: select error %s(%d)\n", strerror(errno), errno); exit(EXIT_FAILURE); } else if (r == 0) continue; /* reading directly from the socket avoids decode overhead */ errno = 0; r = (int)read(gpsdata.gps_fd, serial_buff, sizeof(serial_buff)); if (r > 0) { int i = 0; size_t frame_size = 0; for (i = 0; i < r; i++) { if (serial_buff[i] == (char)LGW_GPS_UBX_SYNC_CHAR) { /*********************** * Found UBX sync char * ***********************/ size_t ubx_size = (uint8_t)serial_buff[i+4]; ubx_size |= (uint8_t)serial_buff[i+5] << 8; ubx_size += 8; if (ubx_size < 27){ latest_msg = lgw_parse_ubx(&serial_buff[i], ubx_size , &frame_size); } if (frame_size > 0) { if(latest_msg == INVALID || latest_msg == UNKNOWN) { /* checksum failed */ frame_size = 0; } else if (latest_msg == UBX_NAV_TIMEGPS) { gps_process_sync(); } } } else if((serial_buff[i] == LGW_GPS_NMEA_SYNC_CHAR) && (serial_buff[i+1] == 0x47) && (serial_buff[i+2] == 0x50)){ /************************ * Found NMEA sync char * ************************/ int k, l= 0; token[0] = strtok(serial_buff, delim); while (token[l] != NULL) { l++; token[l] = strtok(NULL, delim); } for (k=0; k<=l-1; k++) { if ((strlen(token[k]) > 66) && (strlen(token[k]) < 74)){ latest_msg = lgw_parse_nmea(token[k], strlen(token[k])); 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(); } } } } } } else { if (r == -1) { if (errno == EAGAIN) continue; else { MSG(stderr, "gpspipe: read error %s(%d)\n", strerror(errno), errno); exit(EXIT_FAILURE); } } else { exit(EXIT_SUCCESS); } } } 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); if (duty_cycle_enabled) { static struct timespec last = { 0, 0 }; struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); if (last.tv_sec != 0) { // uint64(now.tv_sec) * 1000 + now.tv_nsec / 1000000 duty_cycle_time_avail += difftimespec(now, last) * 1000u * duty_cycle_ratio; if (duty_cycle_time_avail > duty_cycle_time_max) { duty_cycle_time_avail = duty_cycle_time_max; } } last = now; } if (gps_enabled != true) { continue; } /* 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"); } /* -------------------------------------------------------------------------- */ /* --- THREAD 6: Spectral Scan --- */ void thread_spectralscan(void) { if (read_fpga_version() < 31) { /* FPGA v31+ is required to run this thread */ MSG("\nWARNING: Invalid FPGA version for spectran scan. Ending spectral scan thread\n"); return; } struct timeval tv; /* Application parameters */ uint16_t i, j, k; /* loop and temporary variables */ int x; /* return code for functions */ int32_t reg_val; /* Application parameters */ /* Local var */ bool lbt_support = false; uint16_t histogram_clean_retry = 0; uint16_t histogram_ready_retry = 0; uint16_t histogram_clean_counter = 0; uint16_t histogram_ready_counter = 0; uint16_t retry_limit = 1500; int freq_idx; uint32_t freq_nb; int old_min = -1; uint64_t freq_reg; uint32_t freq; uint8_t read_burst[RSSI_RANGE*2]; uint16_t rssi_histo; uint16_t rssi_cumu; uint32_t reset_counter_a = 0; uint32_t reset_counter_b = 0; scan_config.init = DEFAULT_START; scan_config.start = DEFAULT_START; scan_config.stop = DEFAULT_STOP; scan_config.step = DEFAULT_STEP; scan_config.samples = DEFAULT_SAMPLES; scan_config.offset = DEFAULT_SX127X_RSSI_OFFSET; scan_config.bandwidth = 0; scan_config.read = true; pthread_mutex_lock(&mx_scan_config); scan_config_new = scan_config; pthread_mutex_unlock(&mx_scan_config); while (!exit_sig && !quit_sig) { wait_ms(1000); gettimeofday(&tv, NULL); long hms = tv.tv_sec % 86400; int min = (hms % 3600) / 60; pthread_mutex_lock(&mx_scan_config); if (scan_config_new.read == false) { scan_config = scan_config_new; scan_config_new.read = true; } pthread_mutex_unlock(&mx_scan_config); if (scan_config.read == false) { /* Main loop */ setup_spectral_scan(&lbt_support, &freq_reg, &freq_nb); JSON_Value *root_value = json_value_init_object(); JSON_Object *root_object = json_value_get_object(root_value); JSON_Array *interests_arr = NULL; json_object_set_number(root_object, "bandwidth", scan_config.bandwidth); char eui[17]; sprintf(&eui[0], "%016llX", lgwm); json_object_set_string(root_object, "eui", eui); json_object_set_value(root_object, "results", json_value_init_array()); interests_arr = json_object_get_array(root_object, "results"); json_object_set_number(root_object, "start", scan_config.start); json_object_set_number(root_object, "stop", scan_config.stop); json_object_set_number(root_object, "step", scan_config.step); json_object_set_number(root_object, "floor", -160); json_object_set_number(root_object, "offset", scan_config.offset); json_object_set_number(root_object, "samples", scan_config.samples); struct timespec t1; time_t t; t = time(NULL); clock_gettime(CLOCK_MONOTONIC, &t1); char scan_timestamp[24]; strftime(scan_timestamp, sizeof scan_timestamp, "%F %T %Z", gmtime(&t)); json_object_set_string(root_object, "time", scan_timestamp); for(j = 0; j < freq_nb; j++) { /* Current frequency */ if (exit_sig || quit_sig) exit(EXIT_FAILURE); freq = scan_config.start + j * scan_config.step; struct timespec t2; clock_gettime(CLOCK_MONOTONIC, &t2); int scan_time = (int)(1000 * difftimespec(t2, t1)); if (lbt_support == false) { /* Set SX127x */ x = lgw_setup_sx127x(freq, MOD_FSK, map_bandwidth(scan_config.bandwidth), 0); if( x != 0 ) { MSG( "ERROR: SX127x setup failed\n" ); exit(EXIT_FAILURE); } /* Start FPGA state machine for spectral scal */ lgw_fpga_reg_w(LGW_FPGA_CTRL_FEATURE_START, 1); } else { /* Do Nothing */ /* LBT setup has already done the necessary */ } /* Clean histogram */ lgw_fpga_reg_w(LGW_FPGA_CTRL_CLEAR_HISTO_MEM, 1); /* Wait for histogram clean to start */ do { if (histogram_clean_counter > HISTOGRAM_CLEAN_TIMEOUT) { if (histogram_clean_retry >= retry_limit) { printf("\nHistogram clean retry limit reached."); exit(EXIT_FAILURE); } else { reset_counter_a++; printf(" - ERROR: Histogram clean timed out! Resetting FPGA and trying again\n"); lgw_stop(); lgw_start(); x = reset_fpga(lbt_support, scan_config.samples); setup_spectral_scan(&lbt_support, &freq_reg, &freq_nb); if( x != 0 ) { printf( "ERROR: SX127x reset failed\n" ); } lgw_fpga_reg_w(LGW_FPGA_CTRL_CLEAR_HISTO_MEM, 1); if (lbt_support == false) { x = lgw_setup_sx127x(freq, MOD_FSK, map_bandwidth(scan_config.bandwidth), 0); if( x != 0 ) { printf( "ERROR: SX127x setup failed\n" ); exit(EXIT_FAILURE); } lgw_fpga_reg_w(LGW_FPGA_CTRL_FEATURE_START, 1); } histogram_clean_counter = 0; histogram_clean_retry++; } } wait_ms(10); lgw_fpga_reg_r(LGW_FPGA_STATUS, ®_val); histogram_clean_counter++; } while((TAKE_N_BITS_FROM((uint8_t)reg_val, 0, 5)) != 1 && !exit_sig && !quit_sig); /* Clear has started */ /* Set scan frequency during clear process */ if (lbt_support == false) { /* We can directly set the scan frequency */ freq_reg = ((uint64_t)freq << 19) / (uint64_t)32000000; lgw_fpga_reg_w(LGW_FPGA_HISTO_SCAN_FREQ, (int32_t)freq_reg); } else { /* The possible scan frequencies are hard-coded in FPGA, we give an offset from init */ freq_idx = (freq - scan_config.init) / LBT_MIN_STEP; lgw_fpga_reg_w(LGW_FPGA_SCAN_FREQ_OFFSET, freq_idx); } /* Release FPGA state machine */ lgw_fpga_reg_w(LGW_FPGA_CTRL_CLEAR_HISTO_MEM, 0); /* Wait for histogram ready */ do { if (histogram_ready_counter > HISTOGRAM_READY_TIMEOUT) { if (histogram_ready_retry >= retry_limit) { MSG("\nHistogram timeout retry limit reached"); lgw_stop(); lgw_start(); x = reset_fpga(lbt_support, scan_config.samples); setup_spectral_scan(&lbt_support, &freq_reg, &freq_nb); } else { reset_counter_b++; MSG("ERROR: Histogram ready timed out! Resetting FPGA and trying again\n"); x = reset_fpga(lbt_support, scan_config.samples); setup_spectral_scan(&lbt_support, &freq_reg, &freq_nb); if( x != 0 ) { MSG( "ERROR: SX127x reset failed\n" ); exit(EXIT_FAILURE); } lgw_fpga_reg_w(LGW_FPGA_CTRL_CLEAR_HISTO_MEM, 1); if (lbt_support == false) { x = lgw_setup_sx127x(freq, MOD_FSK, map_bandwidth(scan_config.bandwidth), 0); if( x != 0 ) { MSG( "ERROR: SX127x setup failed\n" ); setup_spectral_scan(&lbt_support, &freq_reg, &freq_nb); } lgw_fpga_reg_w(LGW_FPGA_CTRL_FEATURE_START, 1); } histogram_ready_counter = 0; histogram_ready_retry++; if (lbt_support == false) { freq_reg = ((uint64_t)freq << 19) / (uint64_t)32000000; lgw_fpga_reg_w(LGW_FPGA_HISTO_SCAN_FREQ, (int32_t)freq_reg); } else { freq_idx = (freq - scan_config.init) / LBT_MIN_STEP; lgw_fpga_reg_w(LGW_FPGA_SCAN_FREQ_OFFSET, freq_idx); } lgw_fpga_reg_w(LGW_FPGA_CTRL_CLEAR_HISTO_MEM, 0); } } wait_ms(1000); lgw_fpga_reg_r(LGW_FPGA_STATUS, ®_val); histogram_ready_counter++; } while((TAKE_N_BITS_FROM((uint8_t)reg_val, 5, 1)) != 1 && !exit_sig && !quit_sig); if (lbt_support == false) { /* Stop FPGA state machine for spectral scan */ lgw_fpga_reg_w(LGW_FPGA_CTRL_FEATURE_START, 0); } else { /* Do Nothing */ /* LBT is running */ } /* Read histogram */ lgw_fpga_reg_w(LGW_FPGA_CTRL_ACCESS_HISTO_MEM, 1); /* HOST gets access to FPGA RAM */ lgw_fpga_reg_w(LGW_FPGA_HISTO_RAM_ADDR, 0); lgw_fpga_reg_rb(LGW_FPGA_HISTO_RAM_DATA, read_burst, RSSI_RANGE*2); lgw_fpga_reg_w(LGW_FPGA_CTRL_ACCESS_HISTO_MEM, 0); /* FPGA gets access to RAM back */ rssi_cumu = 0; k = 0; char param_name[32]; char *seria_string = NULL; JSON_Value *scan_value = json_value_init_object(); JSON_Object *scan_object = json_value_get_object(scan_value); char scan_string[1024] = ""; snprintf(scan_string, sizeof scan_string, "%d, %d", freq, scan_time); float rssi_thresh[] = {0.1,0.3,0.5,0.8,1}; for (i = 0; i < RSSI_RANGE; i++) { rssi_histo = (uint16_t)read_burst[2*i] | ((uint16_t)read_burst[2*i+1] << 8); rssi_cumu += rssi_histo; if (rssi_cumu > scan_config.samples) { MSG("WARNING: number of RSSI points higher than expected (%u,%u)", rssi_cumu, scan_config.samples); rssi_cumu = scan_config.samples; } if (rssi_cumu > rssi_thresh[k]*scan_config.samples) { snprintf(param_name, sizeof param_name, "rssi_%d", (uint16_t)(rssi_thresh[k]*100)); json_object_set_number(scan_object, param_name, -i/2.0); k++; } if (rssi_histo > 0) { char valid_hiso[1024] = "\""; snprintf(valid_hiso, sizeof valid_hiso, ",%.1f,%d", (-i/2.0), rssi_histo); strcat(scan_string, valid_hiso); } } json_array_append_string(interests_arr, scan_string); json_free_serialized_string(seria_string); json_value_free(scan_value); } char array_string[5000] = "[\""; for (i = 0; i < json_array_get_count(interests_arr); i++) { const char * v = json_array_get_string(interests_arr, i); strcat(array_string, v); if (i < json_array_get_count(interests_arr) - 1 ) { strcat(array_string,"\",\""); } } strcat(array_string, "\"]"); json_object_set_value(root_object, "results", json_parse_string(array_string)); old_min = min; pthread_mutex_lock(&mx_scan_report); scan_ready = true; strcpy(serialized_string, json_serialize_to_string(root_value)); pthread_mutex_unlock(&mx_scan_report); json_value_free(root_value); scan_config.read = true; } } MSG("\nINFO: End of spectral scan thread\n"); } /* --- EOF ------------------------------------------------------------------ */