2019-07-12 15:40:13 +02:00
/*
/ _____ ) _ | |
( ( ____ _____ ____ _ | | _ _____ ____ | | __
\ ____ \ | ___ | ( _ _ ) ___ | / ___ ) _ \
_____ ) ) ____ | | | | | | _ | ____ ( ( ___ | | | |
( ______ / | _____ ) _ | _ | _ | \ __ ) _____ ) \ ____ ) _ | | _ |
( 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 <stdint.h> /* C99 types */
# include <stdbool.h> /* bool type */
# include <stdio.h> /* printf fprintf */
# include <string.h> /* memcpy */
# include <math.h> /* pow, cell */
# include <time.h>
# include <unistd.h> /* symlink, unlink */
# include <fcntl.h>
# 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;a<b;++a) fprintf(stderr,"%x.",c[a]);fprintf(stderr,"end\n")
# define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_HAL_ERROR;}
# else
# define DEBUG_MSG(str)
# define DEBUG_PRINTF(fmt, args...)
# define DEBUG_ARRAY(a,b,c) for(a=0;a!=0;){}
# define CHECK_NULL(a) if(a==NULL){return LGW_HAL_ERROR;}
# endif
# define TRACE() fprintf(stderr, "@ %s %d\n", __FUNCTION__, __LINE__);
# define CONTEXT_STARTED lgw_context.is_started
# define CONTEXT_SPI lgw_context.board_cfg.spidev_path
# define CONTEXT_LWAN_PUBLIC lgw_context.board_cfg.lorawan_public
# define CONTEXT_BOARD lgw_context.board_cfg
# define CONTEXT_RF_CHAIN lgw_context.rf_chain_cfg
# define CONTEXT_IF_CHAIN lgw_context.if_chain_cfg
# define CONTEXT_LORA_SERVICE lgw_context.lora_service_cfg
# define CONTEXT_FSK lgw_context.fsk_cfg
# define CONTEXT_TX_GAIN_LUT lgw_context.tx_gain_lut
# define CONTEXT_TIMESTAMP lgw_context.timestamp_cfg
# define CONTEXT_DEBUG lgw_context.debug_cfg
/* -------------------------------------------------------------------------- */
/* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */
# define FW_VERSION_AGC 1 /* Expected version of AGC firmware */
# define FW_VERSION_ARB 1 /* Expected version of arbiter firmware */
/* Useful bandwidth of SX125x radios to consider depending on channel bandwidth */
/* Note: the below values come from lab measurements. For any question, please contact Semtech support */
# define LGW_RF_RX_BANDWIDTH_125KHZ 1600000 /* for 125KHz channels */
# define LGW_RF_RX_BANDWIDTH_250KHZ 1600000 /* for 250KHz channels */
# define LGW_RF_RX_BANDWIDTH_500KHZ 1600000 /* for 500KHz channels */
# define LGW_RF_RX_FREQ_MIN 100E6
# define LGW_RF_RX_FREQ_MAX 1E9
/* Version string, used to identify the library version/options once compiled */
const char lgw_version_string [ ] = " Version: " LIBLORAGW_VERSION " ; " ;
/* -------------------------------------------------------------------------- */
/* --- PRIVATE VARIABLES ---------------------------------------------------- */
# include "arb_fw.var" /* text_arb_sx1302_13_Nov_3 */
# include "agc_fw_sx1250.var" /* text_agc_sx1250_05_Juillet_2019_3 */
# include "agc_fw_sx1257.var" /* text_agc_sx1257_19_Nov_1 */
/*
The following static variable holds the gateway configuration provided by the
user that need to be propagated in the drivers .
Parameters validity and coherency is verified by the _setconf functions and
the _start and _send functions assume they are valid .
*/
static lgw_context_t lgw_context = {
. is_started = false ,
. board_cfg . spidev_path = " /dev/spidev0.0 " ,
. board_cfg . lorawan_public = true ,
. board_cfg . clksrc = 0 ,
. board_cfg . full_duplex = false ,
. rf_chain_cfg = { { 0 } } ,
. if_chain_cfg = { { 0 } } ,
. lora_service_cfg = {
. enable = 0 , /* not used, handled by if_chain_cfg */
. rf_chain = 0 , /* not used, handled by if_chain_cfg */
. freq_hz = 0 , /* not used, handled by if_chain_cfg */
. bandwidth = BW_250KHZ ,
. datarate = DR_LORA_SF7 ,
. implicit_hdr = false ,
. implicit_payload_length = 0 ,
. implicit_crc_en = 0 ,
. implicit_coderate = 0
} ,
. fsk_cfg = {
. enable = 0 , /* not used, handled by if_chain_cfg */
. rf_chain = 0 , /* not used, handled by if_chain_cfg */
. freq_hz = 0 , /* not used, handled by if_chain_cfg */
. bandwidth = BW_125KHZ ,
. datarate = 50000 ,
. sync_word_size = 3 ,
. sync_word = 0xC194C1
} ,
. tx_gain_lut = {
{
. size = 1 ,
. lut [ 0 ] = {
. rf_power = 14 ,
. dig_gain = 0 ,
. pa_gain = 2 ,
. dac_gain = 3 ,
. mix_gain = 10 ,
. offset_i = 0 ,
. offset_q = 0 ,
. pwr_idx = 0
}
} , {
. size = 1 ,
. lut [ 0 ] = {
. rf_power = 14 ,
. dig_gain = 0 ,
. pa_gain = 2 ,
. dac_gain = 3 ,
. mix_gain = 10 ,
. offset_i = 0 ,
. offset_q = 0 ,
. pwr_idx = 0
}
}
} ,
. timestamp_cfg = {
. enable_precision_ts = false ,
. max_ts_metrics = 0xFF ,
. nb_symbols = 1
} ,
. debug_cfg = {
. nb_ref_payload = 0 ,
. log_file_name = " loragw_hal.log "
}
} ;
/* File handle to write debug logs */
FILE * log_file = NULL ;
/* File descriptor to I2C linux device */
int lgw_i2c_target = - 1 ;
/* -------------------------------------------------------------------------- */
/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
int32_t lgw_sf_getval ( int x ) ;
int32_t lgw_bw_getval ( int x ) ;
/* -------------------------------------------------------------------------- */
/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
int32_t lgw_bw_getval ( int x ) {
switch ( x ) {
case BW_500KHZ : return 500000 ;
case BW_250KHZ : return 250000 ;
case BW_125KHZ : return 125000 ;
default : return - 1 ;
}
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
int32_t lgw_sf_getval ( int x ) {
switch ( x ) {
case DR_LORA_SF5 : return 5 ;
case DR_LORA_SF6 : return 6 ;
case DR_LORA_SF7 : return 7 ;
case DR_LORA_SF8 : return 8 ;
case DR_LORA_SF9 : return 9 ;
case DR_LORA_SF10 : return 10 ;
case DR_LORA_SF11 : return 11 ;
case DR_LORA_SF12 : return 12 ;
default : return - 1 ;
}
}
/* -------------------------------------------------------------------------- */
/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
int lgw_board_setconf ( struct lgw_conf_board_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 ;
}
/* set internal config according to parameters */
CONTEXT_LWAN_PUBLIC = conf - > lorawan_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 ) {
2019-07-22 16:50:17 +02:00
int i , err , err_id_1 , err_id_2 ;
2019-07-12 15:40:13 +02:00
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 */
2019-07-22 16:50:17 +02:00
err_id_1 = i2c_linuxdev_open ( I2C_DEVICE , I2C_PORT_TEMP_SENSOR_1 , & lgw_i2c_target ) ;
err_id_2 = i2c_linuxdev_open ( I2C_DEVICE , I2C_PORT_TEMP_SENSOR_2 , & lgw_i2c_target ) ;
if ( ( ( err_id_1 ! = 0 ) | | ( lgw_i2c_target < = 0 ) ) & & ( ( err_id_2 ! = 0 ) | | ( lgw_i2c_target < = 0 ) ) ) {
2019-07-12 15:40:13 +02:00
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 ( & current_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 ------------------------------------------------------------------ */