/* / _____) _ | | ( (____ _____ ____ _| |_ _____ ____| |__ \____ \| ___ | (_ _) ___ |/ ___) _ \ _____) ) ____| | | || |_| ____( (___| | | | (______/|_____)_|_|_| \__)_____)\____)_| |_| (C)2019 Semtech Description: LoRa concentrator Hardware Abstraction Layer License: Revised BSD License, see LICENSE.TXT file include in the project */ /* -------------------------------------------------------------------------- */ /* --- 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 */ #include /* memcpy */ #include /* pow, cell */ #include #include /* symlink, unlink */ #include #include "loragw_reg.h" #include "loragw_hal.h" #include "loragw_aux.h" #include "loragw_spi.h" #include "loragw_i2c.h" #include "loragw_sx1250.h" #include "loragw_sx125x.h" #include "loragw_sx1302.h" #include "loragw_stts751.h" #include "loragw_debug.h" /* -------------------------------------------------------------------------- */ /* --- DEBUG CONSTANTS ------------------------------------------------------ */ #define HAL_DEBUG_FILE_LOG 0 /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_HAL == 1 #define DEBUG_MSG(str) fprintf(stderr, str) #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) #define DEBUG_ARRAY(a,b,c) for(a=0;alorawan_public; CONTEXT_BOARD.clksrc = conf->clksrc; CONTEXT_BOARD.full_duplex = conf->full_duplex; strncpy(CONTEXT_SPI, conf->spidev_path, sizeof CONTEXT_SPI); DEBUG_PRINTF("Note: board configuration: spidev_path: %s, lorawan_public:%d, clksrc:%d, full_duplex:%d\n", CONTEXT_SPI, CONTEXT_LWAN_PUBLIC, CONTEXT_BOARD.clksrc, CONTEXT_BOARD.full_duplex); return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_rxrf_setconf(uint8_t rf_chain, struct lgw_conf_rxrf_s * conf) { CHECK_NULL(conf); /* check if the concentrator is running */ if (CONTEXT_STARTED == true) { DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n"); return LGW_HAL_ERROR; } if (conf->enable == false) { /* nothing to do */ DEBUG_PRINTF("Note: rf_chain %d disabled\n", rf_chain); return LGW_HAL_SUCCESS; } /* check input range (segfault prevention) */ if (rf_chain >= LGW_RF_CHAIN_NB) { DEBUG_MSG("ERROR: NOT A VALID RF_CHAIN NUMBER\n"); return LGW_HAL_ERROR; } /* check if radio type is supported */ if ((conf->type != LGW_RADIO_TYPE_SX1255) && (conf->type != LGW_RADIO_TYPE_SX1257) && (conf->type != LGW_RADIO_TYPE_SX1250)) { DEBUG_PRINTF("ERROR: NOT A VALID RADIO TYPE (%d)\n", conf->type); return LGW_HAL_ERROR; } /* check if the radio central frequency is valid */ if ((conf->freq_hz < LGW_RF_RX_FREQ_MIN) || (conf->freq_hz > LGW_RF_RX_FREQ_MAX)) { DEBUG_PRINTF("ERROR: NOT A VALID RADIO CENTER FREQUENCY, PLEASE CHECK IF IT HAS BEEN GIVEN IN HZ (%u)\n", conf->freq_hz); return LGW_HAL_ERROR; } /* set internal config according to parameters */ CONTEXT_RF_CHAIN[rf_chain].enable = conf->enable; CONTEXT_RF_CHAIN[rf_chain].freq_hz = conf->freq_hz; CONTEXT_RF_CHAIN[rf_chain].rssi_offset = conf->rssi_offset; CONTEXT_RF_CHAIN[rf_chain].rssi_tcomp.coeff_a = conf->rssi_tcomp.coeff_a; CONTEXT_RF_CHAIN[rf_chain].rssi_tcomp.coeff_b = conf->rssi_tcomp.coeff_b; CONTEXT_RF_CHAIN[rf_chain].rssi_tcomp.coeff_c = conf->rssi_tcomp.coeff_c; CONTEXT_RF_CHAIN[rf_chain].rssi_tcomp.coeff_d = conf->rssi_tcomp.coeff_d; CONTEXT_RF_CHAIN[rf_chain].rssi_tcomp.coeff_e = conf->rssi_tcomp.coeff_e; CONTEXT_RF_CHAIN[rf_chain].type = conf->type; CONTEXT_RF_CHAIN[rf_chain].tx_enable = conf->tx_enable; DEBUG_PRINTF("Note: rf_chain %d configuration; en:%d freq:%d rssi_offset:%f radio_type:%d tx_enable:%d\n", rf_chain, CONTEXT_RF_CHAIN[rf_chain].enable, CONTEXT_RF_CHAIN[rf_chain].freq_hz, CONTEXT_RF_CHAIN[rf_chain].rssi_offset, CONTEXT_RF_CHAIN[rf_chain].type, CONTEXT_RF_CHAIN[rf_chain].tx_enable); return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_rxif_setconf(uint8_t if_chain, struct lgw_conf_rxif_s * conf) { int32_t bw_hz; uint32_t rf_rx_bandwidth; CHECK_NULL(conf); /* check if the concentrator is running */ if (CONTEXT_STARTED == true) { DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n"); return LGW_HAL_ERROR; } /* check input range (segfault prevention) */ if (if_chain >= LGW_IF_CHAIN_NB) { DEBUG_PRINTF("ERROR: %d NOT A VALID IF_CHAIN NUMBER\n", if_chain); return LGW_HAL_ERROR; } /* if chain is disabled, don't care about most parameters */ if (conf->enable == false) { CONTEXT_IF_CHAIN[if_chain].enable = false; CONTEXT_IF_CHAIN[if_chain].freq_hz = 0; DEBUG_PRINTF("Note: if_chain %d disabled\n", if_chain); return LGW_HAL_SUCCESS; } /* check 'general' parameters */ if (sx1302_get_ifmod_config(if_chain) == IF_UNDEFINED) { DEBUG_PRINTF("ERROR: IF CHAIN %d NOT CONFIGURABLE\n", if_chain); } if (conf->rf_chain >= LGW_RF_CHAIN_NB) { DEBUG_MSG("ERROR: INVALID RF_CHAIN TO ASSOCIATE WITH A LORA_STD IF CHAIN\n"); return LGW_HAL_ERROR; } /* check if IF frequency is optimal based on channel and radio bandwidths */ switch (conf->bandwidth) { case BW_250KHZ: rf_rx_bandwidth = LGW_RF_RX_BANDWIDTH_250KHZ; /* radio bandwidth */ break; case BW_500KHZ: rf_rx_bandwidth = LGW_RF_RX_BANDWIDTH_500KHZ; /* radio bandwidth */ break; default: /* For 125KHz and below */ rf_rx_bandwidth = LGW_RF_RX_BANDWIDTH_125KHZ; /* radio bandwidth */ break; } bw_hz = lgw_bw_getval(conf->bandwidth); /* channel bandwidth */ if ((conf->freq_hz + ((bw_hz==-1)?LGW_REF_BW:bw_hz)/2) > ((int32_t)rf_rx_bandwidth/2)) { DEBUG_PRINTF("ERROR: IF FREQUENCY %d TOO HIGH\n", conf->freq_hz); return LGW_HAL_ERROR; } else if ((conf->freq_hz - ((bw_hz==-1)?LGW_REF_BW:bw_hz)/2) < -((int32_t)rf_rx_bandwidth/2)) { DEBUG_PRINTF("ERROR: IF FREQUENCY %d TOO LOW\n", conf->freq_hz); return LGW_HAL_ERROR; } /* check parameters according to the type of IF chain + modem, fill default if necessary, and commit configuration if everything is OK */ switch (sx1302_get_ifmod_config(if_chain)) { case IF_LORA_STD: /* fill default parameters if needed */ if (conf->bandwidth == BW_UNDEFINED) { conf->bandwidth = BW_250KHZ; } if (conf->datarate == DR_UNDEFINED) { conf->datarate = DR_LORA_SF7; } /* check BW & DR */ if (!IS_LORA_BW(conf->bandwidth)) { DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA_STD IF CHAIN\n"); return LGW_HAL_ERROR; } if (!IS_LORA_DR(conf->datarate)) { DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY LORA_STD IF CHAIN\n"); return LGW_HAL_ERROR; } /* set internal configuration */ CONTEXT_IF_CHAIN[if_chain].enable = conf->enable; CONTEXT_IF_CHAIN[if_chain].rf_chain = conf->rf_chain; CONTEXT_IF_CHAIN[if_chain].freq_hz = conf->freq_hz; CONTEXT_LORA_SERVICE.bandwidth = conf->bandwidth; CONTEXT_LORA_SERVICE.datarate = conf->datarate; CONTEXT_LORA_SERVICE.implicit_hdr = conf->implicit_hdr; CONTEXT_LORA_SERVICE.implicit_payload_length = conf->implicit_payload_length; CONTEXT_LORA_SERVICE.implicit_crc_en = conf->implicit_crc_en; CONTEXT_LORA_SERVICE.implicit_coderate = conf->implicit_coderate; DEBUG_PRINTF("Note: LoRa 'std' if_chain %d configuration; en:%d freq:%d bw:%d dr:%d\n", if_chain, CONTEXT_IF_CHAIN[if_chain].enable, CONTEXT_IF_CHAIN[if_chain].freq_hz, CONTEXT_LORA_SERVICE.bandwidth, CONTEXT_LORA_SERVICE.datarate); break; case IF_LORA_MULTI: /* fill default parameters if needed */ if (conf->bandwidth == BW_UNDEFINED) { conf->bandwidth = BW_125KHZ; } if (conf->datarate == DR_UNDEFINED) { conf->datarate = DR_LORA_SF7; } /* check BW & DR */ if (conf->bandwidth != BW_125KHZ) { DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA_MULTI IF CHAIN\n"); return LGW_HAL_ERROR; } if (!IS_LORA_DR(conf->datarate)) { DEBUG_MSG("ERROR: DATARATE(S) NOT SUPPORTED BY LORA_MULTI IF CHAIN\n"); return LGW_HAL_ERROR; } /* set internal configuration */ CONTEXT_IF_CHAIN[if_chain].enable = conf->enable; CONTEXT_IF_CHAIN[if_chain].rf_chain = conf->rf_chain; CONTEXT_IF_CHAIN[if_chain].freq_hz = conf->freq_hz; DEBUG_PRINTF("Note: LoRa 'multi' if_chain %d configuration; en:%d freq:%d\n", if_chain, CONTEXT_IF_CHAIN[if_chain].enable, CONTEXT_IF_CHAIN[if_chain].freq_hz); break; case IF_FSK_STD: /* fill default parameters if needed */ if (conf->bandwidth == BW_UNDEFINED) { conf->bandwidth = BW_250KHZ; } if (conf->datarate == DR_UNDEFINED) { conf->datarate = 64000; /* default datarate */ } /* check BW & DR */ if(!IS_FSK_BW(conf->bandwidth)) { DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY FSK IF CHAIN\n"); return LGW_HAL_ERROR; } if(!IS_FSK_DR(conf->datarate)) { DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY FSK IF CHAIN\n"); return LGW_HAL_ERROR; } /* set internal configuration */ CONTEXT_IF_CHAIN[if_chain].enable = conf->enable; CONTEXT_IF_CHAIN[if_chain].rf_chain = conf->rf_chain; CONTEXT_IF_CHAIN[if_chain].freq_hz = conf->freq_hz; CONTEXT_FSK.bandwidth = conf->bandwidth; CONTEXT_FSK.datarate = conf->datarate; if (conf->sync_word > 0) { CONTEXT_FSK.sync_word_size = conf->sync_word_size; CONTEXT_FSK.sync_word = conf->sync_word; } DEBUG_PRINTF("Note: FSK if_chain %d configuration; en:%d freq:%d bw:%d dr:%d (%d real dr) sync:0x%0*llX\n", if_chain, CONTEXT_IF_CHAIN[if_chain].enable, CONTEXT_IF_CHAIN[if_chain].freq_hz, CONTEXT_FSK.bandwidth, CONTEXT_FSK.datarate, LGW_XTAL_FREQU/(LGW_XTAL_FREQU/CONTEXT_FSK.datarate), 2*CONTEXT_FSK.sync_word_size, CONTEXT_FSK.sync_word); break; default: DEBUG_PRINTF("ERROR: IF CHAIN %d TYPE NOT SUPPORTED\n", if_chain); return LGW_HAL_ERROR; } return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_txgain_setconf(uint8_t rf_chain, struct lgw_tx_gain_lut_s * conf) { int i; CHECK_NULL(conf); /* Check LUT size */ if ((conf->size < 1) || (conf->size > TX_GAIN_LUT_SIZE_MAX)) { DEBUG_PRINTF("ERROR: TX gain LUT must have at least one entry and maximum %d entries\n", TX_GAIN_LUT_SIZE_MAX); return LGW_HAL_ERROR; } CONTEXT_TX_GAIN_LUT[rf_chain].size = conf->size; for (i = 0; i < CONTEXT_TX_GAIN_LUT[rf_chain].size; i++) { /* Check gain range */ if (conf->lut[i].dig_gain > 3) { DEBUG_MSG("ERROR: TX gain LUT: SX1302 digital gain must be between 0 and 3\n"); return LGW_HAL_ERROR; } if (conf->lut[i].dac_gain > 3) { DEBUG_MSG("ERROR: TX gain LUT: SX1257 DAC gains must not exceed 3\n"); return LGW_HAL_ERROR; } if ((conf->lut[i].mix_gain < 5) || (conf->lut[i].mix_gain > 15)) { DEBUG_MSG("ERROR: TX gain LUT: SX1257 mixer gain must be betwen [5..15]\n"); return LGW_HAL_ERROR; } if (conf->lut[i].pa_gain > 3) { DEBUG_MSG("ERROR: TX gain LUT: External PA gain must not exceed 3\n"); return LGW_HAL_ERROR; } if (conf->lut[i].pwr_idx > 22) { DEBUG_MSG("ERROR: TX gain LUT: SX1250 power iundex must not exceed 22\n"); return LGW_HAL_ERROR; } /* Set internal LUT */ CONTEXT_TX_GAIN_LUT[rf_chain].lut[i].rf_power = conf->lut[i].rf_power; CONTEXT_TX_GAIN_LUT[rf_chain].lut[i].dig_gain = conf->lut[i].dig_gain; CONTEXT_TX_GAIN_LUT[rf_chain].lut[i].pa_gain = conf->lut[i].pa_gain; /* sx125x */ CONTEXT_TX_GAIN_LUT[rf_chain].lut[i].dac_gain = conf->lut[i].dac_gain; CONTEXT_TX_GAIN_LUT[rf_chain].lut[i].mix_gain = conf->lut[i].mix_gain; CONTEXT_TX_GAIN_LUT[rf_chain].lut[i].offset_i = 0; /* To be calibrated */ CONTEXT_TX_GAIN_LUT[rf_chain].lut[i].offset_q = 0; /* To be calibrated */ /* sx1250 */ CONTEXT_TX_GAIN_LUT[rf_chain].lut[i].pwr_idx = conf->lut[i].pwr_idx; } return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_timestamp_setconf(struct lgw_conf_timestamp_s * conf) { CHECK_NULL(conf); CONTEXT_TIMESTAMP.enable_precision_ts = conf->enable_precision_ts; CONTEXT_TIMESTAMP.max_ts_metrics = conf->max_ts_metrics; CONTEXT_TIMESTAMP.nb_symbols = conf->nb_symbols; return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_debug_setconf(struct lgw_conf_debug_s * conf) { int i; CHECK_NULL(conf); CONTEXT_DEBUG.nb_ref_payload = conf->nb_ref_payload; for (i = 0; i < CONTEXT_DEBUG.nb_ref_payload; i++) { /* Get user configuration */ CONTEXT_DEBUG.ref_payload[i].id = conf->ref_payload[i].id; /* Initialize global context */ CONTEXT_DEBUG.ref_payload[i].prev_cnt = 0; CONTEXT_DEBUG.ref_payload[i].payload[0] = (uint8_t)(CONTEXT_DEBUG.ref_payload[i].id >> 24); CONTEXT_DEBUG.ref_payload[i].payload[1] = (uint8_t)(CONTEXT_DEBUG.ref_payload[i].id >> 16); CONTEXT_DEBUG.ref_payload[i].payload[2] = (uint8_t)(CONTEXT_DEBUG.ref_payload[i].id >> 8); CONTEXT_DEBUG.ref_payload[i].payload[3] = (uint8_t)(CONTEXT_DEBUG.ref_payload[i].id >> 0); } if (conf->log_file_name != NULL) { strncpy(CONTEXT_DEBUG.log_file_name, conf->log_file_name, strlen(conf->log_file_name)); } return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_start(void) { int i, err; int reg_stat; if (CONTEXT_STARTED == true) { DEBUG_MSG("Note: LoRa concentrator already started, restarting it now\n"); } reg_stat = lgw_connect(CONTEXT_SPI); if (reg_stat == LGW_REG_ERROR) { DEBUG_MSG("ERROR: FAIL TO CONNECT BOARD\n"); return LGW_HAL_ERROR; } /* Calibrate radios */ err = sx1302_radio_calibrate(&CONTEXT_RF_CHAIN[0], CONTEXT_BOARD.clksrc, &CONTEXT_TX_GAIN_LUT[0]); if (err != LGW_REG_SUCCESS) { printf("ERROR: radio calibration failed\n"); return LGW_HAL_ERROR; } /* Setup radios for RX */ for (i = 0; i < LGW_RF_CHAIN_NB; i++) { if (CONTEXT_RF_CHAIN[i].enable == true) { sx1302_radio_reset(i, CONTEXT_RF_CHAIN[i].type); switch (CONTEXT_RF_CHAIN[i].type) { case LGW_RADIO_TYPE_SX1250: sx1250_setup(i, CONTEXT_RF_CHAIN[i].freq_hz); break; case LGW_RADIO_TYPE_SX1255: case LGW_RADIO_TYPE_SX1257: sx125x_setup(i, CONTEXT_BOARD.clksrc, true, CONTEXT_RF_CHAIN[i].type, CONTEXT_RF_CHAIN[i].freq_hz); break; default: DEBUG_PRINTF("ERROR: RADIO TYPE NOT SUPPORTED (RF_CHAIN %d)\n", i); return LGW_HAL_ERROR; } sx1302_radio_set_mode(i, CONTEXT_RF_CHAIN[i].type); } } /* Select the radio which provides the clock to the sx1302 */ sx1302_radio_clock_select(CONTEXT_BOARD.clksrc); /* Release host control on radio (will be controlled by AGC) */ sx1302_radio_host_ctrl(false); /* Basic initialization of the sx1302 */ sx1302_init(&CONTEXT_TIMESTAMP); /* Configure PA/LNA LUTs */ sx1302_pa_lna_lut_configure(); /* Configure Radio FE */ sx1302_radio_fe_configure(); /* Configure the Channelizer */ sx1302_channelizer_configure(CONTEXT_IF_CHAIN, false); /* configure LoRa 'multi' demodulators */ sx1302_lora_correlator_configure(); sx1302_lora_modem_configure(CONTEXT_RF_CHAIN[0].freq_hz); /* TODO: freq_hz used to confiogure freq to time drift, based on RF0 center freq only */ /* configure LoRa 'stand-alone' modem */ if (CONTEXT_IF_CHAIN[8].enable == true) { sx1302_lora_service_correlator_configure(&(CONTEXT_LORA_SERVICE)); sx1302_lora_service_modem_configure(&(CONTEXT_LORA_SERVICE), CONTEXT_RF_CHAIN[0].freq_hz); /* TODO: freq_hz used to confiogure freq to time drift, based on RF0 center freq only */ } /* configure FSK modem */ if (CONTEXT_IF_CHAIN[9].enable == true) { sx1302_fsk_configure(&(CONTEXT_FSK)); } /* configure syncword */ sx1302_lora_syncword(CONTEXT_LWAN_PUBLIC, CONTEXT_LORA_SERVICE.datarate); /* enable demodulators - to be done before starting AGC/ARB */ sx1302_modem_enable(); /* Load firmware */ switch (CONTEXT_RF_CHAIN[CONTEXT_BOARD.clksrc].type) { case LGW_RADIO_TYPE_SX1250: DEBUG_MSG("Loading AGC fw for sx1250\n"); if (sx1302_agc_load_firmware(agc_firmware_sx1250) != LGW_HAL_SUCCESS) { return LGW_HAL_ERROR; } break; case LGW_RADIO_TYPE_SX1257: DEBUG_MSG("Loading AGC fw for sx125x\n"); if (sx1302_agc_load_firmware(agc_firmware_sx125x) != LGW_HAL_SUCCESS) { return LGW_HAL_ERROR; } break; default: break; } if (sx1302_agc_start(FW_VERSION_AGC, CONTEXT_RF_CHAIN[CONTEXT_BOARD.clksrc].type, SX1302_AGC_RADIO_GAIN_AUTO, SX1302_AGC_RADIO_GAIN_AUTO, (CONTEXT_BOARD.full_duplex == true) ? 1 : 0) != LGW_HAL_SUCCESS) { return LGW_HAL_ERROR; } DEBUG_MSG("Loading ARB fw\n"); if (sx1302_arb_load_firmware(arb_firmware) != LGW_HAL_SUCCESS) { return LGW_HAL_ERROR; } if (sx1302_arb_start(FW_VERSION_ARB) != LGW_HAL_SUCCESS) { return LGW_HAL_ERROR; } /* static TX configuration */ sx1302_tx_configure(CONTEXT_RF_CHAIN[CONTEXT_BOARD.clksrc].type); /* enable GPS */ sx1302_gps_enable(true); /* For debug logging */ #if HAL_DEBUG_FILE_LOG char timestamp_str[40]; struct tm *timenow; /* Append current time to log file name */ time_t now = time(NULL); timenow = gmtime(&now); strftime(timestamp_str, sizeof(timestamp_str), ".%Y-%m-%d_%H%M%S", timenow); strncat(CONTEXT_DEBUG.log_file_name, timestamp_str, sizeof CONTEXT_DEBUG.log_file_name); /* Open the file for writting */ log_file = fopen(CONTEXT_DEBUG.log_file_name, "w+"); /* create log file, overwrite if file already exist */ if (log_file == NULL) { printf("ERROR: impossible to create log file %s\n", CONTEXT_DEBUG.log_file_name); return LGW_HAL_ERROR; } else { printf("INFO: %s file opened for debug log\n", CONTEXT_DEBUG.log_file_name); /* Create "pktlog.csv" symlink to simplify user life */ unlink("loragw_hal.log"); i = symlink(CONTEXT_DEBUG.log_file_name, "loragw_hal.log"); if (i < 0) { printf("ERROR: impossible to create symlink to log file %s\n", CONTEXT_DEBUG.log_file_name); } } #endif /* Configure the pseudo-random generator (For Debug) */ dbg_init_random(); #if 0 /* Configure a GPIO to be toggled for debug purpose */ dbg_init_gpio(); #endif /* Open I2C */ err = i2c_linuxdev_open(I2C_DEVICE, I2C_PORT_TEMP_SENSOR, &lgw_i2c_target); if ((err != 0) || (lgw_i2c_target <= 0)) { printf("ERROR: failed to open I2C device %s (err=%i)\n", I2C_DEVICE, err); return LGW_HAL_ERROR; } /* Configure the CoreCell temperature sensor */ if (lgw_stts751_configure() != LGW_I2C_SUCCESS) { printf("ERROR: failed to configure temperature sensor\n"); return LGW_HAL_ERROR; } /* set hal state */ CONTEXT_STARTED = true; return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_stop(void) { int i, err; DEBUG_MSG("INFO: aborting TX\n"); for (i = 0; i < LGW_RF_CHAIN_NB; i++) { lgw_abort_tx(i); } /* Close log file */ if (log_file != NULL) { fclose(log_file); log_file = NULL; } DEBUG_MSG("INFO: Disconnecting\n"); lgw_disconnect(); DEBUG_MSG("INFO: Closing I2C\n"); err = i2c_linuxdev_close(lgw_i2c_target); if (err != 0) { printf("ERROR: failed to close I2C device (err=%i)\n", err); /* TODO: return error or not ? */ } CONTEXT_STARTED = false; return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data) { int res; uint16_t sz = 0; uint16_t nb_pkt_found = 0; uint16_t nb_pkt_dropped = 0; float current_temperature, rssi_temperature_offset; /* Check that AGC/ARB firmwares are not corrupted, and update internal counter */ /* WARNING: this needs to be called regularly by the upper layer */ res = sx1302_update(); if (res != LGW_REG_SUCCESS) { return LGW_HAL_ERROR; } /* Get packets from SX1302, if any */ res = sx1302_fetch(&sz); if (res != LGW_REG_SUCCESS) { printf("ERROR: failed to fetch packets from SX1302\n"); return LGW_HAL_ERROR; } if (sz == 0) { return 0; } /* Get the current temperature for further RSSI compensation : TODO */ res = lgw_stts751_get_temperature(¤t_temperature); if (res != LGW_I2C_SUCCESS) { printf("ERROR: failed to get current temperature\n"); return LGW_HAL_ERROR; } DEBUG_PRINTF("INFO: current temperature is %f C\n", current_temperature); /* Iterate on the RX buffer to get parsed packets */ res = LGW_REG_SUCCESS; while ((res == LGW_REG_SUCCESS) && (nb_pkt_found <= max_pkt)) { res = sx1302_parse(&lgw_context, &pkt_data[nb_pkt_found]); if (res == LGW_REG_SUCCESS) { /* we found a packet and parsed it */ if ((nb_pkt_found + 1) > max_pkt) { printf("WARNING: no space left, dropping packet\n"); nb_pkt_dropped += 1; continue; } /* Appli RSSI offset calibrated for the board */ pkt_data[nb_pkt_found].rssic += CONTEXT_RF_CHAIN[pkt_data[nb_pkt_found].rf_chain].rssi_offset; pkt_data[nb_pkt_found].rssis += CONTEXT_RF_CHAIN[pkt_data[nb_pkt_found].rf_chain].rssi_offset; /* Apply RSSI temperature compensation */ rssi_temperature_offset = sx1302_rssi_get_temperature_offset(&CONTEXT_RF_CHAIN[pkt_data[nb_pkt_found].rf_chain].rssi_tcomp, current_temperature); pkt_data[nb_pkt_found].rssic += rssi_temperature_offset; pkt_data[nb_pkt_found].rssis += rssi_temperature_offset; DEBUG_PRINTF("INFO: RSSI temperature offset applied: %.3f dB\n", rssi_temperature_offset); /* Next packet */ nb_pkt_found += 1; } } DEBUG_PRINTF("INFO: nb pkt found:%u dropped:%u\n", nb_pkt_found, nb_pkt_dropped); return nb_pkt_found; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_send(struct lgw_pkt_tx_s * pkt_data) { /* check if the concentrator is running */ if (CONTEXT_STARTED == false) { DEBUG_MSG("ERROR: CONCENTRATOR IS NOT RUNNING, START IT BEFORE SENDING\n"); return LGW_HAL_ERROR; } CHECK_NULL(pkt_data); /* check input range (segfault prevention) */ if (pkt_data->rf_chain >= LGW_RF_CHAIN_NB) { DEBUG_MSG("ERROR: INVALID RF_CHAIN TO SEND PACKETS\n"); return LGW_HAL_ERROR; } /* check input variables */ if (CONTEXT_RF_CHAIN[pkt_data->rf_chain].tx_enable == false) { DEBUG_MSG("ERROR: SELECTED RF_CHAIN IS DISABLED FOR TX ON SELECTED BOARD\n"); return LGW_HAL_ERROR; } if (CONTEXT_RF_CHAIN[pkt_data->rf_chain].enable == false) { DEBUG_MSG("ERROR: SELECTED RF_CHAIN IS DISABLED\n"); return LGW_HAL_ERROR; } if (!IS_TX_MODE(pkt_data->tx_mode)) { DEBUG_MSG("ERROR: TX_MODE NOT SUPPORTED\n"); return LGW_HAL_ERROR; } if (pkt_data->modulation == MOD_LORA) { if (!IS_LORA_BW(pkt_data->bandwidth)) { DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA TX\n"); return LGW_HAL_ERROR; } if (!IS_LORA_DR(pkt_data->datarate)) { DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY LORA TX\n"); return LGW_HAL_ERROR; } if (!IS_LORA_CR(pkt_data->coderate)) { DEBUG_MSG("ERROR: CODERATE NOT SUPPORTED BY LORA TX\n"); return LGW_HAL_ERROR; } if (pkt_data->size > 255) { DEBUG_MSG("ERROR: PAYLOAD LENGTH TOO BIG FOR LORA TX\n"); return LGW_HAL_ERROR; } } else if (pkt_data->modulation == MOD_FSK) { if((pkt_data->f_dev < 1) || (pkt_data->f_dev > 200)) { DEBUG_MSG("ERROR: TX FREQUENCY DEVIATION OUT OF ACCEPTABLE RANGE\n"); return LGW_HAL_ERROR; } if(!IS_FSK_DR(pkt_data->datarate)) { DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY FSK IF CHAIN\n"); return LGW_HAL_ERROR; } if (pkt_data->size > 255) { DEBUG_MSG("ERROR: PAYLOAD LENGTH TOO BIG FOR FSK TX\n"); return LGW_HAL_ERROR; } } else if (pkt_data->modulation == MOD_CW) { /* do nothing */ } else { DEBUG_MSG("ERROR: INVALID TX MODULATION\n"); return LGW_HAL_ERROR; } return sx1302_send(CONTEXT_RF_CHAIN[pkt_data->rf_chain].type, &CONTEXT_TX_GAIN_LUT[pkt_data->rf_chain], CONTEXT_LWAN_PUBLIC, &CONTEXT_FSK, pkt_data); } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_status(uint8_t rf_chain, uint8_t select, uint8_t *code) { /* check input variables */ CHECK_NULL(code); if (rf_chain >= LGW_RF_CHAIN_NB) { DEBUG_MSG("ERROR: NOT A VALID RF_CHAIN NUMBER\n"); return LGW_HAL_ERROR; } /* Get status */ if (select == TX_STATUS) { if (CONTEXT_STARTED == false) { *code = TX_OFF; } else { *code = sx1302_tx_status(rf_chain); } } else if (select == RX_STATUS) { if (CONTEXT_STARTED == false) { *code = RX_OFF; } else { *code = sx1302_rx_status(rf_chain); } } else { DEBUG_MSG("ERROR: SELECTION INVALID, NO STATUS TO RETURN\n"); return LGW_HAL_ERROR; } //DEBUG_PRINTF("INFO: STATUS %u\n", *code); return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_abort_tx(uint8_t rf_chain) { /* check input variables */ if (rf_chain >= LGW_RF_CHAIN_NB) { DEBUG_MSG("ERROR: NOT A VALID RF_CHAIN NUMBER\n"); return LGW_HAL_ERROR; } /* Abort current TX */ return sx1302_tx_abort(rf_chain); } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_get_trigcnt(uint32_t* trig_cnt_us) { *trig_cnt_us = sx1302_timestamp_counter(true); return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_get_instcnt(uint32_t* inst_cnt_us) { *inst_cnt_us = sx1302_timestamp_counter(false); return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_get_eui(uint64_t* eui) { if (sx1302_get_eui(eui) != LGW_REG_SUCCESS) { return LGW_HAL_ERROR; } return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ const char* lgw_version_info() { return lgw_version_string; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ uint32_t lgw_time_on_air(struct lgw_pkt_tx_s *packet) { int32_t val; uint8_t SF, H, DE; uint16_t BW; uint32_t payloadSymbNb, Tpacket; double Tsym, Tpreamble, Tpayload, Tfsk; if (packet == NULL) { DEBUG_MSG("ERROR: Failed to compute time on air, wrong parameter\n"); return 0; } if (packet->modulation == MOD_LORA) { /* Get bandwidth */ val = lgw_bw_getval(packet->bandwidth); if (val != -1) { BW = (uint16_t)(val / 1E3); } else { DEBUG_PRINTF("ERROR: Cannot compute time on air for this packet, unsupported bandwidth (0x%02X)\n", packet->bandwidth); return 0; } /* Get datarate */ val = lgw_sf_getval(packet->datarate); if (val != -1) { SF = (uint8_t)val; /* TODO: update formula for SF5/SF6 */ if (SF < 7) { DEBUG_MSG("WARNING: clipping time on air computing to SF7 for SF5/SF6\n"); SF = 7; } } else { DEBUG_PRINTF("ERROR: Cannot compute time on air for this packet, unsupported datarate (0x%02X)\n", packet->datarate); return 0; } /* Duration of 1 symbol */ Tsym = pow(2, SF) / BW; /* Duration of preamble */ Tpreamble = ((double)(packet->preamble) + 4.25) * Tsym; /* Duration of payload */ H = (packet->no_header==false) ? 0 : 1; /* header is always enabled, except for beacons */ DE = (SF >= 11) ? 1 : 0; /* Low datarate optimization enabled for SF11 and SF12 */ payloadSymbNb = 8 + (ceil((double)(8*packet->size - 4*SF + 28 + 16 - 20*H) / (double)(4*(SF - 2*DE))) * (packet->coderate + 4)); /* Explicitely cast to double to keep precision of the division */ Tpayload = payloadSymbNb * Tsym; /* Duration of packet */ Tpacket = Tpreamble + Tpayload; } else if (packet->modulation == MOD_FSK) { /* PREAMBLE + SYNC_WORD + PKT_LEN + PKT_PAYLOAD + CRC PREAMBLE: default 5 bytes SYNC_WORD: default 3 bytes PKT_LEN: 1 byte (variable length mode) PKT_PAYLOAD: x bytes CRC: 0 or 2 bytes */ Tfsk = (8 * (double)(packet->preamble + CONTEXT_FSK.sync_word_size + 1 + packet->size + ((packet->no_crc == true) ? 0 : 2)) / (double)packet->datarate) * 1E3; /* Duration of packet */ Tpacket = (uint32_t)Tfsk + 1; /* add margin for rounding */ } else { Tpacket = 0; DEBUG_PRINTF("ERROR: Cannot compute time on air for this packet, unsupported modulation (0x%02X)\n", packet->modulation); } return Tpacket; } /* --- EOF ------------------------------------------------------------------ */